blob: 525c259b7a5dbb928ef496ef2eba3a5d88e9efec [file] [log] [blame]
Chih-Hung Hsieh43f06942019-12-19 15:01:08 -08001//===--- 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
30namespace clang {
31namespace clangd {
32namespace {
33// information regarding the Expr that is being extracted
34class ExtractionContext {
35public:
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
46private:
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
63static std::vector<clang::Decl *>
64computeReferencedDecls(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 =
82static 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
92ExtractionContext::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
107bool 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
127const 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
170tooling::Replacement
171ExtractionContext::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
180tooling::Replacement
181ExtractionContext::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;
203class ExtractVariable : public Tweak {
204public:
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
213private:
214 // the expression to extract
215 std::unique_ptr<ExtractionContext> Target;
216};
217REGISTER_TWEAK(ExtractVariable)
218bool 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
229Expected<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