https://github.com/Ferdi265 updated https://github.com/llvm/llvm-project/pull/235356
>From 195de4bf8ae46a8d9989803aaf0e229c8a2b1db9 Mon Sep 17 00:00:00 2001 From: Ferdinand Bachmann <[email protected]> Date: Sat, 17 Oct 2026 11:26:25 +0200 Subject: [PATCH 1/4] [Clang][AST] Fix lambda conversion operators not being immediate-escalating [expr.prim.lambda.closure]/p11 defines that the function F should be immediate if the call operator is immediate (i.e. immediate-escalating), but says nothing about whether the call operator should be. Since it falls under none of the other definitions of [expr.const.imm]/p3 it should not be immediate-escalating per the letter of the standard. However, since the conversion operator for a consteval lambda would otherwise be uncallable, consider this a defect in the standard and make the conversion operator immediate-escalating. This is also how other compilers, such as GCC, implement it. Fixes #235353. Signed-off-by: Ferdinand Bachmann <[email protected]> --- clang/include/clang/AST/ASTLambda.h | 12 ++++++------ clang/lib/AST/Decl.cpp | 4 ++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/clang/include/clang/AST/ASTLambda.h b/clang/include/clang/AST/ASTLambda.h index a1854b6a1a9491..eeb1fc6064d3ab 100644 --- a/clang/include/clang/AST/ASTLambda.h +++ b/clang/include/clang/AST/ASTLambda.h @@ -66,17 +66,17 @@ inline bool isGenericLambdaCallOperatorSpecialization(const CXXMethodDecl *MD) { return false; } -inline bool isLambdaConversionOperator(CXXConversionDecl *C) { +inline bool isLambdaConversionOperator(const CXXConversionDecl *C) { return C ? C->getParent()->isLambda() : false; } -inline bool isLambdaConversionOperator(Decl *D) { +inline bool isLambdaConversionOperator(const Decl *D) { if (!D) return false; - if (CXXConversionDecl *Conv = dyn_cast<CXXConversionDecl>(D)) + if (const CXXConversionDecl *Conv = dyn_cast<CXXConversionDecl>(D)) return isLambdaConversionOperator(Conv); - if (FunctionTemplateDecl *F = dyn_cast<FunctionTemplateDecl>(D)) - if (CXXConversionDecl *Conv = - dyn_cast_or_null<CXXConversionDecl>(F->getTemplatedDecl())) + if (const FunctionTemplateDecl *F = dyn_cast<FunctionTemplateDecl>(D)) + if (const CXXConversionDecl *Conv = + dyn_cast_or_null<CXXConversionDecl>(F->getTemplatedDecl())) return isLambdaConversionOperator(Conv); return false; } diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index f920a1e9b8fd85..4431301ecf69a8 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -3370,6 +3370,10 @@ bool FunctionDecl::isImmediateEscalating() const { // specifier, if (isLambdaCallOperator(this) && !isConsteval()) return true; + // defect: [expr.prim.lambda.closure]/p11 should define that the conversion + // operator is immediate if the call operator is immediate (i.e. escalating) + if (isLambdaConversionOperator(this) && !isConsteval()) + return true; // - a defaulted special member function that is not declared with the // consteval specifier, if (isDefaulted() && !isConsteval()) >From fbe9002d8b7928d84775cd95d53718bee8f97c1d Mon Sep 17 00:00:00 2001 From: Ferdinand Bachmann <[email protected]> Date: Sat, 17 Oct 2026 21:09:04 +0200 Subject: [PATCH 2/4] [Clang][Sema] Fix SynthesizedFunctionScope not escalating immediate functions properly CheckImmediateEscalatingFunctionDefinition() relies on FSI->FoundImmediateEscalatingExpression already being set for the function. This only happens in HandleImmediateInvocations() as part of PopExpressionEvaluationContext(), which was only being called afterward. Ensure we call PopExpressionEvaluationContext() before CheckImmediateEscalatingFunctionDefinition(). This fixes immediate escalation of functions defined using SynthesizedFunctionScope, such as lambda conversion operators. --- clang/include/clang/Sema/Sema.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index cd2227f2aa5c69..9062355b99b179 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -13809,11 +13809,11 @@ class Sema final : public SemaBase { ~SynthesizedFunctionScope() { if (PushedCodeSynthesisContext) S.popCodeSynthesisContext(); + S.PopExpressionEvaluationContext(); if (auto *FD = dyn_cast<FunctionDecl>(S.CurContext)) { FD->setWillHaveBody(false); S.CheckImmediateEscalatingFunctionDefinition(FD, S.getCurFunction()); } - S.PopExpressionEvaluationContext(); S.PopFunctionScopeInfo(); } >From d9d33989dc275221d3209d3a1f730193b4388c48 Mon Sep 17 00:00:00 2001 From: Ferdinand Bachmann <[email protected]> Date: Sat, 17 Oct 2026 19:47:47 +0200 Subject: [PATCH 3/4] [Clang][Sema] Fix lambda methods inheriting immediate function context Since lambda conversion operators are now immediate escalating, we don't need this special case for lambda methods any more. Additionally, the previous fix only worked in situations where an outer immediate function context was allowed to "leak in", and didn't correctly capture cases where such a lambda method was called from a non-immediate scope. --- clang/lib/Sema/SemaExpr.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 43b0c86132172b..7177d5faf46cf0 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -18598,8 +18598,6 @@ void Sema::PushExpressionEvaluationContextForFunction( FD && FD->isConsteval() ? ExpressionEvaluationContext::ImmediateFunctionContext : NewContext); - const Sema::ExpressionEvaluationContextRecord &Parent = - parentEvaluationContext(); Sema::ExpressionEvaluationContextRecord &Current = currentEvaluationContext(); Current.InDiscardedStatement = false; @@ -18617,12 +18615,7 @@ void Sema::PushExpressionEvaluationContextForFunction( Current.InImmediateEscalatingFunctionContext = getLangOpts().CPlusPlus20 && FD->isImmediateEscalating(); - if (isLambdaMethod(FD) && !isLambdaCallOperator(FD)) - Current.InImmediateFunctionContext = FD->isConsteval() || - Parent.isConstantEvaluated() || - Parent.isImmediateFunctionContext(); - else - Current.InImmediateFunctionContext = FD->isConsteval(); + Current.InImmediateFunctionContext = FD->isConsteval(); } } >From bf84d64d4174e889835decfc7432f2810cec800c Mon Sep 17 00:00:00 2001 From: Ferdinand Bachmann <[email protected]> Date: Sun, 18 Oct 2026 18:04:27 +0200 Subject: [PATCH 4/4] WIP: update consteval lambda fptr conversion testcases --- .../cxx23-static-callop-lambda-expression.cpp | 8 +++---- clang/test/SemaCXX/cxx2a-consteval.cpp | 23 ++++++++++++------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/clang/test/SemaCXX/cxx23-static-callop-lambda-expression.cpp b/clang/test/SemaCXX/cxx23-static-callop-lambda-expression.cpp index 84fac9edad1027..2d7c1b25660e8c 100644 --- a/clang/test/SemaCXX/cxx23-static-callop-lambda-expression.cpp +++ b/clang/test/SemaCXX/cxx23-static-callop-lambda-expression.cpp @@ -9,12 +9,12 @@ namespace ns1 { namespace ns1_1 { - auto lstatic = []() static consteval //expected-error{{cannot take address of consteval call}} \ - expected-note {{declared here}} + auto lstatic = []() static consteval // expected-note {{operator int (*)()' is an immediate function because its body evaluates the address of a consteval function 'operator()'}} \ + // expected-note {{declared here}} { return 3; }; - // FIXME: the above error should indicate that it was triggered below. - int (*f2)(void) = lstatic; + int (*f2)(void) = lstatic; // expected-error {{call to immediate function 'ns1_1::(lambda)::operator int (*)()' is not a constant expression}} \ + // expected-note {{pointer to a consteval declaration is not a constant expression}} } diff --git a/clang/test/SemaCXX/cxx2a-consteval.cpp b/clang/test/SemaCXX/cxx2a-consteval.cpp index 7a017e513b2a3c..ade887e9941cf2 100644 --- a/clang/test/SemaCXX/cxx2a-consteval.cpp +++ b/clang/test/SemaCXX/cxx2a-consteval.cpp @@ -251,13 +251,14 @@ auto l0 = [](int i) consteval { return f(i); }; -auto l1 = [](int i) constexpr { // expected-error{{cannot take address of immediate call operator}} \ +auto l1 = [](int i) constexpr { // expected-note {{operator int (*)(int)' is an immediate function because its body evaluates the address of an immediate function '__invoke'}} \ // expected-note {{declared here}} int t = f(i); return f(0); }; -int(*test)(int) = l1; +int(*test)(int) = l1; // expected-error {{call to immediate function 'context::(lambda)::operator int (*)(int)' is not a constant expression}} \ ++ // expected-note {{pointer to a consteval declaration is not a constant expression}} } @@ -1046,24 +1047,30 @@ int f() { namespace GH57682 { void test() { - constexpr auto l1 = []() consteval { // expected-error {{cannot take address of consteval call operator of '(lambda at}} \ + constexpr auto l1 = []() consteval { // expected-note {{'operator int (*)()' is an immediate function because its body evaluates the address of a consteval function '__invoke'}} \ // expected-note 2{{declared here}} return 3; }; - constexpr int (*f1)(void) = l1; // expected-error {{constexpr variable 'f1' must be initialized by a constant expression}} \ + constexpr int (*f1)(void) = l1; // expected-error {{call to immediate function 'GH57682::test()::(lambda)::operator int (*)()' is not a constant expression}} \ + // expected-error {{constexpr variable 'f1' must be initialized by a constant expression}} \ + // expected-note {{pointer to a consteval declaration is not a constant expression}} \ // expected-note {{pointer to a consteval declaration is not a constant expression}} - constexpr auto lstatic = []() static consteval { // expected-error {{cannot take address of consteval call operator of '(lambda at}} \ + constexpr auto lstatic = []() static consteval { // expected-note {{'operator int (*)()' is an immediate function because its body evaluates the address of a consteval function 'operator()'}} \ // expected-note 2{{declared here}} \ // expected-warning {{extension}} return 3; }; - constexpr int (*f2)(void) = lstatic; // expected-error {{constexpr variable 'f2' must be initialized by a constant expression}} \ + constexpr int (*f2)(void) = lstatic; // expected-error {{call to immediate function 'GH57682::test()::(lambda)::operator int (*)()' is not a constant expression}} \ + // expected-error {{constexpr variable 'f2' must be initialized by a constant expression}} \ + // expected-note {{pointer to a consteval declaration is not a constant expression}} \ // expected-note {{pointer to a consteval declaration is not a constant expression}} - int (*f3)(void) = []() consteval { return 3; }; // expected-error {{cannot take address of consteval call operator of '(lambda at}} \ - // expected-note {{declared here}} + int (*f3)(void) = []() consteval { return 3; }; // expected-error {{call to immediate function 'GH57682::test()::(lambda)::operator int (*)()' is not a constant expression}} \ + // expected-note {{'operator int (*)()' is an immediate function because its body evaluates the address of a consteval function '__invoke'}} \ + // expected-note {{pointer to a consteval declaration is not a constant expression}} \ + // expected-note {{declared here}} } consteval void consteval_test() { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
