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 @@ -12,6 +12,7 @@ #include "SourceCode.h" #include "refactor/Tweak.h" #include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/OperationKinds.h" @@ -78,6 +79,23 @@ return true; } }; + + // Local variables declared inside of the selected lambda cannot go out of + // scope. The DeclRefExprs that are important are the lambda captures and + // capture var intitializers. + if (const auto *const LExpr = llvm::dyn_cast(Expr)) { + FindDeclRefsVisitor Visitor; + for (const auto &Capture : LExpr->captures()) { + if (Capture.capturesVariable()) { + auto *const CapturedVar = Capture.getCapturedVar(); + if (auto *const VDecl = llvm::dyn_cast(CapturedVar)) { + Visitor.TraverseStmt(VDecl->getInit()); + } + } + } + return Visitor.ReferencedDecls; + } + FindDeclRefsVisitor Visitor; Visitor.TraverseStmt(const_cast(cast(Expr))); return Visitor.ReferencedDecls; @@ -152,10 +170,12 @@ auto CanExtractOutside = [](const SelectionTree::Node *InsertionPoint) -> bool { if (const clang::Stmt *Stmt = InsertionPoint->ASTNode.get()) { - // Allow all expressions except LambdaExpr since we don't want to extract - // from the captures/default arguments of a lambda + // Allow all expressions except partial LambdaExpr selections since we + // don't want to extract from the captures/default arguments of a lambda if (isa(Stmt)) - return !isa(Stmt); + return !isa(Stmt) || + InsertionPoint->Selected == SelectionTree::Complete; + // 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. diff --git a/clang-tools-extra/clangd/unittests/tweaks/ExtractVariableTests.cpp b/clang-tools-extra/clangd/unittests/tweaks/ExtractVariableTests.cpp --- a/clang-tools-extra/clangd/unittests/tweaks/ExtractVariableTests.cpp +++ b/clang-tools-extra/clangd/unittests/tweaks/ExtractVariableTests.cpp @@ -98,6 +98,7 @@ return [[t]].bar([[t]].z); } void v() { return; } + template void callable_sink(T) {} // function default argument void f(int b = [[1]]) { // empty selection @@ -131,6 +132,21 @@ goto label; label: a = [[1]]; + + // lambdas + callable_sink([][[(){}]]); + + // captures + int x = 0; + callable_sink([ [[=]] ](){}); + callable_sink([ [[&]] ](){}); + callable_sink([ [[x]] ](){}); + callable_sink([ [[&x] ]](){}); + callable_sink([y = [[x]] ](){}); + callable_sink([ [[y = x]] ](){}); + + // default args + callable_sink([](int x = [[10]]){}); } )cpp"; EXPECT_UNAVAILABLE(UnavailableCases); @@ -282,6 +298,67 @@ void f() { auto placeholder = S(2) + S(3) + S(4); S x = S(1) + placeholder + S(5); })cpp"}, + // Complete lambda expressions + {R"cpp(template void f(T) {} + void f2() { + f([[ [](){ return 42; }]]); + } + )cpp", + R"cpp(template void f(T) {} + void f2() { + auto placeholder = [](){ return 42; }; f( placeholder); + } + )cpp"}, + {R"cpp(auto foo(int VarA) { + return [VarA]() { + return [[ [VarA, VarC = 42 + VarA](int VarB) { return VarA + VarB + VarC; }]]; + }; + } + )cpp", + R"cpp(auto foo(int VarA) { + return [VarA]() { + auto placeholder = [VarA, VarC = 42 + VarA](int VarB) { return VarA + VarB + VarC; }; return placeholder; + }; + } + )cpp"}, + {R"cpp(template void f(T) {} + void f2(int var) { + f([[ [&var](){ auto internal_val = 42; return var + internal_val; }]]); + } + )cpp", + R"cpp(template void f(T) {} + void f2(int var) { + auto placeholder = [&var](){ auto internal_val = 42; return var + internal_val; }; f( placeholder); + } + )cpp"}, + {R"cpp(template void f(T) { } + struct A { + void f2(int& var) { + auto local_var = 42; + f([[ [&var, &local_var, this]() { + auto internal_val = 42; + return var + local_var + internal_val + member; + }]]); + } + + int member = 42; +}; + )cpp", + R"cpp(template void f(T) { } + struct A { + void f2(int& var) { + auto local_var = 42; + auto placeholder = [&var, &local_var, this]() { + auto internal_val = 42; + return var + local_var + internal_val + member; + }; f( placeholder); + } + + int member = 42; +}; + )cpp"}, + {R"cpp(void f() { auto x = [[ [](){ return 42; }]]; })cpp", + R"cpp(void f() { auto placeholder = [](){ return 42; }; auto x = placeholder; })cpp"}, // Don't try to analyze across macro boundaries // FIXME: it'd be nice to do this someday (in a safe way) {R"cpp(#define ECHO(X) X diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -66,6 +66,11 @@ Code completion ^^^^^^^^^^^^^^^ +Code actions +^^^^^^^^^^^^ + +- The extract variable tweak gained support for extracting complete lambda expressions to a variable. + Signature help ^^^^^^^^^^^^^^