From 81a34a6b68184436726489b81d44267c40f6fbe7 Mon Sep 17 00:00:00 2001 From: Jason Merrill Date: Wed, 6 Nov 2019 19:21:44 -0500 Subject: [PATCH] Use satisfaction with nested requirements. gcc/cp/ 2019-11-06 Andrew Sutton * constraint.cc (build_parameter_mapping): Use current_template_parms when the declaration is not available. (norm_info::norm_info) Make explicit. (normalize_constraint_expression): Factor into a separate overload that takes arguments, and use that in the original function. (tsubst_nested_requirement): Use satisfy_constraint instead of trying to evaluate this as a constant expression. (finish_nested_requirement): Keep the normalized constraint and the original normalization arguments with the requirement. (diagnose_nested_requirement): Use satisfy_constraint. Tentatively implement more comprehensive diagnostics, but do not enable. * parser.c (cp_parser_requires_expression): Relax requirement that requires-expressions can live only inside templates. * pt.c (any_template_parm_r): Look into type of PARM_DECL. 2019-11-06 Jason Merrill * pt.c (use_pack_expansion_extra_args_p): Still do substitution if all packs are simple pack expansions. (add_extra_args): Check that the extra args aren't dependent. gcc/testsuite/ * lib/prune.exp: Ignore "in requirements" in diagnostics. * g++.dg/cpp2a/requires-18.C: New test. * g++.dg/cpp2a/requires-19.C: New test. From-SVN: r277900 --- gcc/cp/ChangeLog | 24 +++++ gcc/cp/constraint.cc | 100 ++++++++++-------- gcc/cp/parser.c | 11 -- gcc/cp/pt.c | 42 +++++++- .../g++.dg/cpp2a/concepts-requires18.C | 77 ++++++++++++++ .../g++.dg/cpp2a/concepts-requires19.C | 58 ++++++++++ gcc/testsuite/lib/prune.exp | 1 + 7 files changed, 256 insertions(+), 57 deletions(-) create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-requires18.C create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-requires19.C diff --git a/gcc/cp/ChangeLog b/gcc/cp/ChangeLog index c185af408b0..cf3e00a843c 100644 --- a/gcc/cp/ChangeLog +++ b/gcc/cp/ChangeLog @@ -1,3 +1,27 @@ +2019-11-06 Jason Merrill + + * pt.c (use_pack_expansion_extra_args_p): Still do substitution if + all packs are simple pack expansions. + (add_extra_args): Check that the extra args aren't dependent. + +2019-11-06 Andrew Sutton + + Use satisfaction with nested requirements. + * constraint.cc (build_parameter_mapping): Use + current_template_parms when the declaration is not available. + (norm_info::norm_info) Make explicit. + (normalize_constraint_expression): Factor into a separate overload + that takes arguments, and use that in the original function. + (tsubst_nested_requirement): Use satisfy_constraint instead of + trying to evaluate this as a constant expression. + (finish_nested_requirement): Keep the normalized constraint and the + original normalization arguments with the requirement. + (diagnose_nested_requirement): Use satisfy_constraint. Tentatively + implement more comprehensive diagnostics, but do not enable. + * parser.c (cp_parser_requires_expression): Relax requirement that + requires-expressions can live only inside templates. + * pt.c (any_template_parm_r): Look into type of PARM_DECL. + 2019-11-06 Jason Merrill C++20 NB CA378 - Remove constrained non-template functions. diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc index db2a30ced7c..00b59a90868 100644 --- a/gcc/cp/constraint.cc +++ b/gcc/cp/constraint.cc @@ -98,6 +98,8 @@ struct subst_info tree in_decl; }; +static tree satisfy_constraint (tree, tree, subst_info); + /* True if T is known to be some type other than bool. Note that this is false for dependent types and errors. */ @@ -564,6 +566,15 @@ build_parameter_mapping (tree expr, tree args, tree decl) tree parms = DECL_TEMPLATE_PARMS (decl); depth = TREE_INT_CST_LOW (TREE_PURPOSE (parms)); } + else if (current_template_parms) + { + /* TODO: This should probably be the only case, but because the + point of declaration of concepts is currently set after the + initializer, the template parameter lists are not available + when normalizing concept definitions, hence the case above. */ + depth = TMPL_PARMS_DEPTH (current_template_parms); + } + tree parms = find_template_parameters (expr, depth); tree map = map_arguments (parms, args); return map; @@ -592,7 +603,7 @@ parameter_mapping_equivalent_p (tree t1, tree t2) struct norm_info : subst_info { - norm_info(tsubst_flags_t complain) + explicit norm_info (tsubst_flags_t complain) : subst_info (tf_warning_or_error | complain, NULL_TREE), context() {} @@ -872,6 +883,20 @@ normalize_nontemplate_requirements (tree decl, bool diag = false) return get_normalized_constraints_from_decl (decl, diag); } +/* Normalize an EXPR as a constraint using ARGS. */ + +static tree +normalize_constraint_expression (tree expr, tree args, bool diag = false) +{ + if (!expr || expr == error_mark_node) + return expr; + ++processing_template_decl; + norm_info info (diag ? tf_norm : tf_none); + tree norm = get_normalized_constraints (expr, args, info); + --processing_template_decl; + return norm; +} + /* Normalize an EXPR as a constraint. */ static tree @@ -891,11 +916,7 @@ normalize_constraint_expression (tree expr, bool diag = false) else args = NULL_TREE; - ++processing_template_decl; - norm_info info (diag ? tf_norm : tf_none); - tree norm = get_normalized_constraints (expr, args, info); - --processing_template_decl; - return norm; + return normalize_constraint_expression (expr, args, diag); } /* 17.4.1.2p2. Two constraints are identical if they are formed @@ -1930,33 +1951,14 @@ tsubst_compound_requirement (tree t, tree args, subst_info info) static tree tsubst_nested_requirement (tree t, tree args, subst_info info) { - tree t0 = TREE_OPERAND (t, 0); - tree expr = tsubst_expr (t0, args, info.complain, info.in_decl, false); - if (expr == error_mark_node) - return error_mark_node; - - /* Ensure that concrete results are satisfied. */ - if (!uses_template_parms (args)) - { - /* FIXME satisfy_constraint_expression (t0, args, info) */ - - /* [17.4.1.2] ... lvalue-to-value conversion is performed as necessary, - and EXPR shall be a constant expression of type bool. */ - tree result = force_rvalue (expr, tf_error); - if (result == error_mark_node) - return error_mark_node; - - /* FIXME: The expression must have boolean type. */ - if (cv_unqualified (TREE_TYPE (result)) != boolean_type_node) - return error_mark_node; - - /* Compute the value of the expression. */ - result = satisfaction_value (cxx_constant_value (result)); - if (result == error_mark_node || result == boolean_false_node) - return error_mark_node; - } + gcc_assert (!uses_template_parms (args)); - return finish_nested_requirement (EXPR_LOCATION (t), expr); + /* Ensure that we're in an evaluation context prior to satisfaction. */ + tree norm = TREE_VALUE (TREE_TYPE (t)); + tree result = satisfy_constraint (norm, args, info); + if (result != boolean_true_node) + return error_mark_node; + return result; } /* Substitute ARGS into the requirement T. */ @@ -2385,7 +2387,7 @@ satisfaction_value (tree t) tree get_mapped_args (tree map) { - /* If there's no map, then there are no arguments. */ + /* No map, no arguments. */ if (!map) return NULL_TREE; @@ -2419,7 +2421,7 @@ get_mapped_args (tree map) list[index] = TREE_PURPOSE (p); } - /* Build the actual argument list. */ + /* Build the new argument list. */ tree args = make_tree_vec (lists.length ()); for (unsigned i = 0; i != lists.length (); ++i) { @@ -2453,8 +2455,7 @@ satisfy_atom (tree t, tree args, subst_info info) removed before returning. */ diagnosing_failed_constraint failure (t, args, info.noisy ()); - /* Instantiate the parameter mapping, so that we map directly to - the arguments provided to the instantiation. */ + /* Instantiate the parameter mapping. */ tree map = tsubst_parameter_mapping (ATOMIC_CONSTR_MAP (t), args, quiet); if (map == error_mark_node) { @@ -2550,10 +2551,6 @@ satisfy_constraint (tree t, tree args, subst_info info) /* We need to check access during satisfaction. */ deferring_access_check_sentinel acs (dk_no_deferred); - /* Avoid early exit in tsubst and tsubst_copy from null args. */ - if (args == NULL_TREE) - args = make_tree_vec (1); - return satisfy_constraint_r (t, args, info); } @@ -2808,7 +2805,16 @@ finish_compound_requirement (location_t loc, tree expr, tree type, bool noexcept tree finish_nested_requirement (location_t loc, tree expr) { - tree r = build_nt (NESTED_REQ, expr); + /* Save the normalized constraint and complete set of normalization + arguments with the requirement. We keep the complete set of arguments + around for re-normalization during diagnostics. */ + tree args = current_template_parms + ? template_parms_to_args (current_template_parms) : NULL_TREE; + tree norm = normalize_constraint_expression (expr, args, false); + tree info = build_tree_list (args, norm); + + /* Build the constraint, saving its normalization as its type. */ + tree r = build1 (NESTED_REQ, info, expr); SET_EXPR_LOCATION (r, loc); return r; } @@ -3169,15 +3175,21 @@ diagnose_type_requirement (tree req, tree args, tree in_decl) static void diagnose_nested_requirement (tree req, tree args) { - tree expr = TREE_OPERAND (req, 0); - if (constraints_satisfied_p (expr, args)) + /* Quietly check for satisfaction first. We can elaborate details + later if needed. */ + tree norm = TREE_VALUE (TREE_TYPE (req)); + subst_info info (tf_none, NULL_TREE); + tree result = satisfy_constraint (norm, args, info); + if (result == boolean_true_node) return; + + tree expr = TREE_OPERAND (req, 0); location_t loc = cp_expr_location (expr); inform (loc, "nested requirement %qE is not satisfied", expr); /* TODO: Replay the substitution to diagnose the error? */ // subst_info noisy (tf_warning_or_error, NULL_TREE); - // constraints_satisfied_p (expr, args, noisy); + // satisfy_constraint (norm, args, info); } static void diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c index b17e0336e1c..7138aebebce 100644 --- a/gcc/cp/parser.c +++ b/gcc/cp/parser.c @@ -27347,17 +27347,6 @@ cp_parser_requires_expression (cp_parser *parser) gcc_assert (cp_lexer_next_token_is_keyword (parser->lexer, RID_REQUIRES)); location_t loc = cp_lexer_consume_token (parser->lexer)->location; - /* A requires-expression shall appear only within a concept - definition or a requires-clause. - - TODO: Implement this diagnostic correctly. */ - if (!processing_template_decl) - { - error_at (loc, "a requires expression cannot appear outside a template"); - cp_parser_skip_to_end_of_statement (parser); - return error_mark_node; - } - /* This is definitely a requires-expression. */ cp_parser_commit_to_tentative_parse (parser); diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c index 313b8073a3c..c8df1d090bc 100644 --- a/gcc/cp/pt.c +++ b/gcc/cp/pt.c @@ -10402,6 +10402,13 @@ any_template_parm_r (tree t, void *data) if (TREE_TYPE (t)) WALK_SUBTREE (TREE_TYPE (t)); break; + + case PARM_DECL: + /* A parameter or constraint variable may also depend on a template + parameter without explicitly naming it. */ + WALK_SUBTREE (TREE_TYPE (t)); + break; + default: break; } @@ -12071,7 +12078,23 @@ use_pack_expansion_extra_args_p (tree parm_packs, if (parm_packs == NULL_TREE) return false; else if (has_empty_arg) - return true; + { + /* If all the actual packs are pack expansions, we can still + subsitute directly. */ + for (tree p = parm_packs; p; p = TREE_CHAIN (p)) + { + tree a = TREE_VALUE (p); + if (TREE_CODE (a) == ARGUMENT_PACK_SELECT) + a = ARGUMENT_PACK_SELECT_FROM_PACK (a); + a = ARGUMENT_PACK_ARGS (a); + if (TREE_VEC_LENGTH (a) == 1) + a = TREE_VEC_ELT (a, 0); + if (PACK_EXPANSION_P (a)) + continue; + return true; + } + return false; + } bool has_expansion_arg = false; for (int i = 0 ; i < arg_pack_len; ++i) @@ -12551,7 +12574,22 @@ add_extra_args (tree extra, tree args) gcc_assert (!TREE_PURPOSE (extra)); extra = TREE_VALUE (extra); } - return add_to_template_args (extra, args); +#if 1 + /* I think we should always be able to substitute dependent args into the + pattern. If that turns out to be incorrect in some cases, enable the + alternate code (and add complain/in_decl parms to this function). */ + gcc_checking_assert (!uses_template_parms (extra)); +#else + if (!uses_template_parms (extra)) + { + gcc_unreachable (); + extra = tsubst_template_args (extra, args, complain, in_decl); + args = add_outermost_template_args (args, extra); + } + else +#endif + args = add_to_template_args (extra, args); + return args; } /* Substitute ARGS into T, which is an pack expansion diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-requires18.C b/gcc/testsuite/g++.dg/cpp2a/concepts-requires18.C new file mode 100644 index 00000000000..9d9d0d9f508 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-requires18.C @@ -0,0 +1,77 @@ +// { dg-do compile { target c++2a } } + +template +concept integer = __is_same_as(T, int); + +template +concept subst = requires (T x) { requires true; }; + +template +concept c1 = requires { requires integer || subst; }; // { dg-message "in requirements" } + +static_assert(requires { requires true; }); +static_assert(requires { requires false; }); // { dg-error "static assertion failed" } +static_assert(requires { requires integer; }); +static_assert(requires { requires integer; }); // { dg-error "static assertion failed" } +static_assert(requires { requires c1; }); +static_assert(requires { requires c1; }); +static_assert(requires { requires c1; }); // { dg-error "static assertion failed" } +static_assert(requires { requires subst; }); // { dg-error "cannot declare|failed" } + +static_assert(c1); +static_assert(c1); +static_assert(c1); // { dg-error "static assertion failed" } + +template +void f1() { } + +template + requires requires { requires integer || subst; } // { dg-message "in requirements" } +void f2(); + +template +struct data +{ + template + void f1() {} + + template + requires requires { requires integer || subst; } // { dg-message in requirements" } + void f2() {} + + static_assert(requires { requires subst; }); // { dg-error "forming reference|failed" } + + template + constexpr bool test() + { + if constexpr (requires { requires subst; }) // { dg-error "forming reference" } + return true; + else + return false; + } +}; + +void test() +{ + f1(); + f1(); + f1(); // { dg-error "unsatisfied" } + + f2(); + f2(); + f2(); // { dg-error "unsatisfied" } + + data x; + x.f1(); + x.f1(); + x.f1(); // { dg-error "no matching function" } + x.f2(); + x.f2(); + x.f2(); // { dg-error "no matching function" } + + data fail; + + data t; + static_assert(t.test()); + static_assert(t.test()); // { dg-error "static assertion failed" } +} diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-requires19.C b/gcc/testsuite/g++.dg/cpp2a/concepts-requires19.C new file mode 100644 index 00000000000..071a838f754 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-requires19.C @@ -0,0 +1,58 @@ +// { dg-do compile { target c++2a } } + +template +concept check_c = false; + +template +concept c1 = requires (T x) { + requires check_c; +}; + +template +void f1() { } + +template +void f2(T x) requires requires { requires check_c; } { } + + +template +constexpr bool check_f() { return false; } + +template +concept c2 = requires (T x) { + requires check_f(); +}; + +template +void f3() { } + +template +void f4(T x) requires requires { requires check_f(); } { } + + +template +constexpr bool check_v = false; + +template +concept c3 = requires (T x) { + requires check_v; +}; + +template +void f5() { } + +template +void f6(T x) requires requires { requires check_v; } { } + + +void test() +{ + f1(); // { dg-error "unsatisfied" } + f2(0); // { dg-error "unsatisfied" } + + f3(); // { dg-error "unsatisfied" } + f4(0); // { dg-error "unsatisfied" } + + f5(); // { dg-error "unsatisfied" } + f6(0); // { dg-error "unsatisfied" } +} diff --git a/gcc/testsuite/lib/prune.exp b/gcc/testsuite/lib/prune.exp index a9beef48ecb..ae556cad73b 100644 --- a/gcc/testsuite/lib/prune.exp +++ b/gcc/testsuite/lib/prune.exp @@ -36,6 +36,7 @@ proc prune_gcc_output { text } { regsub -all "(^|\n)\[^\n\]*: (recursively )?required \[^\n\]*" $text "" text regsub -all "(^|\n)\[^\n\]*: . skipping \[0-9\]* instantiation contexts \[^\n\]*" $text "" text regsub -all "(^|\n)\[^\n\]*: in constexpr expansion \[^\n\]*" $text "" text + regsub -all "(^|\n)\[^\n\]*: in requirements \[^\n\]*" $text "" text regsub -all "(^|\n) inlined from \[^\n\]*" $text "" text regsub -all "(^|\n)collect2: error: ld returned \[^\n\]*" $text "" text regsub -all "(^|\n)collect: re(compiling|linking)\[^\n\]*" $text "" text -- 2.30.2