xref: /freebsd/contrib/llvm-project/clang/lib/Tooling/Refactoring/Extract/Extract.cpp (revision a90b9d0159070121c221b966469c3e36d912bf82)
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