Skip to content

Commit

Permalink
[clangd] Consider expression statements in ExtractVariable tweak (llv…
Browse files Browse the repository at this point in the history
…m#112525)

For instance:
  int func();
  int main()
  {
    func(); // => auto placeholder = func();
  }
  • Loading branch information
ckandeler authored Dec 11, 2024
1 parent 71fd528 commit 8d714db
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 17 deletions.
49 changes: 34 additions & 15 deletions clang-tools-extra/clangd/refactor/tweaks/ExtractVariable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ class ExtractionContext {
llvm::StringRef VarName) const;
// Generate Replacement for declaring the selected Expr as a new variable
tooling::Replacement insertDeclaration(llvm::StringRef VarName,
SourceRange InitChars) const;
SourceRange InitChars,
bool AddSemicolon) const;

private:
bool Extractable = false;
Expand Down Expand Up @@ -252,15 +253,18 @@ ExtractionContext::replaceWithVar(SourceRange Chars,
// returns the Replacement for declaring a new variable storing the extraction
tooling::Replacement
ExtractionContext::insertDeclaration(llvm::StringRef VarName,
SourceRange InitializerChars) const {
SourceRange InitializerChars,
bool AddSemicolon) const {
llvm::StringRef ExtractionCode = toSourceCode(SM, InitializerChars);
const SourceLocation InsertionLoc =
toHalfOpenFileRange(SM, Ctx.getLangOpts(),
InsertionPoint->getSourceRange())
->getBegin();
std::string ExtractedVarDecl =
printType(VarType, ExprNode->getDeclContext(), VarName) + " = " +
ExtractionCode.str() + "; ";
ExtractionCode.str();
if (AddSemicolon)
ExtractedVarDecl += "; ";
return tooling::Replacement(SM, InsertionLoc, 0, ExtractedVarDecl);
}

Expand Down Expand Up @@ -419,12 +423,10 @@ const SelectionTree::Node *getCallExpr(const SelectionTree::Node *DeclRef) {

// Returns true if Inner (which is a direct child of Outer) is appearing as
// a statement rather than an expression whose value can be used.
bool childExprIsStmt(const Stmt *Outer, const Expr *Inner) {
bool childExprIsDisallowedStmt(const Stmt *Outer, const Expr *Inner) {
if (!Outer || !Inner)
return false;
// Exclude the most common places where an expr can appear but be unused.
if (llvm::isa<CompoundStmt>(Outer))
return true;
if (llvm::isa<SwitchCase>(Outer))
return true;
// Control flow statements use condition etc, but not the body.
Expand Down Expand Up @@ -476,12 +478,9 @@ bool eligibleForExtraction(const SelectionTree::Node *N) {
const auto *Parent = OuterImplicit.Parent;
if (!Parent)
return false;
// We don't want to extract expressions used as statements, that would leave
// a `placeholder;` around that has no effect.
// Unfortunately because the AST doesn't have ExprStmt, we have to check in
// this roundabout way.
if (childExprIsStmt(Parent->ASTNode.get<Stmt>(),
OuterImplicit.ASTNode.get<Expr>()))
// Filter non-applicable expression statements.
if (childExprIsDisallowedStmt(Parent->ASTNode.get<Stmt>(),
OuterImplicit.ASTNode.get<Expr>()))
return false;

std::function<bool(const SelectionTree::Node *)> IsFullySelected =
Expand Down Expand Up @@ -516,6 +515,12 @@ bool eligibleForExtraction(const SelectionTree::Node *N) {
return false;
}

// If e.g. a capture clause was selected, the target node is the lambda
// expression. We only want to offer the extraction if the entire lambda
// expression was selected.
if (llvm::isa<LambdaExpr>(E))
return N->Selected == SelectionTree::Complete;

// The same logic as for assignments applies to initializations.
// However, we do allow extracting the RHS of an init capture, as it is
// a valid use case to move non-trivial expressions out of the capture clause.
Expand Down Expand Up @@ -599,10 +604,24 @@ Expected<Tweak::Effect> ExtractVariable::apply(const Selection &Inputs) {
// FIXME: get variable name from user or suggest based on type
std::string VarName = "placeholder";
SourceRange Range = Target->getExtractionChars();
// insert new variable declaration
if (auto Err = Result.add(Target->insertDeclaration(VarName, Range)))

const SelectionTree::Node &OuterImplicit =
Target->getExprNode()->outerImplicit();
assert(OuterImplicit.Parent);
bool IsExprStmt = llvm::isa_and_nonnull<CompoundStmt>(
OuterImplicit.Parent->ASTNode.get<Stmt>());

// insert new variable declaration. add a semicolon if and only if
// we are not dealing with an expression statement, which already has
// a semicolon that stays where it is, as it's not part of the range.
if (auto Err =
Result.add(Target->insertDeclaration(VarName, Range, !IsExprStmt)))
return std::move(Err);
// replace expression with variable name

// replace expression with variable name, unless it's an expression statement,
// in which case we remove it.
if (IsExprStmt)
VarName.clear();
if (auto Err = Result.add(Target->replaceWithVar(Range, VarName)))
return std::move(Err);
return Effect::mainFileEdit(Inputs.AST->getSourceManager(),
Expand Down
14 changes: 12 additions & 2 deletions clang-tools-extra/clangd/unittests/tweaks/ExtractVariableTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,8 @@ TEST_F(ExtractVariableTest, Test) {
// Variable DeclRefExpr
a = [[b]];
a = [[xyz()]];
// statement expression
[[xyz()]];
// expression statement of type void
[[v()]];
while (a)
[[++a]];
// label statement
Expand Down Expand Up @@ -493,6 +493,16 @@ TEST_F(ExtractVariableTest, Test) {
a = a + 1;
}
})cpp"},
{R"cpp(
int func() { return 0; }
int main() {
[[func()]];
})cpp",
R"cpp(
int func() { return 0; }
int main() {
auto placeholder = func();
})cpp"},
{R"cpp(
template <typename T>
auto call(T t) { return t(); }
Expand Down

0 comments on commit 8d714db

Please sign in to comment.