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