On Sat, 13 Dec 2025, Patrick Palka wrote:

> On Sat, 13 Dec 2025, Jason Merrill wrote:
> 
> > 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.
> 
> This is related to PR104111.  Some downsides of going this route:
> 
>   template<class T> requires C<T> || D<T>
>   void f() {
>     if constexpr (C<T>) // potentially IFNDR if evaluation of C<T>
>                         // depends on access context of f (though
>                         // in practice we'll just reuse the cached
>                         // value obtained earlier during satisfaction
>                         // with the right access context)
>       ...
>     else
>       ...
>   }
> 
> --
> 
>   template<class T> requires (!C<T>) // C<T> is not checked in
>                                      // access context of g
>   void g();
> 
> 
> To me it seems that evaluating a concept-id in the access context
> of where the concept-id appears is the better choice once we extend
> the satisfaction cache to consider access context (which it currently
> doesn't).  Doing push_to_top_level would mean the above two testcases
> could never work "as expected" even after we fix the satisfaction cache.

Oops, perhaps you didn't mean to just do push_to_top_level.  If
we do push_to_top_level followed by push_access_scope to restore
the previous access context perhaps this wouldn't have an effect
on the PR104111 testcases.

> 
> > 
> > >   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