[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