diff options
author | Patrick Palka <ppalka@redhat.com> | 2021-04-08 13:07:43 -0400 |
---|---|---|
committer | Patrick Palka <ppalka@redhat.com> | 2021-04-08 13:07:43 -0400 |
commit | 123b3e03c911a43054c1f88f5d3110e1d084dd4e (patch) | |
tree | 060c0876ab31fd31f0a892da209e32acdabd178a | |
parent | 05708d6eef87a3dd0c68b1aed7f8d9c3824062b8 (diff) | |
download | gcc-123b3e03c911a43054c1f88f5d3110e1d084dd4e.zip gcc-123b3e03c911a43054c1f88f5d3110e1d084dd4e.tar.gz gcc-123b3e03c911a43054c1f88f5d3110e1d084dd4e.tar.bz2 |
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.
-rw-r--r-- | gcc/cp/constraint.cc | 43 | ||||
-rw-r--r-- | gcc/cp/cp-tree.h | 20 | ||||
-rw-r--r-- | gcc/cp/lambda.c | 2 | ||||
-rw-r--r-- | gcc/cp/pt.c | 42 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/cpp2a/concepts-lambda16.C | 61 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/cpp2a/concepts-lambda17.C | 14 |
6 files changed, 150 insertions, 32 deletions
diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc index 5cf43bd..0a9d1bf 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 a5d9d7ac..bf9d5ad 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<tree, va_gc> *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 b0fd6ec..c0a5ffb 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 842a58c..daf1b5a 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 0000000..01fda6e --- /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 <class T> +struct A { + static inline auto a = [] <class U> (U) { + return [] <class V> (V) requires requires (T t, U u, V v) { t + u + v; } { }; + }; + + template <class W> + static inline auto b = [] <class U> (U) { + return [] <class V> (V) requires requires (T t, U u, V v, W w) { t + u + v + w; } { }; + }; + + static auto f() { + return [] <class U> (U) { + return [] <class V> (V) requires requires (T t, U u, V v) { t + u + v; } { }; + }; + } + + template <class W> + static auto g() { + return [] <class U> (U) { + return [] <class V> (V) requires requires (T t, U u, V v, W w) { t + u + v + w; } { }; + }; + } +}; + +template <class T> +auto a = [] <class U> (U) { + return [] <class V> (V) requires requires (T t, U u, V v) { t + u + v; } { }; +}; + +template <class T> +auto b = [] <class U> (U) { + return [] <class V> (V) { + return [] { + return [] () requires requires (T t, U u, V v) { t + u + v; } { }; + }; + }; +}; + +int main() { + A<int>::a(0)(0); + A<int>::a(0)(nullptr); // { dg-error "no match" } + + A<int>::b<int>(0)(0); + A<int>::b<int>(0)(nullptr); // { dg-error "no match" } + + A<int>::f()(0)(0); + A<int>::f()(0)(nullptr); // { dg-error "no match" } + + A<int>::g<int>()(0)(0); + A<int>::g<int>()(0)(nullptr); // { dg-error "no match" } + + a<int>(0)(0); + a<int>(0)(nullptr); // { dg-error "no match" } + + b<int>(0)(0)(); + b<int>(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 0000000..32ae1e1 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-lambda17.C @@ -0,0 +1,14 @@ +// { dg-do compile { target c++20 } } + +template<class T> +struct A { static const bool value = T::value; }; + +template<class T> +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<T>::value) || true { }(); + [] <class U> (U) requires (U::value && A<T>::value) || true { }(0); +} + +template void f<int>(); |