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
23namespace clang {
24namespace tooling {
25
26namespace {
27
28/// Returns true if \c E is a simple literal or a reference expression that
29/// should not be extracted.
30bool 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
47SourceLocation 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
59const 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
68Expected<ExtractFunction>
69ExtractFunction::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.
98Expected<AtomicChanges>
99ExtractFunction::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