clang-tools  9.0.0
ExtractVariable.cpp
Go to the documentation of this file.
1 //===--- ExtractVariable.cpp ------------------------------------*- C++-*-===//
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 #include "ClangdUnit.h"
9 #include "Logger.h"
10 #include "Protocol.h"
11 #include "Selection.h"
12 #include "SourceCode.h"
13 #include "refactor/Tweak.h"
14 #include "clang/AST/ASTContext.h"
15 #include "clang/AST/Expr.h"
16 #include "clang/AST/OperationKinds.h"
17 #include "clang/AST/RecursiveASTVisitor.h"
18 #include "clang/AST/Stmt.h"
19 #include "clang/AST/StmtCXX.h"
20 #include "clang/Basic/LangOptions.h"
21 #include "clang/Basic/SourceLocation.h"
22 #include "clang/Basic/SourceManager.h"
23 #include "clang/Tooling/Core/Replacement.h"
24 #include "llvm/ADT/None.h"
25 #include "llvm/ADT/SmallVector.h"
26 #include "llvm/ADT/StringRef.h"
27 #include "llvm/Support/Casting.h"
28 #include "llvm/Support/Error.h"
29 
30 namespace clang {
31 namespace clangd {
32 namespace {
33 // information regarding the Expr that is being extracted
34 class ExtractionContext {
35 public:
36  ExtractionContext(const SelectionTree::Node *Node, const SourceManager &SM,
37  const ASTContext &Ctx);
38  const clang::Expr *getExpr() const { return Expr; }
39  const SelectionTree::Node *getExprNode() const { return ExprNode; }
40  bool isExtractable() const { return Extractable; }
41  // Generate Replacement for replacing selected expression with given VarName
42  tooling::Replacement replaceWithVar(llvm::StringRef VarName) const;
43  // Generate Replacement for declaring the selected Expr as a new variable
44  tooling::Replacement insertDeclaration(llvm::StringRef VarName) const;
45 
46 private:
47  bool Extractable = false;
48  const clang::Expr *Expr;
49  const SelectionTree::Node *ExprNode;
50  // Stmt before which we will extract
51  const clang::Stmt *InsertionPoint = nullptr;
52  const SourceManager &SM;
53  const ASTContext &Ctx;
54  // Decls referenced in the Expr
55  std::vector<clang::Decl *> ReferencedDecls;
56  // returns true if the Expr doesn't reference any variable declared in scope
57  bool exprIsValidOutside(const clang::Stmt *Scope) const;
58  // computes the Stmt before which we will extract out Expr
59  const clang::Stmt *computeInsertionPoint() const;
60 };
61 
62 // Returns all the Decls referenced inside the given Expr
63 static std::vector<clang::Decl *>
64 computeReferencedDecls(const clang::Expr *Expr) {
65  // RAV subclass to find all DeclRefs in a given Stmt
66  class FindDeclRefsVisitor
67  : public clang::RecursiveASTVisitor<FindDeclRefsVisitor> {
68  public:
69  std::vector<Decl *> ReferencedDecls;
70  bool VisitDeclRefExpr(DeclRefExpr *DeclRef) { // NOLINT
71  ReferencedDecls.push_back(DeclRef->getDecl());
72  return true;
73  }
74  };
75  FindDeclRefsVisitor Visitor;
76  Visitor.TraverseStmt(const_cast<Stmt *>(dyn_cast<Stmt>(Expr)));
77  return Visitor.ReferencedDecls;
78 }
79 
80 // An expr is not extractable if it's null or an expression of type void
81 // FIXME: Ignore assignment (a = 1) Expr since it is extracted as dummy = a =
82 static bool isExtractableExpr(const clang::Expr *Expr) {
83  if (Expr) {
84  const Type *ExprType = Expr->getType().getTypePtrOrNull();
85  // FIXME: check if we need to cover any other types
86  if (ExprType)
87  return !ExprType->isVoidType();
88  }
89  return false;
90 }
91 
92 ExtractionContext::ExtractionContext(const SelectionTree::Node *Node,
93  const SourceManager &SM,
94  const ASTContext &Ctx)
95  : ExprNode(Node), SM(SM), Ctx(Ctx) {
96  Expr = Node->ASTNode.get<clang::Expr>();
97  if (isExtractableExpr(Expr)) {
98  ReferencedDecls = computeReferencedDecls(Expr);
99  InsertionPoint = computeInsertionPoint();
100  if (InsertionPoint)
101  Extractable = true;
102  }
103 }
104 
105 // checks whether extracting before InsertionPoint will take a
106 // variable reference out of scope
107 bool ExtractionContext::exprIsValidOutside(const clang::Stmt *Scope) const {
108  SourceLocation ScopeBegin = Scope->getBeginLoc();
109  SourceLocation ScopeEnd = Scope->getEndLoc();
110  for (const Decl *ReferencedDecl : ReferencedDecls) {
111  if (SM.isPointWithin(ReferencedDecl->getBeginLoc(), ScopeBegin, ScopeEnd) &&
112  SM.isPointWithin(ReferencedDecl->getEndLoc(), ScopeBegin, ScopeEnd))
113  return false;
114  }
115  return true;
116 }
117 
118 // Return the Stmt before which we need to insert the extraction.
119 // To find the Stmt, we go up the AST Tree and if the Parent of the current
120 // Stmt is a CompoundStmt, we can extract inside this CompoundStmt just before
121 // the current Stmt. We ALWAYS insert before a Stmt whose parent is a
122 // CompoundStmt
123 //
124 
125 // FIXME: Extraction from switch and case statements
126 // FIXME: Doens't work for FoldExpr
127 const clang::Stmt *ExtractionContext::computeInsertionPoint() const {
128  // returns true if we can extract before InsertionPoint
129  auto CanExtractOutside =
130  [](const SelectionTree::Node *InsertionPoint) -> bool {
131  if (const clang::Stmt *Stmt = InsertionPoint->ASTNode.get<clang::Stmt>()) {
132  // Allow all expressions except LambdaExpr since we don't want to extract
133  // from the captures/default arguments of a lambda
134  if (isa<clang::Expr>(Stmt))
135  return !isa<LambdaExpr>(Stmt);
136  // We don't yet allow extraction from switch/case stmt as we would need to
137  // jump over the switch stmt even if there is a CompoundStmt inside the
138  // switch. And there are other Stmts which we don't care about (e.g.
139  // continue and break) as there can never be anything to extract from
140  // them.
141  return isa<AttributedStmt>(Stmt) || isa<CompoundStmt>(Stmt) ||
142  isa<CXXForRangeStmt>(Stmt) || isa<DeclStmt>(Stmt) ||
143  isa<DoStmt>(Stmt) || isa<ForStmt>(Stmt) || isa<IfStmt>(Stmt) ||
144  isa<LabelStmt>(Stmt) || isa<ReturnStmt>(Stmt) ||
145  isa<WhileStmt>(Stmt);
146  }
147  if (InsertionPoint->ASTNode.get<VarDecl>())
148  return true;
149  return false;
150  };
151  for (const SelectionTree::Node *CurNode = getExprNode();
152  CurNode->Parent && CanExtractOutside(CurNode);
153  CurNode = CurNode->Parent) {
154  const clang::Stmt *CurInsertionPoint = CurNode->ASTNode.get<Stmt>();
155  // give up if extraction will take a variable out of scope
156  if (CurInsertionPoint && !exprIsValidOutside(CurInsertionPoint))
157  break;
158  if (const clang::Stmt *CurParent = CurNode->Parent->ASTNode.get<Stmt>()) {
159  if (isa<CompoundStmt>(CurParent)) {
160  // Ensure we don't write inside a macro.
161  if (CurParent->getBeginLoc().isMacroID())
162  continue;
163  return CurInsertionPoint;
164  }
165  }
166  }
167  return nullptr;
168 }
169 // returns the replacement for substituting the extraction with VarName
170 tooling::Replacement
171 ExtractionContext::replaceWithVar(llvm::StringRef VarName) const {
172  const llvm::Optional<SourceRange> ExtractionRng =
173  toHalfOpenFileRange(SM, Ctx.getLangOpts(), getExpr()->getSourceRange());
174  unsigned ExtractionLength = SM.getFileOffset(ExtractionRng->getEnd()) -
175  SM.getFileOffset(ExtractionRng->getBegin());
176  return tooling::Replacement(SM, ExtractionRng->getBegin(), ExtractionLength,
177  VarName);
178 }
179 // returns the Replacement for declaring a new variable storing the extraction
180 tooling::Replacement
181 ExtractionContext::insertDeclaration(llvm::StringRef VarName) const {
182  const llvm::Optional<SourceRange> ExtractionRng =
183  toHalfOpenFileRange(SM, Ctx.getLangOpts(), getExpr()->getSourceRange());
184  assert(ExtractionRng && "ExtractionRng should not be null");
185  llvm::StringRef ExtractionCode = toSourceCode(SM, *ExtractionRng);
186  const SourceLocation InsertionLoc =
187  toHalfOpenFileRange(SM, Ctx.getLangOpts(),
188  InsertionPoint->getSourceRange())
189  ->getBegin();
190  // FIXME: Replace auto with explicit type and add &/&& as necessary
191  std::string ExtractedVarDecl = std::string("auto ") + VarName.str() + " = " +
192  ExtractionCode.str() + "; ";
193  return tooling::Replacement(SM, InsertionLoc, 0, ExtractedVarDecl);
194 }
195 
196 /// Extracts an expression to the variable dummy
197 /// Before:
198 /// int x = 5 + 4 * 3;
199 /// ^^^^^
200 /// After:
201 /// auto dummy = 5 + 4;
202 /// int x = dummy * 3;
203 class ExtractVariable : public Tweak {
204 public:
205  const char *id() const override final;
206  bool prepare(const Selection &Inputs) override;
207  Expected<Effect> apply(const Selection &Inputs) override;
208  std::string title() const override {
209  return "Extract subexpression to variable";
210  }
211  Intent intent() const override { return Refactor; }
212 
213 private:
214  // the expression to extract
215  std::unique_ptr<ExtractionContext> Target;
216 };
217 REGISTER_TWEAK(ExtractVariable)
218 bool ExtractVariable::prepare(const Selection &Inputs) {
219  const ASTContext &Ctx = Inputs.AST.getASTContext();
220  const SourceManager &SM = Inputs.AST.getSourceManager();
221  const SelectionTree::Node *N = Inputs.ASTSelection.commonAncestor();
222  // we don't trigger on empty selections for now
223  if (!N || Inputs.SelectionBegin == Inputs.SelectionEnd)
224  return false;
225  Target = llvm::make_unique<ExtractionContext>(N, SM, Ctx);
226  return Target->isExtractable();
227 }
228 
229 Expected<Tweak::Effect> ExtractVariable::apply(const Selection &Inputs) {
230  tooling::Replacements Result;
231  // FIXME: get variable name from user or suggest based on type
232  std::string VarName = "dummy";
233  // insert new variable declaration
234  if (auto Err = Result.add(Target->insertDeclaration(VarName)))
235  return std::move(Err);
236  // replace expression with variable name
237  if (auto Err = Result.add(Target->replaceWithVar(VarName)))
238  return std::move(Err);
239  return Effect::applyEdit(Result);
240 }
241 
242 } // namespace
243 } // namespace clangd
244 } // namespace clang
#define REGISTER_TWEAK(Subclass)
Definition: Tweak.h:113
Context Ctx
llvm::Optional< SourceRange > toHalfOpenFileRange(const SourceManager &SM, const LangOptions &LangOpts, SourceRange R)
Turns a token range into a half-open range and checks its correctness.
Definition: SourceCode.cpp:331
llvm::StringRef toSourceCode(const SourceManager &SM, SourceRange R)
Returns the source code covered by the source range.
Definition: SourceCode.cpp:352
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
llvm::Optional< llvm::Expected< tooling::AtomicChanges > > Result
Definition: Rename.cpp:36
std::vector< const char * > Expected
const DeclRefExpr * DeclRef
NodeType Type