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 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