[clang] Revert "[Clang] Rebuild lambda captures in default member initializers while skipping body (#196597)" (PR #211001)
Alexander Kornienko via cfe-commits
cfe-commits at lists.llvm.org
Tue Jul 21 06:49:59 PDT 2026
https://github.com/alexfh created https://github.com/llvm/llvm-project/pull/211001
This reverts commit 50f30bedaa81919915049474f4350ef19c36b7ca. The commit causes a crash in clang: https://github.com/llvm/llvm-project/pull/196597#issuecomment-4997866573
>From bed5ae1abc75152cf94e4418ec08bb5e8de1e9f0 Mon Sep 17 00:00:00 2001
From: Alexander Kornienko <alexfh at google.com>
Date: Tue, 21 Jul 2026 13:43:38 +0000
Subject: [PATCH] Revert "[Clang] Rebuild lambda captures in default member
initializers while skipping body (#196597)"
This reverts commit 50f30bedaa81919915049474f4350ef19c36b7ca.
---
clang/lib/Sema/SemaExpr.cpp | 56 +++----------------
...469-default-member-init-lambda-cleanup.cpp | 37 ------------
...469-default-member-init-lambda-capture.cpp | 22 --------
clang/test/SemaCXX/source_location.cpp | 12 ++++
4 files changed, 20 insertions(+), 107 deletions(-)
delete mode 100644 clang/test/CodeGenCXX/gh196469-default-member-init-lambda-cleanup.cpp
delete mode 100644 clang/test/SemaCXX/gh196469-default-member-init-lambda-capture.cpp
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index b844670543a55..81cff1e374aaf 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -5698,17 +5698,12 @@ struct ImmediateCallVisitor : DynamicRecursiveASTVisitor {
}
// A nested lambda might have parameters with immediate invocations
- // in their default arguments, or init-captures that are evaluated in the
- // enclosing context.
+ // in their default arguments.
// The compound statement is not visited (as it does not constitute a
// subexpression).
+ // FIXME: We should consider visiting and transforming captures
+ // with init expressions.
bool VisitLambdaExpr(LambdaExpr *E) override {
- auto Init = E->capture_init_begin();
- for (auto C = E->capture_begin(), CEnd = E->capture_end(); C != CEnd;
- ++C, ++Init) {
- if (E->isInitCapture(C) && !TraverseLambdaCapture(E, C, *Init))
- return false;
- }
return VisitCXXMethodDecl(E->getCallOperator());
}
@@ -5723,51 +5718,16 @@ struct ImmediateCallVisitor : DynamicRecursiveASTVisitor {
struct EnsureImmediateInvocationInDefaultArgs
: TreeTransform<EnsureImmediateInvocationInDefaultArgs> {
- using Base = TreeTransform<EnsureImmediateInvocationInDefaultArgs>;
-
EnsureImmediateInvocationInDefaultArgs(Sema &SemaRef)
: TreeTransform(SemaRef) {}
bool AlwaysRebuild() { return true; }
- bool ReplacingOriginal() { return true; }
-
- // Lambda bodies are not subexpressions of the enclosing default initializer,
- // but init-capture expressions are evaluated in the enclosing context. Keep
- // the existing closure type and capture declarations so the existing body
- // still refers to the right declarations.
- ExprResult TransformLambdaExpr(LambdaExpr *E) {
- SmallVector<Expr *, 4> CaptureInits(E->capture_inits());
-
- bool Changed = false;
- for (unsigned I = 0, N = E->capture_size(); I != N; ++I) {
- const LambdaCapture *C = E->capture_begin() + I;
- if (!E->isInitCapture(C))
- continue;
-
- auto *VD = cast<VarDecl>(C->getCapturedVar());
- Expr *Init = CaptureInits[I];
- ExprResult NewInit =
- TransformInitializer(Init, VD->getInitStyle() == VarDecl::CallInit);
- if (NewInit.isInvalid())
- return ExprError();
- Changed |= NewInit.get() != Init;
- CaptureInits[I] = NewInit.get();
- }
- LambdaExpr *Lambda = E;
- if (Changed) {
- // Reuse the existing closure class: it owns the capture declarations,
- // fields, and call operator body. Only the LambdaExpr's capture
- // initializer list is replaced.
- Lambda = LambdaExpr::Create(
- SemaRef.Context, E->getLambdaClass(), E->getIntroducerRange(),
- E->getCaptureDefault(), E->getCaptureDefaultLoc(),
- E->hasExplicitParameters(), E->hasExplicitResultType(), CaptureInits,
- E->getEndLoc(), E->containsUnexpandedParameterPack());
- }
-
- return SemaRef.MaybeBindToTemporary(Lambda);
- }
+ // Lambda can only have immediate invocations in the default
+ // args of their parameters, which is transformed upon calling the closure.
+ // The body is not a subexpression, so we have nothing to do.
+ // FIXME: Immediate calls in capture initializers should be transformed.
+ ExprResult TransformLambdaExpr(LambdaExpr *E) { return E; }
ExprResult TransformBlockExpr(BlockExpr *E) { return E; }
// Make sure we don't rebuild the this pointer as it would
diff --git a/clang/test/CodeGenCXX/gh196469-default-member-init-lambda-cleanup.cpp b/clang/test/CodeGenCXX/gh196469-default-member-init-lambda-cleanup.cpp
deleted file mode 100644
index 905db0f583afd..0000000000000
--- a/clang/test/CodeGenCXX/gh196469-default-member-init-lambda-cleanup.cpp
+++ /dev/null
@@ -1,37 +0,0 @@
-// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s | FileCheck %s
-
-struct Noisy {
- Noisy();
- ~Noisy();
-};
-
-struct Function {
- template <typename F> Function(F) {}
-};
-
-struct Options {
- Function function{[noisy = Noisy{}] {}};
-};
-
-Options kOptions{};
-
-int side();
-
-struct ReturnsCapture {
- int x;
- int value = [value = x] { return value; }();
-};
-
-ReturnsCapture kReturnsCapture{side()};
-
-// CHECK-LABEL: define internal void @__cxx_global_var_init
-// CHECK: call void @_ZN5NoisyC1Ev
-// CHECK: call void @_ZN8FunctionC1IN7Options8functionMUlvE_EEET_
-// CHECK: call void @_ZN7Options8functionMUlvE_D1Ev
-// CHECK: call {{.*}} @_ZNK14ReturnsCapture5valueMUlvE_clEv
-
-// CHECK-LABEL: define linkonce_odr {{.*}} @_ZNK14ReturnsCapture5valueMUlvE_clEv
-// CHECK: ret i32
-
-// CHECK-LABEL: define {{.*}} @_ZN7Options8functionMUlvE_D2Ev
-// CHECK: call void @_ZN5NoisyD1Ev
diff --git a/clang/test/SemaCXX/gh196469-default-member-init-lambda-capture.cpp b/clang/test/SemaCXX/gh196469-default-member-init-lambda-capture.cpp
deleted file mode 100644
index ed8335775ba67..0000000000000
--- a/clang/test/SemaCXX/gh196469-default-member-init-lambda-capture.cpp
+++ /dev/null
@@ -1,22 +0,0 @@
-// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify -verify-ignore-unexpected=note %s
-
-struct Noisy {
- int x;
- consteval Noisy(int x) : x(x) {}
- ~Noisy() {}
-};
-
-struct Function {
- template <typename F> Function(F) {}
-};
-
-struct Options {
- int x;
- Function function{ // expected-note {{declared here}}
- // expected-error at +1 {{call to consteval function}}
- [noisy = Noisy{x}] {}};
-};
-
-int foo();
-// expected-note at +1 {{in the default initializer of 'function'}}
-Options options{foo()};
diff --git a/clang/test/SemaCXX/source_location.cpp b/clang/test/SemaCXX/source_location.cpp
index 1ede22eaf458a..eaa6cb04c5d1c 100644
--- a/clang/test/SemaCXX/source_location.cpp
+++ b/clang/test/SemaCXX/source_location.cpp
@@ -862,11 +862,23 @@ struct CompoundLiteral {
static_assert(CompoundLiteral{}.a == __LINE__);
+// FIXME
+// Init captures are subexpressions of the lambda expression
+// so according to the standard immediate invocations in init captures
+// should be evaluated at the call site.
+// However Clang does not yet implement this as it would introduce
+// a fair bit of complexity.
+// We intend to implement that functionality once we find real world
+// use cases that require it.
constexpr int test_init_capture(int a =
[b = SL::current().line()] { return b; }()) {
return a;
}
+#if defined(USE_CONSTEVAL) && !defined(NEW_INTERP)
+static_assert(test_init_capture() == __LINE__ - 4);
+#else
static_assert(test_init_capture() == __LINE__ );
+#endif
namespace check_immediate_invocations_in_templates {
More information about the cfe-commits
mailing list