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