On 12/13/25 7:50 PM, Nathaniel Shead wrote:
Bootstrapped and regtested on x86_64-pc-linux-gnu (so far just dg.exp
and modules.exp), OK for trunk if full regtest succeeds?

-- >8 --

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 ensuring that the
lambda tag is added to an unrelated block that is then thrown away.

Would it make sense to just push_to(/pop_from)_top_level in evaluate_concept_check? This seems like another instance of the recurring problem of not pushing out of a local scope sufficiently before handling a template.

        PR c++/123075

gcc/cp/ChangeLog:

        * constraint.cc (evaluate_concept_check): Start a scope.
        * name-lookup.cc (cp_binding_level_descriptor): Handle concept
        scopes.
        (begin_scope): Likewise.
        * name-lookup.h (enum scope_kind): New sk_concept scope kind.
        * 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]>
---
  gcc/cp/constraint.cc                           | 10 +++++++++-
  gcc/cp/name-lookup.cc                          |  4 +++-
  gcc/cp/name-lookup.h                           |  2 ++
  gcc/cp/pt.cc                                   |  7 ++++++-
  gcc/testsuite/g++.dg/cpp2a/concepts-lambda25.C | 17 +++++++++++++++++
  gcc/testsuite/g++.dg/modules/lambda-13.h       | 11 +++++++++++
  gcc/testsuite/g++.dg/modules/lambda-13_a.H     |  6 ++++++
  gcc/testsuite/g++.dg/modules/lambda-13_b.C     |  6 ++++++
  8 files changed, 60 insertions(+), 3 deletions(-)
  create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-lambda25.C
  create mode 100644 gcc/testsuite/g++.dg/modules/lambda-13.h
  create mode 100644 gcc/testsuite/g++.dg/modules/lambda-13_a.H
  create mode 100644 gcc/testsuite/g++.dg/modules/lambda-13_b.C

diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index 6abd0966fcd..93c68bc6e7a 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -2860,9 +2860,17 @@ 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
+     enter a new scope that inhibits these declarations.  */
+  begin_scope (sk_concept, NULL_TREE);
+
    /* 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);
+
+  pop_bindings_and_leave_scope ();
+  return r;
  }
/* Evaluate the requires-expression T, returning either boolean_true_node
diff --git a/gcc/cp/name-lookup.cc b/gcc/cp/name-lookup.cc
index 4c07fd40f64..e617105600d 100644
--- a/gcc/cp/name-lookup.cc
+++ b/gcc/cp/name-lookup.cc
@@ -4700,7 +4700,8 @@ cp_binding_level_descriptor (cp_binding_level *scope)
      "template-explicit-spec-scope",
      "transaction-scope",
      "openmp-scope",
-    "lambda-scope"
+    "lambda-scope",
+    "concept-scope",
    };
    static_assert (ARRAY_SIZE (scope_kind_names) == sk_count,
                 "must keep names aligned with scope_kind enum");
@@ -4793,6 +4794,7 @@ begin_scope (scope_kind kind, tree entity)
      case sk_omp:
      case sk_stmt_expr:
      case sk_lambda:
+    case sk_concept:
        scope->keep = keep_next_level_flag;
        break;
diff --git a/gcc/cp/name-lookup.h b/gcc/cp/name-lookup.h
index da277c49b1a..024ef738161 100644
--- a/gcc/cp/name-lookup.h
+++ b/gcc/cp/name-lookup.h
@@ -229,6 +229,8 @@ enum scope_kind {
    sk_transaction,    /* A synchronized or atomic statement.  */
    sk_omp,          /* An OpenMP structured block.  */
    sk_lambda,       /* A lambda scope.  */
+  sk_concept,       /* The scope of a declaration in the definition of a
+                       concept during evaluation.  */
    sk_count         /* Number of scope_kind enumerations.  */
  };
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 341e5ab8808..b24e646cc29 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -20589,7 +20589,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 00000000000..e064df67f42
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-lambda25.C
@@ -0,0 +1,17 @@
+// PR c++/123075
+// { dg-do compile { target c++20 } }
+
+template <typename T>
+concept r = []{ return true; }();
+
+template <typename T, typename U>
+inline auto foo() {
+  static_assert(r<T>);
+  r<U>;
+  return []{ return false; };
+}
+
+bool x = foo<int, double>()();
+
+// There should only be one lambda keyed to 'foo()'
+// { dg-final { scan-assembler {_ZZ3fooIidEDavENKUlvE_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 00000000000..dfe11954f8c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/lambda-13.h
@@ -0,0 +1,11 @@
+// PR c++/123075
+
+template <typename T>
+concept r = []{ return true; }();
+
+template <typename T>
+inline void foo() {
+  static_assert(r<T>);
+}
+
+template void foo<int>();
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 00000000000..2a748fef88f
--- /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 00000000000..fac66bc5c23
--- /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