Use satisfaction with nested requirements.
gcc/cp/ 2019-11-06 Andrew Sutton <asutton@lock3software.com> * 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 <jason@redhat.com> * 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
This commit is contained in:
parent
67568e1ad4
commit
81a34a6b68
7 changed files with 256 additions and 57 deletions
|
@ -1,3 +1,27 @@
|
|||
2019-11-06 Jason Merrill <jason@redhat.com>
|
||||
|
||||
* 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 <asutton@lock3software.com>
|
||||
|
||||
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 <jason@redhat.com>
|
||||
|
||||
C++20 NB CA378 - Remove constrained non-template functions.
|
||||
|
|
|
@ -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)
|
||||
gcc_assert (!uses_template_parms (args));
|
||||
|
||||
/* 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;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
return finish_nested_requirement (EXPR_LOCATION (t), expr);
|
||||
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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
42
gcc/cp/pt.c
42
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
|
||||
|
|
77
gcc/testsuite/g++.dg/cpp2a/concepts-requires18.C
Normal file
77
gcc/testsuite/g++.dg/cpp2a/concepts-requires18.C
Normal file
|
@ -0,0 +1,77 @@
|
|||
// { dg-do compile { target c++2a } }
|
||||
|
||||
template<typename T>
|
||||
concept integer = __is_same_as(T, int);
|
||||
|
||||
template<typename T>
|
||||
concept subst = requires (T x) { requires true; };
|
||||
|
||||
template<typename T>
|
||||
concept c1 = requires { requires integer<T> || subst<T&>; }; // { dg-message "in requirements" }
|
||||
|
||||
static_assert(requires { requires true; });
|
||||
static_assert(requires { requires false; }); // { dg-error "static assertion failed" }
|
||||
static_assert(requires { requires integer<int>; });
|
||||
static_assert(requires { requires integer<void>; }); // { dg-error "static assertion failed" }
|
||||
static_assert(requires { requires c1<int>; });
|
||||
static_assert(requires { requires c1<bool>; });
|
||||
static_assert(requires { requires c1<void>; }); // { dg-error "static assertion failed" }
|
||||
static_assert(requires { requires subst<void&>; }); // { dg-error "cannot declare|failed" }
|
||||
|
||||
static_assert(c1<int>);
|
||||
static_assert(c1<bool>);
|
||||
static_assert(c1<void>); // { dg-error "static assertion failed" }
|
||||
|
||||
template<c1 T>
|
||||
void f1() { }
|
||||
|
||||
template<typename T>
|
||||
requires requires { requires integer<T> || subst<T&>; } // { dg-message "in requirements" }
|
||||
void f2();
|
||||
|
||||
template<typename T>
|
||||
struct data
|
||||
{
|
||||
template<c1 U>
|
||||
void f1() {}
|
||||
|
||||
template<typename U>
|
||||
requires requires { requires integer<U> || subst<U&>; } // { dg-message in requirements" }
|
||||
void f2() {}
|
||||
|
||||
static_assert(requires { requires subst<T&>; }); // { dg-error "forming reference|failed" }
|
||||
|
||||
template<typename U>
|
||||
constexpr bool test()
|
||||
{
|
||||
if constexpr (requires { requires subst<U&>; }) // { dg-error "forming reference" }
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
void test()
|
||||
{
|
||||
f1<int>();
|
||||
f1<bool>();
|
||||
f1<void>(); // { dg-error "unsatisfied" }
|
||||
|
||||
f2<int>();
|
||||
f2<bool>();
|
||||
f2<void>(); // { dg-error "unsatisfied" }
|
||||
|
||||
data<char> x;
|
||||
x.f1<int>();
|
||||
x.f1<bool>();
|
||||
x.f1<void>(); // { dg-error "no matching function" }
|
||||
x.f2<int>();
|
||||
x.f2<bool>();
|
||||
x.f2<void>(); // { dg-error "no matching function" }
|
||||
|
||||
data<void> fail;
|
||||
|
||||
data<int> t;
|
||||
static_assert(t.test<int>());
|
||||
static_assert(t.test<void>()); // { dg-error "static assertion failed" }
|
||||
}
|
58
gcc/testsuite/g++.dg/cpp2a/concepts-requires19.C
Normal file
58
gcc/testsuite/g++.dg/cpp2a/concepts-requires19.C
Normal file
|
@ -0,0 +1,58 @@
|
|||
// { dg-do compile { target c++2a } }
|
||||
|
||||
template<typename T>
|
||||
concept check_c = false;
|
||||
|
||||
template<typename T>
|
||||
concept c1 = requires (T x) {
|
||||
requires check_c<decltype(x)>;
|
||||
};
|
||||
|
||||
template<c1 T>
|
||||
void f1() { }
|
||||
|
||||
template<typename T>
|
||||
void f2(T x) requires requires { requires check_c<decltype(x)>; } { }
|
||||
|
||||
|
||||
template<typename T>
|
||||
constexpr bool check_f() { return false; }
|
||||
|
||||
template<typename T>
|
||||
concept c2 = requires (T x) {
|
||||
requires check_f<decltype(x)>();
|
||||
};
|
||||
|
||||
template<c2 T>
|
||||
void f3() { }
|
||||
|
||||
template<typename T>
|
||||
void f4(T x) requires requires { requires check_f<decltype(x)>(); } { }
|
||||
|
||||
|
||||
template<typename T>
|
||||
constexpr bool check_v = false;
|
||||
|
||||
template<typename T>
|
||||
concept c3 = requires (T x) {
|
||||
requires check_v<decltype(x)>;
|
||||
};
|
||||
|
||||
template<c3 T>
|
||||
void f5() { }
|
||||
|
||||
template<typename T>
|
||||
void f6(T x) requires requires { requires check_v<decltype(x)>; } { }
|
||||
|
||||
|
||||
void test()
|
||||
{
|
||||
f1<int>(); // { dg-error "unsatisfied" }
|
||||
f2(0); // { dg-error "unsatisfied" }
|
||||
|
||||
f3<int>(); // { dg-error "unsatisfied" }
|
||||
f4(0); // { dg-error "unsatisfied" }
|
||||
|
||||
f5<int>(); // { dg-error "unsatisfied" }
|
||||
f6(0); // { dg-error "unsatisfied" }
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue