https://github.com/Ferdi265 created 
https://github.com/llvm/llvm-project/pull/235356

This PR fixes #235353. This fixes a divergence from GCC behaviour.

As described in #235353, the standard, in 
[\[expr.prim.lambda.closure\]/11](https://eel.is/c++draft/expr.prim.lambda#closure-11),
 forgets to define the lambda conversion operator to be immediate if the call 
operator is immediate. Since implementing it as non-immediate causes the 
generated conversion operator to be ill-formed, it only makes sense to 
implement it as an immediate or immediate-escalating function. This is also how 
GCC handles this case.

Handling this correctly also seems to make the special case from my previous PR 
#232458 redundant, where some lambda methods would need to inherit 
`InImmediateFunctionContext` from their parent scopes anyway. Remove this 
workaround as it appears no longer necessary.

# TODO

- [X] Initial fix for the issue (mark lambda conversion operators as 
immediate-escalating)
- [X] Fix immediate escalation not working properly with 
`SynthesizedFunctionScope`
- [X] Remove workaround from PR #232458
- [ ] Fix regressions
- [ ] Update Testcases
  - [X] Fix testcases that had changed diagnostics (partial, needs nicer commit 
message)
  - [ ] Add new testcases for #235353
  - [ ] Add testcases for regressions

>From 0cc217ac73ffd7b691e1d5d7bb64b0e2cf298f97 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 | 10 +++++-----
 clang/lib/AST/Decl.cpp              |  4 ++++
 2 files changed, 9 insertions(+), 5 deletions(-)

diff --git a/clang/include/clang/AST/ASTLambda.h 
b/clang/include/clang/AST/ASTLambda.h
index a1854b6a1a9491..16c85f36dce5ea 100644
--- a/clang/include/clang/AST/ASTLambda.h
+++ b/clang/include/clang/AST/ASTLambda.h
@@ -66,16 +66,16 @@ 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 =
+  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 4c7de746929cc1ca0f286eafd9ea4da79f2a31f8 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 43bbdbfc223bce42bbe6cbc2b609a31eaee9cc46 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 3d552ba01b7ef8c8255ea9945a9d60f0998eb698 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

Reply via email to