https://gcc.gnu.org/g:020ad96f2e18d483e577343b25f0d3a782065c7e

commit r16-6206-g020ad96f2e18d483e577343b25f0d3a782065c7e
Author: Nathaniel Shead <[email protected]>
Date:   Sun Dec 14 09:32:50 2025 +1100

    c++: Don't record lambdas in concept evaluations [PR123075]
    
    When evaluating a concept definition in a template, any lambdas in the
    definition of the concept get instantiated in the context of where the
    evaluation occurred.
    
    This causes two issues:
    
    - Any lambdas declared later in the body of the function get the wrong
      discriminator, which causes ABI divergences with Clang.
    
    - Modules streaming gets confused, because the lambda is keyed to an
      unrelated declaration.  Keying the lambda to the concept also doesn't
      work because we'd really want to key it to a concept instantiation
      (that doesn't exist) so that merging works correctly.
    
    I think really we just want to throw away these lambdas declarations
    after evaluating the concept.  They can (and will) be recreated in
    importers re-evaluating the concept with the given args regardless.
    
    This patch implements this by disabling scope recording for an
    instantiation of a lambda keyed to a concept, and pushing into an
    unrelated context so that the lambda's type is not mistakenly added
    into the scope it was instantiated from.
    
            PR c++/123075
    
    gcc/cp/ChangeLog:
    
            * constraint.cc (evaluate_concept_check): Push to an unrelated
            scope, but keep the same access context.
            * pt.cc (tsubst_lambda_expr): Don't record lambda scopes for
            lambdas attached to a concept.
    
    gcc/testsuite/ChangeLog:
    
            * g++.dg/cpp2a/concepts-lambda25.C: New test.
            * g++.dg/modules/lambda-13.h: New test.
            * g++.dg/modules/lambda-13_a.H: New test.
            * g++.dg/modules/lambda-13_b.C: New test.
    
    Signed-off-by: Nathaniel Shead <[email protected]>
    Reviewed-by: Jason Merrill <[email protected]>
    Reviewed-by: Patrick Palka <[email protected]>

Diff:
---
 gcc/cp/constraint.cc                           | 20 +++++++++++++++++-
 gcc/cp/pt.cc                                   |  7 ++++++-
 gcc/testsuite/g++.dg/cpp2a/concepts-lambda25.C | 28 ++++++++++++++++++++++++++
 gcc/testsuite/g++.dg/modules/lambda-13.h       | 22 ++++++++++++++++++++
 gcc/testsuite/g++.dg/modules/lambda-13_a.H     |  6 ++++++
 gcc/testsuite/g++.dg/modules/lambda-13_b.C     |  6 ++++++
 6 files changed, 87 insertions(+), 2 deletions(-)

diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index 6abd0966fcd1..92a3a780008b 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -2860,9 +2860,27 @@ evaluate_concept_check (tree check)
 
   gcc_assert (concept_check_p (check));
 
+  /* We don't want any declarations instantiated from a concept evaluation
+     to enter the binding table for the current scope, such as lambdas, so
+     leave that scope.  But maintain the access context (PR104111).  */
+  tree scope = current_scope ();
+  if (CLASS_TYPE_P (scope))
+    scope = TYPE_MAIN_DECL (scope);
+  else if (TREE_CODE (scope) != FUNCTION_DECL)
+    scope = NULL_TREE;
+
+  push_to_top_level ();
+  if (scope)
+    push_access_scope (scope);
+
   /* Check for satisfaction without diagnostics.  */
   sat_info quiet (tf_none, NULL_TREE);
-  return constraint_satisfaction_value (check, /*args=*/NULL_TREE, quiet);
+  tree r = constraint_satisfaction_value (check, /*args=*/NULL_TREE, quiet);
+
+  if (scope)
+    pop_access_scope (scope);
+  pop_from_top_level ();
+  return r;
 }
 
 /* Evaluate the requires-expression T, returning either boolean_true_node
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 435be711d98b..20a1177ffab7 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -20583,7 +20583,12 @@ tsubst_lambda_expr (tree t, tree args, tsubst_flags_t 
complain, tree in_decl)
       return error_mark_node;
     }
 
-  if (LAMBDA_EXPR_EXTRA_SCOPE (t))
+  if (LAMBDA_EXPR_EXTRA_SCOPE (t)
+      /* When evaluating a concept we instantiate any lambda bodies
+        in the context of the evaluation.  For ABI reasons don't
+        record a scope for this instantiated lambda so we don't
+        throw off the scope counter.  */
+      && TREE_CODE (LAMBDA_EXPR_EXTRA_SCOPE (t)) != CONCEPT_DECL)
     record_lambda_scope (r);
   if (TYPE_NAMESPACE_SCOPE_P (TREE_TYPE (t)))
     /* If we're pushed into another scope (PR105652), fix it.  */
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-lambda25.C 
b/gcc/testsuite/g++.dg/cpp2a/concepts-lambda25.C
new file mode 100644
index 000000000000..188a52c7fd93
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-lambda25.C
@@ -0,0 +1,28 @@
+// PR c++/123075
+// { dg-do compile { target c++20 } }
+// { dg-additional-options "-fkeep-inline-functions" }
+
+template <typename T>
+concept r = []{ return true; }();
+
+template <typename T, typename U>
+inline auto foo() {
+  static_assert(r<T>);
+  r<U>;
+  return []{ return false; };
+}
+
+template <typename T>
+struct S {
+  static_assert(r<T>);
+  decltype([]{ return true; }) l;
+};
+S<char> s;
+
+bool use = (foo<int, double>()() || s.l());
+
+// There should only be one lambda keyed to 'foo()' and 'S::l'
+// { dg-final { scan-assembler {_ZZ3fooIidEDavENKUlvE_clEv:} } }
+// { dg-final { scan-assembler {_ZNK1SIcEUlvE_clEv:} } }
+// { dg-final { scan-assembler-not {_ZZ3fooIidEDavENKUlvE0_clEv:} } }
+// { dg-final { scan-assembler-not {_ZNK1SIcEUlvE0_clEv:} } }
diff --git a/gcc/testsuite/g++.dg/modules/lambda-13.h 
b/gcc/testsuite/g++.dg/modules/lambda-13.h
new file mode 100644
index 000000000000..275e6d2269a5
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/lambda-13.h
@@ -0,0 +1,22 @@
+// PR c++/123075
+
+template <typename T>
+concept r = []{ return true; }();
+
+template <typename T>
+inline void foo() {
+  static_assert(r<T>);
+}
+
+template void foo<int>();
+
+template <typename T>
+struct S {
+  static_assert(r<T>);
+};
+
+template struct S<double>;
+
+enum E {
+  X = r<E>,
+};
diff --git a/gcc/testsuite/g++.dg/modules/lambda-13_a.H 
b/gcc/testsuite/g++.dg/modules/lambda-13_a.H
new file mode 100644
index 000000000000..2a748fef88fb
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/lambda-13_a.H
@@ -0,0 +1,6 @@
+// PR c++/123075
+// { dg-do compile { target c++20 } }
+// { dg-additional-options "-fmodule-header" }
+// { dg-module-cmi {} }
+
+#include "lambda-13.h"
diff --git a/gcc/testsuite/g++.dg/modules/lambda-13_b.C 
b/gcc/testsuite/g++.dg/modules/lambda-13_b.C
new file mode 100644
index 000000000000..fac66bc5c23b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/lambda-13_b.C
@@ -0,0 +1,6 @@
+// PR c++/123075
+// { dg-do compile { target c++20 } }
+// { dg-additional-options "-fmodules -fno-module-lazy" }
+
+#include "lambda-13.h"
+import "lambda-13_a.H";

Reply via email to