https://github.com/beanscrack updated 
https://github.com/llvm/llvm-project/pull/229953

>From 7e4dadcdce418bd6e401a9e06c6c4e466976f48c Mon Sep 17 00:00:00 2001
From: fn <[email protected]>
Date: Wed, 7 Oct 2026 19:43:37 -0400
Subject: [PATCH 1/3] [Clang] Check consteval calls in lambda init-captures

---
 clang/lib/Sema/SemaExpr.cpp                   | 67 ++++++++++++++++---
 ...nit-capture-default-member-initializer.cpp | 36 ++++++++++
 2 files changed, 95 insertions(+), 8 deletions(-)
 create mode 100644 
clang/test/SemaCXX/consteval-lambda-init-capture-default-member-initializer.cpp

diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index a51889ed9b3aae..8afaf55ed7c134 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -5770,10 +5770,15 @@ struct ImmediateCallVisitor : 
DynamicRecursiveASTVisitor {
   // A nested lambda might have parameters with immediate invocations
   // 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.
+  // subexpression). Init-capture expressions, however, are evaluated as part
+  // of the lambda expression and need to be checked here.
   bool VisitLambdaExpr(LambdaExpr *E) override {
+    auto Init = E->capture_init_begin();
+    for (const LambdaCapture &Capture : E->captures()) {
+      if (E->isInitCapture(&Capture) && !TraverseStmt(*Init))
+        return false;
+      ++Init;
+    }
     return VisitCXXMethodDecl(E->getCallOperator());
   }
 
@@ -5793,11 +5798,57 @@ struct EnsureImmediateInvocationInDefaultArgs
 
   bool AlwaysRebuild() { return true; }
 
-  // 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; }
+  // Lambda default arguments are transformed upon calling the closure. The
+  // body is not a subexpression, so only transform init-captures here.
+  ExprResult TransformLambdaExpr(LambdaExpr *E) {
+    SmallVector<Expr *, 4> CaptureInits(E->capture_inits().begin(),
+                                        E->capture_inits().end());
+    bool Changed = false;
+    auto Init = CaptureInits.begin();
+    for (LambdaExpr::capture_iterator C = E->capture_begin(),
+                                      CEnd = E->capture_end();
+         C != CEnd; ++C, ++Init) {
+      if (!E->isInitCapture(C))
+        continue;
+
+      auto *CaptureVar = cast<VarDecl>(C->getCapturedVar());
+      ImmediateCallVisitor V(SemaRef.Context);
+      V.TraverseStmt(CaptureVar->getInit());
+      if (!V.HasImmediateCalls)
+        continue;
+
+      ExprResult NewInit =
+          TransformInitializer(CaptureVar->getInit(),
+                               CaptureVar->getInitStyle() == 
VarDecl::CallInit);
+      if (NewInit.isInvalid())
+        return ExprError();
+
+      Expr *NewInitExpr = NewInit.get();
+      SourceLocation EllipsisLoc =
+          C->isPackExpansion() ? C->getEllipsisLoc() : SourceLocation();
+      QualType InitCaptureType = SemaRef.buildLambdaInitCaptureInitialization(
+          C->getLocation(), C->getCaptureKind() == LCK_ByRef, EllipsisLoc,
+          std::nullopt, CaptureVar->getIdentifier(),
+          CaptureVar->getInitStyle() != VarDecl::CInit, NewInitExpr);
+      if (InitCaptureType.isNull())
+        return ExprError();
+      assert(InitCaptureType == CaptureVar->getType() &&
+             "init-capture type changed during immediate invocation check");
+
+      *Init = NewInitExpr;
+      Changed = true;
+    }
+
+    if (!Changed)
+      return E;
+
+    return LambdaExpr::Create(
+        SemaRef.Context, E->getLambdaClass(), E->getIntroducerRange(),
+        E->getCaptureDefault(), E->getCaptureDefaultLoc(),
+        E->hasExplicitParameters(), E->hasExplicitResultType(), CaptureInits,
+        E->getEndLoc(), E->containsUnexpandedParameterPack());
+  }
+
   ExprResult TransformBlockExpr(BlockExpr *E) { return E; }
 
   // Make sure we don't rebuild the this pointer as it would
diff --git 
a/clang/test/SemaCXX/consteval-lambda-init-capture-default-member-initializer.cpp
 
b/clang/test/SemaCXX/consteval-lambda-init-capture-default-member-initializer.cpp
new file mode 100644
index 00000000000000..9ce175217f1341
--- /dev/null
+++ 
b/clang/test/SemaCXX/consteval-lambda-init-capture-default-member-initializer.cpp
@@ -0,0 +1,36 @@
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -std=c++23 -fsyntax-only -verify %s
+
+int gv = 5; // expected-note 2 {{declared here}}
+constexpr int cgv = 5;
+
+struct W {
+  int value;
+  consteval W(int x) : value(x) {}
+};
+
+consteval int id(int x) { return x; }
+
+struct S {
+  int member = [captured = W(gv).value] { return captured; }(); // 
expected-error {{call to consteval function 'W::W' is not a constant 
expression}} \
+  // expected-note {{declared here}} \
+  // expected-note {{read of non-const variable 'gv' is not allowed in a 
constant expression}}
+};
+
+struct F {
+  int member = [captured = id(gv)] { return captured; }(); // expected-error 
{{call to consteval function 'id' is not a constant expression}} \
+  // expected-note {{declared here}} \
+  // expected-note {{read of non-const variable 'gv' is not allowed in a 
constant expression}}
+};
+
+struct Valid {
+  int member = [captured = W(cgv).value] { return captured; }();
+  int function = [captured = id(cgv)] { return captured; }();
+};
+
+static_assert(Valid{}.member == 5);
+static_assert(Valid{}.function == 5);
+
+int main() {
+  return S{}.member + F{}.member; // expected-note 2 {{in the default 
initializer of 'member'}}
+}

>From ff980c8a2459edd6ac3e5b89a1cd238f6f781101 Mon Sep 17 00:00:00 2001
From: fn <[email protected]>
Date: Thu, 8 Oct 2026 20:56:36 -0400
Subject: [PATCH 2/3] [Clang] Add mixed-capture tests and update
 source-location expectation

---
 ...nit-capture-default-member-initializer.cpp | 23 +++++++++++++++++--
 clang/test/SemaCXX/source_location.cpp        | 16 +++----------
 2 files changed, 24 insertions(+), 15 deletions(-)

diff --git 
a/clang/test/SemaCXX/consteval-lambda-init-capture-default-member-initializer.cpp
 
b/clang/test/SemaCXX/consteval-lambda-init-capture-default-member-initializer.cpp
index 9ce175217f1341..11ee30f3b69847 100644
--- 
a/clang/test/SemaCXX/consteval-lambda-init-capture-default-member-initializer.cpp
+++ 
b/clang/test/SemaCXX/consteval-lambda-init-capture-default-member-initializer.cpp
@@ -1,7 +1,7 @@
 // RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s
 // RUN: %clang_cc1 -std=c++23 -fsyntax-only -verify %s
 
-int gv = 5; // expected-note 2 {{declared here}}
+int gv = 5; // expected-note 4 {{declared here}}
 constexpr int cgv = 5;
 
 struct W {
@@ -23,14 +23,33 @@ struct F {
   // expected-note {{read of non-const variable 'gv' is not allowed in a 
constant expression}}
 };
 
+struct MixedThisFirst {
+  int member = [this, captured = W(gv).value] { return captured; }(); // 
expected-error {{call to consteval function 'W::W' is not a constant 
expression}} \
+  // expected-note {{declared here}} \
+  // expected-note {{read of non-const variable 'gv' is not allowed in a 
constant expression}}
+};
+
+struct MixedThisLast {
+  int member = [captured = id(gv), this] { return captured; }(); // 
expected-error {{call to consteval function 'id' is not a constant expression}} 
\
+  // expected-note {{declared here}} \
+  // expected-note {{read of non-const variable 'gv' is not allowed in a 
constant expression}}
+};
+
 struct Valid {
   int member = [captured = W(cgv).value] { return captured; }();
   int function = [captured = id(cgv)] { return captured; }();
+
+  int mixed_ctor =
+      [this, captured = W(cgv).value] { return captured; }();
+  int mixed_function =
+      [captured = id(cgv), this] { return captured; }();
 };
 
 static_assert(Valid{}.member == 5);
 static_assert(Valid{}.function == 5);
+static_assert(Valid{}.mixed_ctor == 5);
+static_assert(Valid{}.mixed_function == 5);
 
 int main() {
-  return S{}.member + F{}.member; // expected-note 2 {{in the default 
initializer of 'member'}}
+  return S{}.member + F{}.member + MixedThisFirst{}.member + 
MixedThisLast{}.member; // expected-note 4 {{in the default initializer of 
'member'}}
 }
diff --git a/clang/test/SemaCXX/source_location.cpp 
b/clang/test/SemaCXX/source_location.cpp
index 8049430738654c..2266aa4de65525 100644
--- a/clang/test/SemaCXX/source_location.cpp
+++ b/clang/test/SemaCXX/source_location.cpp
@@ -862,23 +862,13 @@ 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.
+// Init-captures are subexpressions of the lambda expression, so immediate
+// invocations in them are evaluated where the default argument is used.
 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
+static_assert(test_init_capture() == __LINE__);
 
 namespace check_immediate_invocations_in_templates {
 

>From df0cfacb7977a6119defa16f3e1adc90d7c21148 Mon Sep 17 00:00:00 2001
From: fn <[email protected]>
Date: Sat, 10 Oct 2026 01:38:13 -0400
Subject: [PATCH 3/3] [Clang] Add consteval copy-capture tests and release note

---
 clang/docs/ReleaseNotes.md                    |  3 ++
 ...nit-capture-default-member-initializer.cpp | 43 +++++++++++++++++++
 2 files changed, 46 insertions(+)

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index bac1ce6dc45535..c03d603458e59d 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -706,6 +706,9 @@ features cannot lower the translation-unit ABI level;
 
 #### Bug Fixes to C++ Support
 
+- Fixed missing diagnostics for invalid `consteval` calls in lambda
+  init-captures used in default member initializers. (#GH229809)
+
 - Fixed lambdas with specifiers or attributes after the capture list being
   misparsed as function declarations in direct-initialization contexts under
   `-fms-extensions` or in HLSL mode.
diff --git 
a/clang/test/SemaCXX/consteval-lambda-init-capture-default-member-initializer.cpp
 
b/clang/test/SemaCXX/consteval-lambda-init-capture-default-member-initializer.cpp
index 11ee30f3b69847..d2f106a8ff920d 100644
--- 
a/clang/test/SemaCXX/consteval-lambda-init-capture-default-member-initializer.cpp
+++ 
b/clang/test/SemaCXX/consteval-lambda-init-capture-default-member-initializer.cpp
@@ -53,3 +53,46 @@ static_assert(Valid{}.mixed_function == 5);
 int main() {
   return S{}.member + F{}.member + MixedThisFirst{}.member + 
MixedThisLast{}.member; // expected-note 4 {{in the default initializer of 
'member'}}
 }
+
+namespace consteval_copy_capture {
+struct Copyable {
+  int value;
+  constexpr Copyable(int x) : value(x) {}
+  consteval Copyable(const Copyable &other) : value(other.value) {} // 
expected-note {{read of non-constexpr variable 'runtime_source' is not allowed 
in a constant expression}} \
+  // expected-note {{read of non-constexpr variable 'local_source' is not 
allowed in a constant expression}}
+};
+
+Copyable runtime_source{5}; // expected-note {{declared here}}
+constexpr Copyable constant_source{5};
+
+// Copying an existing lvalue forces the consteval copy constructor to run.
+struct InvalidInitCopy {
+  int member = [captured = runtime_source] { return captured.value; }(); // 
expected-error {{call to consteval function 
'consteval_copy_capture::Copyable::Copyable' is not a constant expression}} \
+  // expected-note {{declared here}} \
+  // expected-note {{in call to 'Copyable(runtime_source)'}}
+};
+
+struct ValidInitCopy {
+  int member = [captured = constant_source] { return captured.value; }();
+};
+
+static_assert(ValidInitCopy{}.member == 5);
+
+int use_invalid_init_copy() {
+  return InvalidInitCopy{}.member; // expected-note {{in the default 
initializer of 'member'}}
+}
+
+// Also cover ordinary by-value captures.
+void invalid_regular_copy() {
+  Copyable local_source{5}; // expected-note {{declared here}}
+  auto lambda = [local_source] { return local_source.value; }; // 
expected-error {{call to consteval function 
'consteval_copy_capture::Copyable::Copyable' is not a constant expression}} \
+  // expected-note {{in call to 'Copyable(local_source)'}}
+}
+
+constexpr int valid_regular_copy() {
+  constexpr Copyable local_source{5};
+  return [local_source] { return local_source.value; }();
+}
+
+static_assert(valid_regular_copy() == 5);
+} // namespace consteval_copy_capture

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to