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().getTypePtr(); 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 if (!N) 223 return false; 224 Target = llvm::make_unique<ExtractionContext>(N, SM, Ctx); 225 return Target->isExtractable(); 226 } 227 228 Expected<Tweak::Effect> ExtractVariable::apply(const Selection &Inputs) { 229 tooling::Replacements Result; 230 // FIXME: get variable name from user or suggest based on type 231 std::string VarName = "dummy"; 232 // insert new variable declaration 233 if (auto Err = Result.add(Target->insertDeclaration(VarName))) 234 return std::move(Err); 235 // replace expression with variable name 236 if (auto Err = Result.add(Target->replaceWithVar(VarName))) 237 return std::move(Err); 238 return Effect::applyEdit(Result); 239 } 240 241 } // namespace 242 } // namespace clangd 243 } // namespace clang 244