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