From: Nathaniel Shead Date: Sat, 13 Dec 2025 22:32:50 +0000 (+1100) Subject: c++: Don't record lambdas in concept evaluations [PR123075] X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=020ad96f2e18d483e577343b25f0d3a782065c7e;p=thirdparty%2Fgcc.git 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 Reviewed-by: Jason Merrill Reviewed-by: Patrick Palka --- diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc index 6abd0966fcd..92a3a780008 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 435be711d98..20a1177ffab 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 00000000000..188a52c7fd9 --- /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 +concept r = []{ return true; }(); + +template +inline auto foo() { + static_assert(r); + r; + return []{ return false; }; +} + +template +struct S { + static_assert(r); + decltype([]{ return true; }) l; +}; +S s; + +bool use = (foo()() || 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 00000000000..275e6d2269a --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/lambda-13.h @@ -0,0 +1,22 @@ +// PR c++/123075 + +template +concept r = []{ return true; }(); + +template +inline void foo() { + static_assert(r); +} + +template void foo(); + +template +struct S { + static_assert(r); +}; + +template struct S; + +enum E { + X = r, +}; 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";