1 | //===--- Extract.cpp - Clang refactoring library --------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | /// |
9 | /// \file |
10 | /// Implements the "extract" refactoring that can pull code into |
11 | /// new functions, methods or declare new variables. |
12 | /// |
13 | //===----------------------------------------------------------------------===// |
14 | |
15 | #include "clang/Tooling/Refactoring/Extract/Extract.h" |
16 | #include "SourceExtraction.h" |
17 | #include "clang/AST/ASTContext.h" |
18 | #include "clang/AST/DeclCXX.h" |
19 | #include "clang/AST/Expr.h" |
20 | #include "clang/AST/ExprObjC.h" |
21 | #include "clang/Rewrite/Core/Rewriter.h" |
22 | |
23 | namespace clang { |
24 | namespace tooling { |
25 | |
26 | namespace { |
27 | |
28 | /// Returns true if \c E is a simple literal or a reference expression that |
29 | /// should not be extracted. |
30 | bool isSimpleExpression(const Expr *E) { |
31 | if (!E) |
32 | return false; |
33 | switch (E->IgnoreParenCasts()->getStmtClass()) { |
34 | case Stmt::DeclRefExprClass: |
35 | case Stmt::PredefinedExprClass: |
36 | case Stmt::IntegerLiteralClass: |
37 | case Stmt::FloatingLiteralClass: |
38 | case Stmt::ImaginaryLiteralClass: |
39 | case Stmt::CharacterLiteralClass: |
40 | case Stmt::StringLiteralClass: |
41 | return true; |
42 | default: |
43 | return false; |
44 | } |
45 | } |
46 | |
47 | SourceLocation computeFunctionExtractionLocation(const Decl *D) { |
48 | if (isa<CXXMethodDecl>(D)) { |
49 | // Code from method that is defined in class body should be extracted to a |
50 | // function defined just before the class. |
51 | while (const auto *RD = dyn_cast<CXXRecordDecl>(D->getLexicalDeclContext())) |
52 | D = RD; |
53 | } |
54 | return D->getBeginLoc(); |
55 | } |
56 | |
57 | } // end anonymous namespace |
58 | |
59 | const RefactoringDescriptor &ExtractFunction::describe() { |
60 | static const RefactoringDescriptor Descriptor = { |
61 | "extract-function", |
62 | "Extract Function", |
63 | "(WIP action; use with caution!) Extracts code into a new function", |
64 | }; |
65 | return Descriptor; |
66 | } |
67 | |
68 | Expected<ExtractFunction> |
69 | ExtractFunction::initiate(RefactoringRuleContext &Context, |
70 | CodeRangeASTSelection Code, |
71 | Optional<std::string> DeclName) { |
72 | // We would like to extract code out of functions/methods/blocks. |
73 | // Prohibit extraction from things like global variable / field |
74 | // initializers and other top-level expressions. |
75 | if (!Code.isInFunctionLikeBodyOfCode()) |
76 | return Context.createDiagnosticError( |
77 | diag::err_refactor_code_outside_of_function); |
78 | |
79 | if (Code.size() == 1) { |
80 | // Avoid extraction of simple literals and references. |
81 | if (isSimpleExpression(dyn_cast<Expr>(Code[0]))) |
82 | return Context.createDiagnosticError( |
83 | diag::err_refactor_extract_simple_expression); |
84 | |
85 | // Property setters can't be extracted. |
86 | if (const auto *PRE = dyn_cast<ObjCPropertyRefExpr>(Code[0])) { |
87 | if (!PRE->isMessagingGetter()) |
88 | return Context.createDiagnosticError( |
89 | diag::err_refactor_extract_prohibited_expression); |
90 | } |
91 | } |
92 | |
93 | return ExtractFunction(std::move(Code), DeclName); |
94 | } |
95 | |
96 | // FIXME: Support C++ method extraction. |
97 | // FIXME: Support Objective-C method extraction. |
98 | Expected<AtomicChanges> |
99 | ExtractFunction::createSourceReplacements(RefactoringRuleContext &Context) { |
100 | const Decl *ParentDecl = Code.getFunctionLikeNearestParent(); |
101 | (0) . __assert_fail ("ParentDecl && \"missing parent\"", "/home/seafit/code_projects/clang_source/clang/lib/Tooling/Refactoring/Extract/Extract.cpp", 101, __PRETTY_FUNCTION__))" file_link="../../../../../include/assert.h.html#88" macro="true">assert(ParentDecl && "missing parent"); |
102 | |
103 | // Compute the source range of the code that should be extracted. |
104 | SourceRange ExtractedRange(Code[0]->getBeginLoc(), |
105 | Code[Code.size() - 1]->getEndLoc()); |
106 | // FIXME (Alex L): Add code that accounts for macro locations. |
107 | |
108 | ASTContext &AST = Context.getASTContext(); |
109 | SourceManager &SM = AST.getSourceManager(); |
110 | const LangOptions &LangOpts = AST.getLangOpts(); |
111 | Rewriter ExtractedCodeRewriter(SM, LangOpts); |
112 | |
113 | // FIXME: Capture used variables. |
114 | |
115 | // Compute the return type. |
116 | QualType ReturnType = AST.VoidTy; |
117 | // FIXME (Alex L): Account for the return statement in extracted code. |
118 | // FIXME (Alex L): Check for lexical expression instead. |
119 | bool IsExpr = Code.size() == 1 && isa<Expr>(Code[0]); |
120 | if (IsExpr) { |
121 | // FIXME (Alex L): Get a more user-friendly type if needed. |
122 | ReturnType = cast<Expr>(Code[0])->getType(); |
123 | } |
124 | |
125 | // FIXME: Rewrite the extracted code performing any required adjustments. |
126 | |
127 | // FIXME: Capture any field if necessary (method -> function extraction). |
128 | |
129 | // FIXME: Sort captured variables by name. |
130 | |
131 | // FIXME: Capture 'this' / 'self' if necessary. |
132 | |
133 | // FIXME: Compute the actual parameter types. |
134 | |
135 | // Compute the location of the extracted declaration. |
136 | SourceLocation ExtractedDeclLocation = |
137 | computeFunctionExtractionLocation(ParentDecl); |
138 | // FIXME: Adjust the location to account for any preceding comments. |
139 | |
140 | // FIXME: Adjust with PP awareness like in Sema to get correct 'bool' |
141 | // treatment. |
142 | PrintingPolicy PP = AST.getPrintingPolicy(); |
143 | // FIXME: PP.UseStdFunctionForLambda = true; |
144 | PP.SuppressStrongLifetime = true; |
145 | PP.SuppressLifetimeQualifiers = true; |
146 | PP.SuppressUnwrittenScope = true; |
147 | |
148 | ExtractionSemicolonPolicy Semicolons = ExtractionSemicolonPolicy::compute( |
149 | Code[Code.size() - 1], ExtractedRange, SM, LangOpts); |
150 | AtomicChange Change(SM, ExtractedDeclLocation); |
151 | // Create the replacement for the extracted declaration. |
152 | { |
153 | std::string ExtractedCode; |
154 | llvm::raw_string_ostream OS(ExtractedCode); |
155 | // FIXME: Use 'inline' in header. |
156 | OS << "static "; |
157 | ReturnType.print(OS, PP, DeclName); |
158 | OS << '('; |
159 | // FIXME: Arguments. |
160 | OS << ')'; |
161 | |
162 | // Function body. |
163 | OS << " {\n"; |
164 | if (IsExpr && !ReturnType->isVoidType()) |
165 | OS << "return "; |
166 | OS << ExtractedCodeRewriter.getRewrittenText(ExtractedRange); |
167 | if (Semicolons.isNeededInExtractedFunction()) |
168 | OS << ';'; |
169 | OS << "\n}\n\n"; |
170 | auto Err = Change.insert(SM, ExtractedDeclLocation, OS.str()); |
171 | if (Err) |
172 | return std::move(Err); |
173 | } |
174 | |
175 | // Create the replacement for the call to the extracted declaration. |
176 | { |
177 | std::string ReplacedCode; |
178 | llvm::raw_string_ostream OS(ReplacedCode); |
179 | |
180 | OS << DeclName << '('; |
181 | // FIXME: Forward arguments. |
182 | OS << ')'; |
183 | if (Semicolons.isNeededInOriginalFunction()) |
184 | OS << ';'; |
185 | |
186 | auto Err = Change.replace( |
187 | SM, CharSourceRange::getTokenRange(ExtractedRange), OS.str()); |
188 | if (Err) |
189 | return std::move(Err); |
190 | } |
191 | |
192 | // FIXME: Add support for assocciated symbol location to AtomicChange to mark |
193 | // the ranges of the name of the extracted declaration. |
194 | return AtomicChanges{std::move(Change)}; |
195 | } |
196 | |
197 | } // end namespace tooling |
198 | } // end namespace clang |
199 | |