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:
Jason Merrill 2019-11-06 19:21:44 -05:00
parent 67568e1ad4
commit 81a34a6b68
7 changed files with 256 additions and 57 deletions

View file

@ -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.

View file

@ -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

View file

@ -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);

View file

@ -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

View 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" }
}

View 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" }
}

View file

@ -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