diff --git a/clang-tools-extra/clangd/refactor/tweaks/ExtractVariable.cpp b/clang-tools-extra/clangd/refactor/tweaks/ExtractVariable.cpp --- a/clang-tools-extra/clangd/refactor/tweaks/ExtractVariable.cpp +++ b/clang-tools-extra/clangd/refactor/tweaks/ExtractVariable.cpp @@ -319,55 +319,6 @@ return *toHalfOpenFileRange(SM, Ctx.getLangOpts(), Expr->getSourceRange()); } -/// 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 apply(const Selection &Inputs) override; - std::string title() const override { - return "Extract subexpression to variable"; - } - Intent intent() const override { return Refactor; } - // Compute the extraction context for the Selection - bool computeExtractionContext(const SelectionTree::Node *N, - const SourceManager &SM, const ASTContext &Ctx); - -private: - // the expression to extract - std::unique_ptr Target; -}; -REGISTER_TWEAK(ExtractVariable) -bool ExtractVariable::prepare(const Selection &Inputs) { - // we don't trigger on empty selections for now - if (Inputs.SelectionBegin == Inputs.SelectionEnd) - return false; - const ASTContext &Ctx = Inputs.AST.getASTContext(); - const SourceManager &SM = Inputs.AST.getSourceManager(); - const SelectionTree::Node *N = Inputs.ASTSelection.commonAncestor(); - return computeExtractionContext(N, SM, Ctx); -} - -Expected ExtractVariable::apply(const Selection &Inputs) { - tooling::Replacements Result; - // FIXME: get variable name from user or suggest based on type - std::string VarName = "dummy"; - SourceRange Range = Target->getExtractionChars(); - // insert new variable declaration - if (auto Err = Result.add(Target->insertDeclaration(VarName, Range))) - return std::move(Err); - // replace expression with variable name - if (auto Err = Result.add(Target->replaceWithVar(Range, VarName))) - return std::move(Err); - return Effect::applyEdit(Result); -} - // Find the CallExpr whose callee is the (possibly wrapped) DeclRef const SelectionTree::Node *getCallExpr(const SelectionTree::Node *DeclRef) { const SelectionTree::Node &MaybeCallee = DeclRef->outerImplicit(); @@ -445,25 +396,79 @@ return true; } -// Find the node that will form our ExtractionContext. +// Find the Expr node that we're going to extract. // We don't want to trigger for assignment expressions and variable/field // DeclRefs. For function/member function, we want to extract the entire // function call. -bool ExtractVariable::computeExtractionContext(const SelectionTree::Node *N, - const SourceManager &SM, - const ASTContext &Ctx) { +const SelectionTree::Node *computeExtractedExpr(const SelectionTree::Node *N) { if (!N) - return false; + return nullptr; const SelectionTree::Node *TargetNode = N; + const clang::Expr *SelectedExpr = N->ASTNode.get(); + if (!SelectedExpr) + return nullptr; // For function and member function DeclRefs, extract the whole call. - if (const Expr *E = N->ASTNode.get()) - if (llvm::isa(E) || llvm::isa(E)) - if (const SelectionTree::Node *Call = getCallExpr(N)) - TargetNode = Call; - if (!eligibleForExtraction(TargetNode)) + if (llvm::isa(SelectedExpr) || + llvm::isa(SelectedExpr)) + if (const SelectionTree::Node *Call = getCallExpr(N)) + TargetNode = Call; + // Extracting Exprs like a = 1 gives dummy = a = 1 which isn't useful. + if (const BinaryOperator *BinOpExpr = + dyn_cast_or_null(SelectedExpr)) { + if (BinOpExpr->getOpcode() == BinaryOperatorKind::BO_Assign) + return nullptr; + } + if (!TargetNode || !eligibleForExtraction(TargetNode)) + return nullptr; + return TargetNode; +} + +/// 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 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 Target; +}; +REGISTER_TWEAK(ExtractVariable) +bool ExtractVariable::prepare(const Selection &Inputs) { + // we don't trigger on empty selections for now + if (Inputs.SelectionBegin == Inputs.SelectionEnd) return false; - Target = llvm::make_unique(TargetNode, SM, Ctx); - return Target->isExtractable(); + const ASTContext &Ctx = Inputs.AST.getASTContext(); + const SourceManager &SM = Inputs.AST.getSourceManager(); + if (const SelectionTree::Node *N = + computeExtractedExpr(Inputs.ASTSelection.commonAncestor())) + Target = llvm::make_unique(N, SM, Ctx); + return Target && Target->isExtractable(); +} + +Expected ExtractVariable::apply(const Selection &Inputs) { + tooling::Replacements Result; + // FIXME: get variable name from user or suggest based on type + std::string VarName = "dummy"; + SourceRange Range = Target->getExtractionChars(); + // insert new variable declaration + if (auto Err = Result.add(Target->insertDeclaration(VarName, Range))) + return std::move(Err); + // replace expression with variable name + if (auto Err = Result.add(Target->replaceWithVar(Range, VarName))) + return std::move(Err); + return Effect::applyEdit(Result); } } // namespace