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
24namespace clang {
25namespace tooling {
26
27namespace {
28
29/// Returns true if \c E is a simple literal or a reference expression that
30/// should not be extracted.
31bool 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
48SourceLocation computeFunctionExtractionLocation(const Decl *D) {
49 if (isa<CXXMethodDecl>(Val: 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>(Val: D->getLexicalDeclContext()))
53 D = RD;
54 }
55 return D->getBeginLoc();
56}
57
58} // end anonymous namespace
59
60const RefactoringDescriptor &ExtractFunction::describe() {
61 static const RefactoringDescriptor Descriptor = {
62 .Name: "extract-function",
63 .Title: "Extract Function",
64 .Description: "(WIP action; use with caution!) Extracts code into a new function",
65 };
66 return Descriptor;
67}
68
69Expected<ExtractFunction>
70ExtractFunction::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 DiagID: diag::err_refactor_code_outside_of_function);
79
80 if (Code.size() == 1) {
81 // Avoid extraction of simple literals and references.
82 if (isSimpleExpression(E: dyn_cast<Expr>(Val: Code[0])))
83 return Context.createDiagnosticError(
84 DiagID: diag::err_refactor_extract_simple_expression);
85
86 // Property setters can't be extracted.
87 if (const auto *PRE = dyn_cast<ObjCPropertyRefExpr>(Val: Code[0])) {
88 if (!PRE->isMessagingGetter())
89 return Context.createDiagnosticError(
90 DiagID: 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.
99Expected<AtomicChanges>
100ExtractFunction::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>(Val: Code[0]);
121 if (IsExpr) {
122 // FIXME (Alex L): Get a more user-friendly type if needed.
123 ReturnType = cast<Expr>(Val: 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(D: 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 S: 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, Policy: PP, PlaceHolder: 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(Range: ExtractedRange);
168 if (Semicolons.isNeededInExtractedFunction())
169 OS << ';';
170 OS << "\n}\n\n";
171 auto Err = Change.insert(SM, Loc: ExtractedDeclLocation, Text: 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, Range: CharSourceRange::getTokenRange(R: ExtractedRange), ReplacementText: 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