From: Patrick Palka Date: Thu, 8 Apr 2021 17:07:43 +0000 (-0400) Subject: c++: Don't substitute into constraints on lambdas [PR99874] X-Git-Tag: basepoints/gcc-12~192 X-Git-Url: https://gcc.gnu.org/git/?a=commitdiff_plain;h=123b3e03c911a43054c1f88f5d3110e1d084dd4e;p=gcc.git c++: Don't substitute into constraints on lambdas [PR99874] We currently substitute through a lambda's constraints whenever we regenerate it via tsubst_lambda_expr. This is the wrong approach because it can lead to hard errors due to constraints being evaluated out of order (as in the testcase concepts-lambda17.C below), and because it doesn't mesh well with the recently added REQUIRES_EXPR_EXTRA_ARGS mechanism for delaying substitution into requires-expressions, which is the cause of this PR. But in order to avoid substituting through a lambda's constraints during regeneration, we need to be able to get at all in-scope template parameters and corresponding template arguments during constraint checking of a lambda's op(). And this information is not easily available when we need it, it seems. To that end, the approach that this patch takes is to add two new fields to LAMBDA_EXPR (and remove one): LAMBDA_EXPR_REGENERATED_FROM (replacing LAMBDA_EXPR_INSTANTIATED), and LAMBDA_EXPR_REGENERATING_TARGS. The former allows us to obtain the complete set of template parameters that are in-scope for a lambda's op(), and the latter gives us all outer template arguments that were used to regenerate the lambda (analogous to the TI_TEMPLATE and TI_ARGS of a TEMPLATE_INFO, respectively). LAMBDA_EXPR_REGENERATING_TARGS is not strictly necessary -- in an earlier prototype, I walked LAMBDA_EXPR_EXTRA_SCOPE to build up this set of outer template arguments on demand, but it seems cleaner to do it this way. (We'd need to walk LAMBDA_EXPR_EXTRA_SCOPE and not DECL/TYPE_CONTEXT because the latter skips over variable template scopes.) This patch also renames the predicate instantiated_lambda_fn_p to regenerated_lambda_fn_p, for sake of consistency with the rest of the patch which uses "regenerated" instead of "instantiated". gcc/cp/ChangeLog: PR c++/99874 * constraint.cc (get_normalized_constraints_from_decl): Handle regenerated lambdas. (satisfy_declaration_constraints): Likewise. Check for dependent args later. * cp-tree.h (LAMBDA_EXPR_INSTANTIATED): Replace with ... (LAMBDA_EXPR_REGENERATED_FROM): ... this. (LAMBDA_EXPR_REGENERATING_TARGS): New. (tree_lambda_expr::regenerated_from): New data member. (tree_lambda_expr::regenerating_targs): New data member. (add_to_template_args): Declare. (regenerated_lambda_fn_p): Likewise. (most_general_lambda): Likewise. * lambda.c (build_lambda_expr): Set LAMBDA_EXPR_REGENERATED_FROM and LAMBDA_EXPR_REGENERATING_TARGS. * pt.c (add_to_template_args): No longer static. (tsubst_function_decl): Unconditionally propagate constraints on the substituted function decl. (instantiated_lambda_fn_p): Rename to ... (regenerated_lambda_fn_p): ... this. Check LAMBDA_EXPR_REGENERATED_FROM instead of LAMBDA_EXPR_INSTANTIATED. (most_general_lambda): Define. (enclosing_instantiation_of): Adjust after renaming instantiated_lambda_fn_p. (tsubst_lambda_expr): Don't set LAMBDA_EXPR_INSTANTIATED. Set LAMBDA_EXPR_REGENERATED_FROM and LAMBDA_EXPR_REGENERATING_TARGS. Don't substitute or set constraints on the regenerated lambda. gcc/testsuite/ChangeLog: PR c++/99874 * g++.dg/cpp2a/concepts-lambda16.C: New test. * g++.dg/cpp2a/concepts-lambda17.C: New test. --- diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc index 5cf43bd6c18a..0a9d1bfea0fb 100644 --- a/gcc/cp/constraint.cc +++ b/gcc/cp/constraint.cc @@ -886,6 +886,16 @@ get_normalized_constraints_from_decl (tree d, bool diag = false) it has the correct template information attached. */ d = strip_inheriting_ctors (d); + if (regenerated_lambda_fn_p (d)) + { + /* If this lambda was regenerated, DECL_TEMPLATE_PARMS doesn't contain + all in-scope template parameters, but the lambda from which it was + ultimately regenerated does, so use that instead. */ + tree lambda = CLASSTYPE_LAMBDA_EXPR (DECL_CONTEXT (d)); + lambda = most_general_lambda (lambda); + d = lambda_function (lambda); + } + if (TREE_CODE (d) == TEMPLATE_DECL) { tmpl = d; @@ -3174,13 +3184,27 @@ satisfy_declaration_constraints (tree t, sat_info info) args = TI_ARGS (ti); if (inh_ctor_targs) args = add_outermost_template_args (args, inh_ctor_targs); + } - /* If any arguments depend on template parameters, we can't - check constraints. Pretend they're satisfied for now. */ - if (uses_template_parms (args)) - return boolean_true_node; + if (regenerated_lambda_fn_p (t)) + { + /* The TI_ARGS of a regenerated lambda contains only the innermost + set of template arguments. Augment this with the outer template + arguments that were used to regenerate the lambda. */ + gcc_assert (!args || TMPL_ARGS_DEPTH (args) == 1); + tree lambda = CLASSTYPE_LAMBDA_EXPR (DECL_CONTEXT (t)); + tree outer_args = LAMBDA_EXPR_REGENERATING_TARGS (lambda); + if (args) + args = add_to_template_args (outer_args, args); + else + args = outer_args; } + /* If any arguments depend on template parameters, we can't + check constraints. Pretend they're satisfied for now. */ + if (uses_template_parms (args)) + return boolean_true_node; + /* Get the normalized constraints. */ tree norm = get_normalized_constraints_from_decl (t, info.noisy ()); @@ -3227,7 +3251,16 @@ satisfy_declaration_constraints (tree t, tree args, sat_info info) gcc_assert (TREE_CODE (t) == TEMPLATE_DECL); - args = add_outermost_template_args (t, args); + if (regenerated_lambda_fn_p (t)) + { + /* As in the two-parameter version of this function. */ + gcc_assert (TMPL_ARGS_DEPTH (args) == 1); + tree lambda = CLASSTYPE_LAMBDA_EXPR (DECL_CONTEXT (t)); + tree outer_args = LAMBDA_EXPR_REGENERATING_TARGS (lambda); + args = add_to_template_args (outer_args, args); + } + else + args = add_outermost_template_args (t, args); /* If any arguments depend on template parameters, we can't check constraints. Pretend they're satisfied for now. */ diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index a5d9d7ac6259..bf9d5add0cf0 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -490,7 +490,6 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX]; DECLTYPE_FOR_REF_CAPTURE (in DECLTYPE_TYPE) CONSTRUCTOR_C99_COMPOUND_LITERAL (in CONSTRUCTOR) OVL_NESTED_P (in OVERLOAD) - LAMBDA_EXPR_INSTANTIATED (in LAMBDA_EXPR) DECL_MODULE_EXPORT_P (in _DECL) 4: IDENTIFIER_MARKED (IDENTIFIER_NODEs) TREE_HAS_CONSTRUCTOR (in INDIRECT_REF, SAVE_EXPR, CONSTRUCTOR, @@ -1434,10 +1433,6 @@ enum cp_lambda_default_capture_mode_type { #define LAMBDA_EXPR_CAPTURE_OPTIMIZED(NODE) \ TREE_LANG_FLAG_2 (LAMBDA_EXPR_CHECK (NODE)) -/* True iff this LAMBDA_EXPR was generated in tsubst_lambda_expr. */ -#define LAMBDA_EXPR_INSTANTIATED(NODE) \ - TREE_LANG_FLAG_3 (LAMBDA_EXPR_CHECK (NODE)) - /* True if this TREE_LIST in LAMBDA_EXPR_CAPTURE_LIST is for an explicit capture. */ #define LAMBDA_CAPTURE_EXPLICIT_P(NODE) \ @@ -1461,6 +1456,16 @@ enum cp_lambda_default_capture_mode_type { #define LAMBDA_EXPR_PENDING_PROXIES(NODE) \ (((struct tree_lambda_expr *)LAMBDA_EXPR_CHECK (NODE))->pending_proxies) +/* The immediate LAMBDA_EXPR from which NODE was regenerated, or NULL_TREE + (if NODE was not regenerated via tsubst_lambda_expr). */ +#define LAMBDA_EXPR_REGENERATED_FROM(NODE) \ + (((struct tree_lambda_expr *)LAMBDA_EXPR_CHECK (NODE))->regenerated_from) + +/* The full set of template arguments used to regenerate NODE, or NULL_TREE + (if NODE was not regenerated via tsubst_lambda_expr). */ +#define LAMBDA_EXPR_REGENERATING_TARGS(NODE) \ + (((struct tree_lambda_expr *)LAMBDA_EXPR_CHECK (NODE))->regenerating_targs) + /* The closure type of the lambda, which is also the type of the LAMBDA_EXPR. */ #define LAMBDA_EXPR_CLOSURE(NODE) \ @@ -1472,6 +1477,8 @@ struct GTY (()) tree_lambda_expr tree capture_list; tree this_capture; tree extra_scope; + tree regenerated_from; + tree regenerating_targs; vec *pending_proxies; location_t locus; enum cp_lambda_default_capture_mode_type default_capture_mode : 8; @@ -7247,6 +7254,7 @@ extern unsigned get_mergeable_specialization_flags (tree tmpl, tree spec); extern void add_mergeable_specialization (bool is_decl, bool is_alias, spec_entry *, tree outer, unsigned); +extern tree add_to_template_args (tree, tree); extern tree add_outermost_template_args (tree, tree); extern tree add_extra_args (tree, tree); extern tree build_extra_args (tree, tree, tsubst_flags_t); @@ -7557,6 +7565,8 @@ extern void record_null_lambda_scope (tree); extern void finish_lambda_scope (void); extern tree start_lambda_function (tree fn, tree lambda_expr); extern void finish_lambda_function (tree body); +extern bool regenerated_lambda_fn_p (tree); +extern tree most_general_lambda (tree); /* in tree.c */ extern int cp_tree_operand_length (const_tree); diff --git a/gcc/cp/lambda.c b/gcc/cp/lambda.c index b0fd6ecc57ea..c0a5ffb427e7 100644 --- a/gcc/cp/lambda.c +++ b/gcc/cp/lambda.c @@ -41,6 +41,8 @@ build_lambda_expr (void) LAMBDA_EXPR_DEFAULT_CAPTURE_MODE (lambda) = CPLD_NONE; LAMBDA_EXPR_CAPTURE_LIST (lambda) = NULL_TREE; LAMBDA_EXPR_THIS_CAPTURE (lambda) = NULL_TREE; + LAMBDA_EXPR_REGENERATED_FROM (lambda) = NULL_TREE; + LAMBDA_EXPR_REGENERATING_TARGS (lambda) = NULL_TREE; LAMBDA_EXPR_PENDING_PROXIES (lambda) = NULL; LAMBDA_EXPR_MUTABLE_P (lambda) = false; return lambda; diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c index 842a58cf2f9f..daf1b5aeb32a 100644 --- a/gcc/cp/pt.c +++ b/gcc/cp/pt.c @@ -151,7 +151,6 @@ static tree coerce_template_parms (tree, tree, tree, tsubst_flags_t, static tree coerce_innermost_template_parms (tree, tree, tree, tsubst_flags_t, bool, bool); static void tsubst_enum (tree, tree, tree); -static tree add_to_template_args (tree, tree); static bool check_instantiated_args (tree, tree, tsubst_flags_t); static int check_non_deducible_conversion (tree, tree, int, int, struct conversion **, bool); @@ -553,7 +552,7 @@ maybe_end_member_template_processing (void) /* Return a new template argument vector which contains all of ARGS, but has as its innermost set of arguments the EXTRA_ARGS. */ -static tree +tree add_to_template_args (tree args, tree extra_args) { tree new_args; @@ -14065,10 +14064,7 @@ tsubst_function_decl (tree t, tree args, tsubst_flags_t complain, don't substitute through the constraints; that's only done when they are checked. */ if (tree ci = get_constraints (t)) - /* Unless we're regenerating a lambda, in which case we'll set the - lambda's constraints in tsubst_lambda_expr. */ - if (!lambda_fntype) - set_constraints (r, ci); + set_constraints (r, ci); if (DECL_FRIEND_CONTEXT (t)) SET_DECL_FRIEND_CONTEXT (r, @@ -14354,13 +14350,24 @@ lambda_fn_in_template_p (tree fn) which the above is true. */ bool -instantiated_lambda_fn_p (tree fn) +regenerated_lambda_fn_p (tree fn) { if (!fn || !LAMBDA_FUNCTION_P (fn)) return false; tree closure = DECL_CONTEXT (fn); tree lam = CLASSTYPE_LAMBDA_EXPR (closure); - return LAMBDA_EXPR_INSTANTIATED (lam); + return LAMBDA_EXPR_REGENERATED_FROM (lam) != NULL_TREE; +} + +/* Return the LAMBDA_EXPR from which T was ultimately regenerated. + If T is not a regenerated LAMBDA_EXPR, return T. */ + +tree +most_general_lambda (tree t) +{ + while (LAMBDA_EXPR_REGENERATED_FROM (t)) + t = LAMBDA_EXPR_REGENERATED_FROM (t); + return t; } /* We're instantiating a variable from template function TCTX. Return the @@ -14376,7 +14383,7 @@ enclosing_instantiation_of (tree otctx) int lambda_count = 0; for (; tctx && (lambda_fn_in_template_p (tctx) - || instantiated_lambda_fn_p (tctx)); + || regenerated_lambda_fn_p (tctx)); tctx = decl_function_context (tctx)) ++lambda_count; @@ -14396,7 +14403,7 @@ enclosing_instantiation_of (tree otctx) { tree ofn = fn; int flambda_count = 0; - for (; fn && instantiated_lambda_fn_p (fn); + for (; fn && regenerated_lambda_fn_p (fn); fn = decl_function_context (fn)) ++flambda_count; if ((fn && DECL_TEMPLATE_INFO (fn)) @@ -19271,7 +19278,9 @@ tsubst_lambda_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl) LAMBDA_EXPR_DEFAULT_CAPTURE_MODE (r) = LAMBDA_EXPR_DEFAULT_CAPTURE_MODE (t); LAMBDA_EXPR_MUTABLE_P (r) = LAMBDA_EXPR_MUTABLE_P (t); - LAMBDA_EXPR_INSTANTIATED (r) = true; + LAMBDA_EXPR_REGENERATED_FROM (r) = t; + LAMBDA_EXPR_REGENERATING_TARGS (r) + = add_to_template_args (LAMBDA_EXPR_REGENERATING_TARGS (t), args); gcc_assert (LAMBDA_EXPR_THIS_CAPTURE (t) == NULL_TREE && LAMBDA_EXPR_PENDING_PROXIES (t) == NULL); @@ -19413,17 +19422,6 @@ tsubst_lambda_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl) finish_member_declaration (fn); } - if (tree ci = get_constraints (oldfn)) - { - /* Substitute into the lambda's constraints. */ - if (oldtmpl) - ++processing_template_decl; - ci = tsubst_constraint_info (ci, args, complain, in_decl); - if (oldtmpl) - --processing_template_decl; - set_constraints (fn, ci); - } - /* Let finish_function set this. */ DECL_DECLARED_CONSTEXPR_P (fn) = false; diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-lambda16.C b/gcc/testsuite/g++.dg/cpp2a/concepts-lambda16.C new file mode 100644 index 000000000000..01fda6efb386 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-lambda16.C @@ -0,0 +1,61 @@ +// PR c++/99874 +// { dg-do compile { target c++20 } } + +template +struct A { + static inline auto a = [] (U) { + return [] (V) requires requires (T t, U u, V v) { t + u + v; } { }; + }; + + template + static inline auto b = [] (U) { + return [] (V) requires requires (T t, U u, V v, W w) { t + u + v + w; } { }; + }; + + static auto f() { + return [] (U) { + return [] (V) requires requires (T t, U u, V v) { t + u + v; } { }; + }; + } + + template + static auto g() { + return [] (U) { + return [] (V) requires requires (T t, U u, V v, W w) { t + u + v + w; } { }; + }; + } +}; + +template +auto a = [] (U) { + return [] (V) requires requires (T t, U u, V v) { t + u + v; } { }; +}; + +template +auto b = [] (U) { + return [] (V) { + return [] { + return [] () requires requires (T t, U u, V v) { t + u + v; } { }; + }; + }; +}; + +int main() { + A::a(0)(0); + A::a(0)(nullptr); // { dg-error "no match" } + + A::b(0)(0); + A::b(0)(nullptr); // { dg-error "no match" } + + A::f()(0)(0); + A::f()(0)(nullptr); // { dg-error "no match" } + + A::g()(0)(0); + A::g()(0)(nullptr); // { dg-error "no match" } + + a(0)(0); + a(0)(nullptr); // { dg-error "no match" } + + b(0)(0)(); + b(0)(nullptr)()(); // { dg-error "no match" } +} diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-lambda17.C b/gcc/testsuite/g++.dg/cpp2a/concepts-lambda17.C new file mode 100644 index 000000000000..32ae1e121748 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-lambda17.C @@ -0,0 +1,14 @@ +// { dg-do compile { target c++20 } } + +template +struct A { static const bool value = T::value; }; + +template +void f() { + // Verify we don't substitute into a lambda's constraints when + // regenerating it, which would lead to a hard error here. + [] () requires (T::value && A::value) || true { }(); + [] (U) requires (U::value && A::value) || true { }(0); +} + +template void f();