xref: /llvm-project/clang-tools-extra/clangd/refactor/tweaks/ExtractVariable.cpp (revision 76ba1cf1f1e80c75b3ae943d86e8531bc2eeec2b)
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/ExprCXX.h"
17 #include "clang/AST/OperationKinds.h"
18 #include "clang/AST/RecursiveASTVisitor.h"
19 #include "clang/AST/Stmt.h"
20 #include "clang/AST/StmtCXX.h"
21 #include "clang/Basic/LangOptions.h"
22 #include "clang/Basic/SourceLocation.h"
23 #include "clang/Basic/SourceManager.h"
24 #include "clang/Tooling/Core/Replacement.h"
25 #include "llvm/ADT/None.h"
26 #include "llvm/ADT/SmallVector.h"
27 #include "llvm/ADT/StringRef.h"
28 #include "llvm/Support/Casting.h"
29 #include "llvm/Support/Error.h"
30 
31 namespace clang {
32 namespace clangd {
33 namespace {
34 // information regarding the Expr that is being extracted
35 class ExtractionContext {
36 public:
37   ExtractionContext(const SelectionTree::Node *Node, const SourceManager &SM,
38                     const ASTContext &Ctx);
39   const clang::Expr *getExpr() const { return Expr; }
40   const SelectionTree::Node *getExprNode() const { return ExprNode; }
41   bool isExtractable() const { return Extractable; }
42   // Generate Replacement for replacing selected expression with given VarName
43   tooling::Replacement replaceWithVar(llvm::StringRef VarName) const;
44   // Generate Replacement for declaring the selected Expr as a new variable
45   tooling::Replacement insertDeclaration(llvm::StringRef VarName) const;
46 
47 private:
48   bool Extractable = false;
49   const clang::Expr *Expr;
50   const SelectionTree::Node *ExprNode;
51   // Stmt before which we will extract
52   const clang::Stmt *InsertionPoint = nullptr;
53   const SourceManager &SM;
54   const ASTContext &Ctx;
55   // Decls referenced in the Expr
56   std::vector<clang::Decl *> ReferencedDecls;
57   // returns true if the Expr doesn't reference any variable declared in scope
58   bool exprIsValidOutside(const clang::Stmt *Scope) const;
59   // computes the Stmt before which we will extract out Expr
60   const clang::Stmt *computeInsertionPoint() const;
61 };
62 
63 // Returns all the Decls referenced inside the given Expr
64 static std::vector<clang::Decl *>
65 computeReferencedDecls(const clang::Expr *Expr) {
66   // RAV subclass to find all DeclRefs in a given Stmt
67   class FindDeclRefsVisitor
68       : public clang::RecursiveASTVisitor<FindDeclRefsVisitor> {
69   public:
70     std::vector<Decl *> ReferencedDecls;
71     bool VisitDeclRefExpr(DeclRefExpr *DeclRef) { // NOLINT
72       ReferencedDecls.push_back(DeclRef->getDecl());
73       return true;
74     }
75   };
76   FindDeclRefsVisitor Visitor;
77   Visitor.TraverseStmt(const_cast<Stmt *>(dyn_cast<Stmt>(Expr)));
78   return Visitor.ReferencedDecls;
79 }
80 
81 ExtractionContext::ExtractionContext(const SelectionTree::Node *Node,
82                                      const SourceManager &SM,
83                                      const ASTContext &Ctx)
84     : ExprNode(Node), SM(SM), Ctx(Ctx) {
85   Expr = Node->ASTNode.get<clang::Expr>();
86   ReferencedDecls = computeReferencedDecls(Expr);
87   InsertionPoint = computeInsertionPoint();
88   if (InsertionPoint)
89     Extractable = true;
90 }
91 
92 // checks whether extracting before InsertionPoint will take a
93 // variable reference out of scope
94 bool ExtractionContext::exprIsValidOutside(const clang::Stmt *Scope) const {
95   SourceLocation ScopeBegin = Scope->getBeginLoc();
96   SourceLocation ScopeEnd = Scope->getEndLoc();
97   for (const Decl *ReferencedDecl : ReferencedDecls) {
98     if (SM.isPointWithin(ReferencedDecl->getBeginLoc(), ScopeBegin, ScopeEnd) &&
99         SM.isPointWithin(ReferencedDecl->getEndLoc(), ScopeBegin, ScopeEnd))
100       return false;
101   }
102   return true;
103 }
104 
105 // Return the Stmt before which we need to insert the extraction.
106 // To find the Stmt, we go up the AST Tree and if the Parent of the current
107 // Stmt is a CompoundStmt, we can extract inside this CompoundStmt just before
108 // the current Stmt. We ALWAYS insert before a Stmt whose parent is a
109 // CompoundStmt
110 //
111 // FIXME: Extraction from label, switch and case statements
112 // FIXME: Doens't work for FoldExpr
113 // FIXME: Ensure extraction from loops doesn't change semantics.
114 const clang::Stmt *ExtractionContext::computeInsertionPoint() const {
115   // returns true if we can extract before InsertionPoint
116   auto CanExtractOutside =
117       [](const SelectionTree::Node *InsertionPoint) -> bool {
118     if (const clang::Stmt *Stmt = InsertionPoint->ASTNode.get<clang::Stmt>()) {
119       // Allow all expressions except LambdaExpr since we don't want to extract
120       // from the captures/default arguments of a lambda
121       if (isa<clang::Expr>(Stmt))
122         return !isa<LambdaExpr>(Stmt);
123       // We don't yet allow extraction from switch/case stmt as we would need to
124       // jump over the switch stmt even if there is a CompoundStmt inside the
125       // switch. And there are other Stmts which we don't care about (e.g.
126       // continue and break) as there can never be anything to extract from
127       // them.
128       return isa<AttributedStmt>(Stmt) || isa<CompoundStmt>(Stmt) ||
129              isa<CXXForRangeStmt>(Stmt) || isa<DeclStmt>(Stmt) ||
130              isa<DoStmt>(Stmt) || isa<ForStmt>(Stmt) || isa<IfStmt>(Stmt) ||
131              isa<ReturnStmt>(Stmt) || isa<WhileStmt>(Stmt);
132     }
133     if (InsertionPoint->ASTNode.get<VarDecl>())
134       return true;
135     return false;
136   };
137   for (const SelectionTree::Node *CurNode = getExprNode();
138        CurNode->Parent && CanExtractOutside(CurNode);
139        CurNode = CurNode->Parent) {
140     const clang::Stmt *CurInsertionPoint = CurNode->ASTNode.get<Stmt>();
141     // give up if extraction will take a variable out of scope
142     if (CurInsertionPoint && !exprIsValidOutside(CurInsertionPoint))
143       break;
144     if (const clang::Stmt *CurParent = CurNode->Parent->ASTNode.get<Stmt>()) {
145       if (isa<CompoundStmt>(CurParent)) {
146         // Ensure we don't write inside a macro.
147         if (CurParent->getBeginLoc().isMacroID())
148           continue;
149         return CurInsertionPoint;
150       }
151     }
152   }
153   return nullptr;
154 }
155 // returns the replacement for substituting the extraction with VarName
156 tooling::Replacement
157 ExtractionContext::replaceWithVar(llvm::StringRef VarName) const {
158   const llvm::Optional<SourceRange> ExtractionRng =
159       toHalfOpenFileRange(SM, Ctx.getLangOpts(), getExpr()->getSourceRange());
160   unsigned ExtractionLength = SM.getFileOffset(ExtractionRng->getEnd()) -
161                               SM.getFileOffset(ExtractionRng->getBegin());
162   return tooling::Replacement(SM, ExtractionRng->getBegin(), ExtractionLength,
163                               VarName);
164 }
165 // returns the Replacement for declaring a new variable storing the extraction
166 tooling::Replacement
167 ExtractionContext::insertDeclaration(llvm::StringRef VarName) const {
168   const llvm::Optional<SourceRange> ExtractionRng =
169       toHalfOpenFileRange(SM, Ctx.getLangOpts(), getExpr()->getSourceRange());
170   assert(ExtractionRng && "ExtractionRng should not be null");
171   llvm::StringRef ExtractionCode = toSourceCode(SM, *ExtractionRng);
172   const SourceLocation InsertionLoc =
173       toHalfOpenFileRange(SM, Ctx.getLangOpts(),
174                           InsertionPoint->getSourceRange())
175           ->getBegin();
176   // FIXME: Replace auto with explicit type and add &/&& as necessary
177   std::string ExtractedVarDecl = std::string("auto ") + VarName.str() + " = " +
178                                  ExtractionCode.str() + "; ";
179   return tooling::Replacement(SM, InsertionLoc, 0, ExtractedVarDecl);
180 }
181 
182 /// Extracts an expression to the variable dummy
183 /// Before:
184 /// int x = 5 + 4 * 3;
185 ///         ^^^^^
186 /// After:
187 /// auto dummy = 5 + 4;
188 /// int x = dummy * 3;
189 class ExtractVariable : public Tweak {
190 public:
191   const char *id() const override final;
192   bool prepare(const Selection &Inputs) override;
193   Expected<Effect> apply(const Selection &Inputs) override;
194   std::string title() const override {
195     return "Extract subexpression to variable";
196   }
197   Intent intent() const override { return Refactor; }
198   // Compute the extraction context for the Selection
199   bool computeExtractionContext(const SelectionTree::Node *N,
200                                 const SourceManager &SM, const ASTContext &Ctx);
201 
202 private:
203   // the expression to extract
204   std::unique_ptr<ExtractionContext> Target;
205 };
206 REGISTER_TWEAK(ExtractVariable)
207 bool ExtractVariable::prepare(const Selection &Inputs) {
208   // we don't trigger on empty selections for now
209   if (Inputs.SelectionBegin == Inputs.SelectionEnd)
210     return false;
211   const ASTContext &Ctx = Inputs.AST.getASTContext();
212   const SourceManager &SM = Inputs.AST.getSourceManager();
213   const SelectionTree::Node *N = Inputs.ASTSelection.commonAncestor();
214   return computeExtractionContext(N, SM, Ctx);
215 }
216 
217 Expected<Tweak::Effect> ExtractVariable::apply(const Selection &Inputs) {
218   tooling::Replacements Result;
219   // FIXME: get variable name from user or suggest based on type
220   std::string VarName = "dummy";
221   // insert new variable declaration
222   if (auto Err = Result.add(Target->insertDeclaration(VarName)))
223     return std::move(Err);
224   // replace expression with variable name
225   if (auto Err = Result.add(Target->replaceWithVar(VarName)))
226     return std::move(Err);
227   return Effect::applyEdit(Result);
228 }
229 
230 // Find the CallExpr whose callee is an ancestor of the DeclRef
231 const SelectionTree::Node *getCallExpr(const SelectionTree::Node *DeclRef) {
232   // we maintain a stack of all exprs encountered while traversing the
233   // selectiontree because the callee of the callexpr can be an ancestor of the
234   // DeclRef. e.g. Callee can be an ImplicitCastExpr.
235   std::vector<const clang::Expr *> ExprStack;
236   for (auto *CurNode = DeclRef; CurNode; CurNode = CurNode->Parent) {
237     const Expr *CurExpr = CurNode->ASTNode.get<Expr>();
238     if (const CallExpr *CallPar = CurNode->ASTNode.get<CallExpr>()) {
239       // check whether the callee of the callexpr is present in Expr stack.
240       if (std::find(ExprStack.begin(), ExprStack.end(), CallPar->getCallee()) !=
241           ExprStack.end())
242         return CurNode;
243       return nullptr;
244     }
245     ExprStack.push_back(CurExpr);
246   }
247   return nullptr;
248 }
249 
250 // check if Expr can be assigned to a variable i.e. is non-void type
251 bool canBeAssigned(const SelectionTree::Node *ExprNode) {
252   const clang::Expr *Expr = ExprNode->ASTNode.get<clang::Expr>();
253   if (const Type *ExprType = Expr->getType().getTypePtrOrNull())
254     // FIXME: check if we need to cover any other types
255     return !ExprType->isVoidType();
256   return true;
257 }
258 
259 // Find the node that will form our ExtractionContext.
260 // We don't want to trigger for assignment expressions and variable/field
261 // DeclRefs. For function/member function, we want to extract the entire
262 // function call.
263 bool ExtractVariable::computeExtractionContext(const SelectionTree::Node *N,
264                                                const SourceManager &SM,
265                                                const ASTContext &Ctx) {
266   if (!N)
267     return false;
268   const clang::Expr *SelectedExpr = N->ASTNode.get<clang::Expr>();
269   const SelectionTree::Node *TargetNode = N;
270   if (!SelectedExpr)
271     return false;
272   // Extracting Exprs like a = 1 gives dummy = a = 1 which isn't useful.
273   if (const BinaryOperator *BinOpExpr =
274           dyn_cast_or_null<BinaryOperator>(SelectedExpr)) {
275     if (BinOpExpr->isAssignmentOp())
276       return false;
277   }
278   // For function and member function DeclRefs, we look for a parent that is a
279   // CallExpr
280   if (const DeclRefExpr *DeclRef =
281           dyn_cast_or_null<DeclRefExpr>(SelectedExpr)) {
282     // Extracting just a variable isn't that useful.
283     if (!isa<FunctionDecl>(DeclRef->getDecl()))
284       return false;
285     TargetNode = getCallExpr(N);
286   }
287   if (const MemberExpr *Member = dyn_cast_or_null<MemberExpr>(SelectedExpr)) {
288     // Extracting just a field member isn't that useful.
289     if (!isa<CXXMethodDecl>(Member->getMemberDecl()))
290       return false;
291     TargetNode = getCallExpr(N);
292   }
293   if (!TargetNode || !canBeAssigned(TargetNode))
294     return false;
295   Target = llvm::make_unique<ExtractionContext>(TargetNode, SM, Ctx);
296   return Target->isExtractable();
297 }
298 
299 } // namespace
300 } // namespace clangd
301 } // namespace clang
302