14#include "clang/AST/ASTContext.h"
15#include "clang/AST/Decl.h"
16#include "clang/AST/DeclCXX.h"
17#include "clang/AST/Expr.h"
18#include "clang/AST/ExprCXX.h"
19#include "clang/AST/LambdaCapture.h"
20#include "clang/AST/OperationKinds.h"
21#include "clang/AST/RecursiveASTVisitor.h"
22#include "clang/AST/Stmt.h"
23#include "clang/AST/StmtCXX.h"
24#include "clang/Basic/LangOptions.h"
25#include "clang/Basic/SourceLocation.h"
26#include "clang/Basic/SourceManager.h"
27#include "clang/Tooling/Core/Replacement.h"
28#include "llvm/ADT/SmallVector.h"
29#include "llvm/ADT/StringRef.h"
30#include "llvm/Support/Casting.h"
31#include "llvm/Support/Error.h"
32#include "llvm/Support/raw_ostream.h"
38class ExtractionContext {
40 ExtractionContext(
const SelectionTree::Node *Node,
const SourceManager &SM,
41 const ASTContext &Ctx);
42 const clang::Expr *getExpr()
const {
return Expr; }
43 const SelectionTree::Node *getExprNode()
const {
return ExprNode; }
44 bool isExtractable()
const {
return Extractable; }
46 SourceRange getExtractionChars()
const;
48 tooling::Replacement replaceWithVar(SourceRange Chars,
49 llvm::StringRef VarName)
const;
51 tooling::Replacement insertDeclaration(llvm::StringRef VarName,
52 SourceRange InitChars,
53 bool AddSemicolon)
const;
56 bool Extractable =
false;
57 const clang::Expr *Expr;
59 const SelectionTree::Node *ExprNode;
61 const clang::Stmt *InsertionPoint =
nullptr;
62 const SourceManager &SM;
63 const ASTContext &Ctx;
65 std::vector<clang::Decl *> ReferencedDecls;
67 bool exprIsValidOutside(
const clang::Stmt *Scope)
const;
69 const clang::Stmt *computeInsertionPoint()
const;
73static std::vector<clang::Decl *>
74computeReferencedDecls(
const clang::Expr *Expr) {
76 class FindDeclRefsVisitor
77 :
public clang::RecursiveASTVisitor<FindDeclRefsVisitor> {
79 std::vector<Decl *> ReferencedDecls;
80 bool VisitDeclRefExpr(DeclRefExpr *
DeclRef) {
83 if (
const auto *
const Method =
84 llvm::dyn_cast<CXXMethodDecl>(
DeclRef->getDecl());
85 Method !=
nullptr &&
Method->getParent()->isLambda()) {
88 ReferencedDecls.push_back(
DeclRef->getDecl());
97 bool TraverseLambdaExpr(LambdaExpr *LExpr) {
98 for (
const auto &[Capture, Initializer] :
99 llvm::zip(LExpr->captures(), LExpr->capture_inits())) {
100 TraverseLambdaCapture(LExpr, &Capture, Initializer);
103 if (clang::Expr *
const RequiresClause =
104 LExpr->getTrailingRequiresClause()) {
105 TraverseStmt(RequiresClause);
108 for (
auto *
const TemplateParam : LExpr->getExplicitTemplateParameters())
109 TraverseDecl(TemplateParam);
111 if (
auto *
const CallOperator = LExpr->getCallOperator()) {
112 TraverseType(CallOperator->getDeclaredReturnType());
114 for (
auto *
const Param : CallOperator->parameters()) {
115 TraverseParmVarDecl(Param);
118 for (
auto *
const Attr : CallOperator->attrs()) {
127 FindDeclRefsVisitor Visitor;
128 Visitor.TraverseStmt(
const_cast<Stmt *
>(cast<Stmt>(Expr)));
129 return Visitor.ReferencedDecls;
132static QualType computeVariableType(
const Expr *Expr,
const ASTContext &Ctx) {
133 if (Ctx.getLangOpts().CPlusPlus11)
134 return Ctx.getAutoDeductType();
136 if (Expr->hasPlaceholderType(BuiltinType::PseudoObject)) {
137 if (
const auto *PR = dyn_cast<ObjCPropertyRefExpr>(Expr)) {
138 if (PR->isMessagingSetter()) {
144 }
else if (PR->isMessagingGetter()) {
145 if (PR->isExplicitProperty())
146 return PR->getExplicitProperty()->getType();
148 return PR->getImplicitPropertyGetter()->getReturnType();
154 return Expr->getType();
157ExtractionContext::ExtractionContext(
const SelectionTree::Node *Node,
158 const SourceManager &SM,
159 const ASTContext &Ctx)
160 : ExprNode(
Node), SM(SM), Ctx(Ctx) {
161 Expr =
Node->ASTNode.get<clang::Expr>();
162 ReferencedDecls = computeReferencedDecls(Expr);
163 InsertionPoint = computeInsertionPoint();
166 VarType = computeVariableType(Expr, Ctx);
167 if (VarType.isNull())
171 AttributedType::stripOuterNullability(VarType);
176bool ExtractionContext::exprIsValidOutside(
const clang::Stmt *Scope)
const {
177 SourceLocation ScopeBegin = Scope->getBeginLoc();
178 SourceLocation ScopeEnd = Scope->getEndLoc();
179 for (
const Decl *ReferencedDecl : ReferencedDecls) {
180 if (ReferencedDecl->getBeginLoc().isValid() &&
181 SM.isPointWithin(ReferencedDecl->getBeginLoc(), ScopeBegin, ScopeEnd) &&
182 SM.isPointWithin(ReferencedDecl->getEndLoc(), ScopeBegin, ScopeEnd))
197const clang::Stmt *ExtractionContext::computeInsertionPoint()
const {
199 auto CanExtractOutside =
200 [](
const SelectionTree::Node *InsertionPoint) ->
bool {
201 if (
const clang::Stmt *Stmt = InsertionPoint->ASTNode.get<clang::Stmt>()) {
202 if (isa<clang::Expr>(Stmt)) {
205 if (InsertionPoint->Parent->ASTNode.get<ParmVarDecl>() !=
nullptr) {
217 return isa<AttributedStmt>(Stmt) || isa<CompoundStmt>(Stmt) ||
218 isa<CXXForRangeStmt>(Stmt) || isa<DeclStmt>(Stmt) ||
219 isa<DoStmt>(Stmt) || isa<ForStmt>(Stmt) || isa<IfStmt>(Stmt) ||
220 isa<ReturnStmt>(Stmt) || isa<WhileStmt>(Stmt);
222 if (InsertionPoint->ASTNode.get<VarDecl>())
226 for (
const SelectionTree::Node *CurNode = getExprNode();
227 CurNode->Parent && CanExtractOutside(CurNode);
228 CurNode = CurNode->Parent) {
229 const clang::Stmt *CurInsertionPoint = CurNode->ASTNode.get<Stmt>();
231 if (CurInsertionPoint && !exprIsValidOutside(CurInsertionPoint))
233 if (
const clang::Stmt *CurParent = CurNode->Parent->ASTNode.get<Stmt>()) {
234 if (isa<CompoundStmt>(CurParent)) {
236 if (CurParent->getBeginLoc().isMacroID())
238 return CurInsertionPoint;
247ExtractionContext::replaceWithVar(SourceRange Chars,
248 llvm::StringRef VarName)
const {
249 unsigned ExtractionLength =
250 SM.getFileOffset(Chars.getEnd()) - SM.getFileOffset(Chars.getBegin());
251 return tooling::Replacement(SM, Chars.getBegin(), ExtractionLength, VarName);
255ExtractionContext::insertDeclaration(llvm::StringRef VarName,
256 SourceRange InitializerChars,
257 bool AddSemicolon)
const {
258 llvm::StringRef ExtractionCode =
toSourceCode(SM, InitializerChars);
259 const SourceLocation InsertionLoc =
261 InsertionPoint->getSourceRange())
263 std::string ExtractedVarDecl =
264 printType(VarType, ExprNode->getDeclContext(), VarName) +
" = " +
265 ExtractionCode.str();
267 ExtractedVarDecl +=
"; ";
268 return tooling::Replacement(SM, InsertionLoc, 0, ExtractedVarDecl);
289struct ParsedBinaryOperator {
295 bool parse(
const SelectionTree::Node &N) {
298 if (
const BinaryOperator *Op =
299 llvm::dyn_cast_or_null<BinaryOperator>(N.ASTNode.get<Expr>())) {
300 Kind = Op->getOpcode();
305 if (
const CXXOperatorCallExpr *Op =
306 llvm::dyn_cast_or_null<CXXOperatorCallExpr>(
307 N.ASTNode.get<Expr>())) {
308 if (!Op->isInfixBinaryOp())
311 Kind = BinaryOperator::getOverloadedOpcode(Op->getOperator());
314 for (
const auto *Child : N.Children) {
315 const Expr *
E = Child->ASTNode.get<Expr>();
316 assert(
E &&
"callee and args should be Exprs!");
317 if (
E == Op->getArg(0) ||
E == Op->getArg(1))
325 bool associative()
const {
341 bool crossesMacroBoundary(
const SourceManager &SM) {
342 FileID F = SM.getFileID(
ExprLoc);
344 if (SM.getFileID(Child->ASTNode.get<Expr>()->getExprLoc()) != F)
362const SourceRange getBinaryOperatorRange(
const SelectionTree::Node &N,
363 const SourceManager &SM,
364 const LangOptions &LangOpts) {
366 ParsedBinaryOperator Op;
367 if (!Op.parse(N.ignoreImplicit()) || !Op.associative() ||
368 Op.crossesMacroBoundary(SM) || Op.SelectedOperands.size() != 2)
369 return SourceRange();
370 BinaryOperatorKind OuterOp = Op.Kind;
376 const SelectionTree::Node *Start = Op.SelectedOperands.front();
377 const SelectionTree::Node *End = Op.SelectedOperands.back();
380 while (Op.parse(Start->ignoreImplicit()) && Op.Kind == OuterOp &&
381 !Op.crossesMacroBoundary(SM)) {
382 assert(!Op.SelectedOperands.empty() &&
"got only operator on one side!");
383 if (Op.SelectedOperands.size() == 1) {
384 Start = Op.SelectedOperands.back();
388 Start = Op.SelectedOperands.front();
398SourceRange ExtractionContext::getExtractionChars()
const {
400 SourceRange BinaryOperatorRange =
401 getBinaryOperatorRange(*ExprNode, SM, Ctx.getLangOpts());
402 if (BinaryOperatorRange.isValid())
403 return BinaryOperatorRange;
410const SelectionTree::Node *getCallExpr(
const SelectionTree::Node *
DeclRef) {
411 const SelectionTree::Node &MaybeCallee =
DeclRef->outerImplicit();
412 const SelectionTree::Node *MaybeCall = MaybeCallee.Parent;
416 llvm::dyn_cast_or_null<CallExpr>(MaybeCall->ASTNode.get<Expr>());
419 if (
CE->getCallee() != MaybeCallee.ASTNode.get<Expr>())
426bool childExprIsDisallowedStmt(
const Stmt *Outer,
const Expr *
Inner) {
427 if (!Outer || !
Inner)
430 if (llvm::isa<SwitchCase>(Outer))
433 if (
const auto *WS = llvm::dyn_cast<WhileStmt>(Outer))
434 return Inner == WS->getBody();
435 if (
const auto *DS = llvm::dyn_cast<DoStmt>(Outer))
436 return Inner == DS->getBody();
437 if (
const auto *FS = llvm::dyn_cast<ForStmt>(Outer))
438 return Inner == FS->getBody();
439 if (
const auto *FS = llvm::dyn_cast<CXXForRangeStmt>(Outer))
440 return Inner == FS->getBody();
441 if (
const auto *IS = llvm::dyn_cast<IfStmt>(Outer))
442 return Inner == IS->getThen() ||
Inner == IS->getElse();
449bool eligibleForExtraction(
const SelectionTree::Node *N) {
450 const Expr *
E = N->ASTNode.get<Expr>();
455 const Type *ExprType =
E->getType().getTypePtrOrNull();
456 if (!ExprType || ExprType->isVoidType())
461 if (llvm::isa<DeclRefExpr>(
E))
465 if (
const auto *ME = dyn_cast<MemberExpr>(
E))
466 if (
const auto *TE = dyn_cast<CXXThisExpr>(ME->getBase()->IgnoreImpCasts()))
467 if (TE->isImplicit())
472 ParsedBinaryOperator BinOp;
473 bool IsBinOp = BinOp.parse(*N);
474 if (IsBinOp && BinaryOperator::isAssignmentOp(BinOp.Kind))
477 const SelectionTree::Node &OuterImplicit = N->outerImplicit();
478 const auto *
Parent = OuterImplicit.Parent;
482 if (childExprIsDisallowedStmt(
Parent->ASTNode.get<Stmt>(),
483 OuterImplicit.ASTNode.get<Expr>()))
486 std::function<bool(
const SelectionTree::Node *)> IsFullySelected =
487 [&](
const SelectionTree::Node *N) {
488 if (N->ASTNode.getSourceRange().isValid() &&
489 N->Selected != SelectionTree::Complete)
491 for (
const auto *Child : N->Children) {
492 if (!IsFullySelected(Child))
497 auto ExprIsFullySelectedTargetNode = [&](
const Expr *
E) {
498 if (
E != OuterImplicit.ASTNode.get<Expr>())
507 return IsFullySelected(N);
513 if (
const auto *BO =
Parent->ASTNode.get<BinaryOperator>()) {
514 if (BO->isAssignmentOp() && ExprIsFullySelectedTargetNode(BO->getRHS()))
521 if (llvm::isa<LambdaExpr>(
E))
522 return N->Selected == SelectionTree::Complete;
529 if (
const auto *
Decl =
Parent->ASTNode.get<VarDecl>()) {
530 if (!
Decl->isInitCapture() &&
531 ExprIsFullySelectedTargetNode(
Decl->getInit())) {
543const SelectionTree::Node *computeExtractedExpr(
const SelectionTree::Node *N) {
546 const SelectionTree::Node *TargetNode = N;
547 const clang::Expr *SelectedExpr = N->ASTNode.get<clang::Expr>();
551 if (llvm::isa<DeclRefExpr>(SelectedExpr) ||
552 llvm::isa<MemberExpr>(SelectedExpr))
553 if (
const SelectionTree::Node *Call = getCallExpr(N))
556 if (
const BinaryOperator *BinOpExpr =
557 dyn_cast_or_null<BinaryOperator>(SelectedExpr)) {
558 if (BinOpExpr->getOpcode() == BinaryOperatorKind::BO_Assign)
561 if (!TargetNode || !eligibleForExtraction(TargetNode))
573class ExtractVariable :
public Tweak {
575 const char *id() const final;
576 bool prepare(const Selection &Inputs) override;
577 Expected<Effect> apply(const Selection &Inputs) override;
578 std::
string title()
const override {
579 return "Extract subexpression to variable";
581 llvm::StringLiteral kind()
const override {
582 return CodeAction::REFACTOR_KIND;
587 std::unique_ptr<ExtractionContext> Target;
590bool ExtractVariable::prepare(
const Selection &Inputs) {
592 if (Inputs.SelectionBegin == Inputs.SelectionEnd)
594 const ASTContext &Ctx = Inputs.AST->getASTContext();
595 const SourceManager &SM = Inputs.AST->getSourceManager();
596 if (
const SelectionTree::Node *N =
597 computeExtractedExpr(Inputs.ASTSelection.commonAncestor()))
598 Target = std::make_unique<ExtractionContext>(N, SM, Ctx);
599 return Target && Target->isExtractable();
602Expected<Tweak::Effect> ExtractVariable::apply(
const Selection &Inputs) {
603 tooling::Replacements Result;
605 std::string VarName =
"placeholder";
606 SourceRange
Range = Target->getExtractionChars();
608 const SelectionTree::Node &OuterImplicit =
609 Target->getExprNode()->outerImplicit();
610 assert(OuterImplicit.Parent);
611 bool IsExprStmt = llvm::isa_and_nonnull<CompoundStmt>(
612 OuterImplicit.Parent->ASTNode.get<Stmt>());
618 Result.add(Target->insertDeclaration(VarName,
Range, !IsExprStmt)))
619 return std::move(Err);
625 if (
auto Err = Result.add(Target->replaceWithVar(
Range, VarName)))
626 return std::move(Err);
627 return Effect::mainFileEdit(Inputs.AST->getSourceManager(),
const FunctionDecl * Decl
std::pair< Context, Canceler > Inner
CharSourceRange Range
SourceRange for the file name.
std::vector< const char * > Expected
::clang::DynTypedNode Node
#define REGISTER_TWEAK(Subclass)
const DeclRefExpr * DeclRef
std::optional< SourceRange > toHalfOpenFileRange(const SourceManager &SM, const LangOptions &LangOpts, SourceRange R)
Turns a token range into a half-open range and checks its correctness.
std::string printType(const QualType QT, const DeclContext &CurContext, const llvm::StringRef Placeholder)
Returns a QualType as string.
llvm::StringRef toSourceCode(const SourceManager &SM, SourceRange R)
Returns the source code covered by the source range.
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//