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
