Skip to content

Commit 8d714db

Browse files
authored
[clangd] Consider expression statements in ExtractVariable tweak (#112525)
For instance: int func(); int main() { func(); // => auto placeholder = func(); }
1 parent 71fd528 commit 8d714db

File tree

2 files changed

+46
-17
lines changed

2 files changed

+46
-17
lines changed

clang-tools-extra/clangd/refactor/tweaks/ExtractVariable.cpp

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ class ExtractionContext {
4949
llvm::StringRef VarName) const;
5050
// Generate Replacement for declaring the selected Expr as a new variable
5151
tooling::Replacement insertDeclaration(llvm::StringRef VarName,
52-
SourceRange InitChars) const;
52+
SourceRange InitChars,
53+
bool AddSemicolon) const;
5354

5455
private:
5556
bool Extractable = false;
@@ -252,15 +253,18 @@ ExtractionContext::replaceWithVar(SourceRange Chars,
252253
// returns the Replacement for declaring a new variable storing the extraction
253254
tooling::Replacement
254255
ExtractionContext::insertDeclaration(llvm::StringRef VarName,
255-
SourceRange InitializerChars) const {
256+
SourceRange InitializerChars,
257+
bool AddSemicolon) const {
256258
llvm::StringRef ExtractionCode = toSourceCode(SM, InitializerChars);
257259
const SourceLocation InsertionLoc =
258260
toHalfOpenFileRange(SM, Ctx.getLangOpts(),
259261
InsertionPoint->getSourceRange())
260262
->getBegin();
261263
std::string ExtractedVarDecl =
262264
printType(VarType, ExprNode->getDeclContext(), VarName) + " = " +
263-
ExtractionCode.str() + "; ";
265+
ExtractionCode.str();
266+
if (AddSemicolon)
267+
ExtractedVarDecl += "; ";
264268
return tooling::Replacement(SM, InsertionLoc, 0, ExtractedVarDecl);
265269
}
266270

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

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

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

518+
// If e.g. a capture clause was selected, the target node is the lambda
519+
// expression. We only want to offer the extraction if the entire lambda
520+
// expression was selected.
521+
if (llvm::isa<LambdaExpr>(E))
522+
return N->Selected == SelectionTree::Complete;
523+
519524
// The same logic as for assignments applies to initializations.
520525
// However, we do allow extracting the RHS of an init capture, as it is
521526
// a valid use case to move non-trivial expressions out of the capture clause.
@@ -599,10 +604,24 @@ Expected<Tweak::Effect> ExtractVariable::apply(const Selection &Inputs) {
599604
// FIXME: get variable name from user or suggest based on type
600605
std::string VarName = "placeholder";
601606
SourceRange Range = Target->getExtractionChars();
602-
// insert new variable declaration
603-
if (auto Err = Result.add(Target->insertDeclaration(VarName, Range)))
607+
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>());
613+
614+
// insert new variable declaration. add a semicolon if and only if
615+
// we are not dealing with an expression statement, which already has
616+
// a semicolon that stays where it is, as it's not part of the range.
617+
if (auto Err =
618+
Result.add(Target->insertDeclaration(VarName, Range, !IsExprStmt)))
604619
return std::move(Err);
605-
// replace expression with variable name
620+
621+
// replace expression with variable name, unless it's an expression statement,
622+
// in which case we remove it.
623+
if (IsExprStmt)
624+
VarName.clear();
606625
if (auto Err = Result.add(Target->replaceWithVar(Range, VarName)))
607626
return std::move(Err);
608627
return Effect::mainFileEdit(Inputs.AST->getSourceManager(),

clang-tools-extra/clangd/unittests/tweaks/ExtractVariableTests.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,8 @@ TEST_F(ExtractVariableTest, Test) {
151151
// Variable DeclRefExpr
152152
a = [[b]];
153153
a = [[xyz()]];
154-
// statement expression
155-
[[xyz()]];
154+
// expression statement of type void
155+
[[v()]];
156156
while (a)
157157
[[++a]];
158158
// label statement
@@ -493,6 +493,16 @@ TEST_F(ExtractVariableTest, Test) {
493493
a = a + 1;
494494
}
495495
})cpp"},
496+
{R"cpp(
497+
int func() { return 0; }
498+
int main() {
499+
[[func()]];
500+
})cpp",
501+
R"cpp(
502+
int func() { return 0; }
503+
int main() {
504+
auto placeholder = func();
505+
})cpp"},
496506
{R"cpp(
497507
template <typename T>
498508
auto call(T t) { return t(); }

0 commit comments

Comments
 (0)