[clang] [Clang] Check consteval calls in lambda init-captures (PR #229953)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Oct 9 22:38:27 PDT 2026
https://github.com/beanscrack updated https://github.com/llvm/llvm-project/pull/229953
>From 7e4dadcdce418bd6e401a9e06c6c4e466976f48c Mon Sep 17 00:00:00 2001
From: fn <sumsjerrywu at gmail.com>
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 <sumsjerrywu at gmail.com>
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 <sumsjerrywu at gmail.com>
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
More information about the cfe-commits
mailing list