c++: requires-expressions and partial instantiation [PR96410]

This patch makes tsubst_requires_expr avoid substituting into a
requires-expression when partially instantiating a generic lambda.
This is necessary in general to ensure that we always check requirements
in lexical order (as in the first testcase below).  A mechanism similar
to PACK_EXPANSION_EXTRA_ARGS is added to remember template arguments and
defer substitution of requires-expressions.

Incidentally, this change also fixes the two mentioned PRs -- the
problem there is that tsubst_requires_expr was performing semantic
checks on template trees, and some of the checks are not prepared to
handle such trees.  With this patch, tsubst_requires_expr no longer
does any semantic checking at all when processing_template_decl.

gcc/cp/ChangeLog:

	PR c++/96409
	PR c++/96410
	* constraint.cc (tsubst_requires_expr): Use REQUIRES_EXPR_PARMS
	and REQUIRES_EXPR_REQS.  Use REQUIRES_EXPR_EXTRA_ARGS,
	add_extra_args and build_extra_args to defer substitution until
	we have all the template arguments.
	(finish_requires_expr): Adjust the call to build_min so that
	REQUIRES_EXPR_EXTRA_ARGS gets set to NULL_TREE.
	* cp-tree.def (REQUIRES_EXPR): Give it a third operand.
	* cp-tree.h (REQUIRES_EXPR_PARMS, REQUIRES_EXPR_REQS,
	REQUIRES_EXPR_EXTRA_ARGS): Define.
	(add_extra_args, build_extra_args): Declare.

gcc/testsuite/ChangeLog:

	PR c++/96409
	PR c++/96410
	* g++.dg/cpp2a/concepts-lambda13.C: New test.
	* g++.dg/cpp2a/concepts-lambda14.C: New test.
This commit is contained in:
Patrick Palka 2020-09-17 09:16:02 -04:00
parent 9fcedcc391
commit b28b621ac6
5 changed files with 80 additions and 9 deletions

View file

@ -2175,7 +2175,19 @@ tsubst_requires_expr (tree t, tree args,
/* A requires-expression is an unevaluated context. */
cp_unevaluated u;
tree parms = TREE_OPERAND (t, 0);
args = add_extra_args (REQUIRES_EXPR_EXTRA_ARGS (t), args);
if (processing_template_decl)
{
/* We're partially instantiating a generic lambda. Substituting into
this requires-expression now may cause its requirements to get
checked out of order, so instead just remember the template
arguments and wait until we can substitute them all at once. */
t = copy_node (t);
REQUIRES_EXPR_EXTRA_ARGS (t) = build_extra_args (t, args, complain);
return t;
}
tree parms = REQUIRES_EXPR_PARMS (t);
if (parms)
{
parms = tsubst_constraint_variables (parms, args, info);
@ -2183,14 +2195,11 @@ tsubst_requires_expr (tree t, tree args,
return boolean_false_node;
}
tree reqs = TREE_OPERAND (t, 1);
tree reqs = REQUIRES_EXPR_REQS (t);
reqs = tsubst_requirement_body (reqs, args, info);
if (reqs == error_mark_node)
return boolean_false_node;
if (processing_template_decl)
return finish_requires_expr (cp_expr_location (t), parms, reqs);
return boolean_true_node;
}
@ -2933,7 +2942,7 @@ finish_requires_expr (location_t loc, tree parms, tree reqs)
}
/* Build the node. */
tree r = build_min (REQUIRES_EXPR, boolean_type_node, parms, reqs);
tree r = build_min (REQUIRES_EXPR, boolean_type_node, parms, reqs, NULL_TREE);
TREE_SIDE_EFFECTS (r) = false;
TREE_CONSTANT (r) = true;
SET_EXPR_LOCATION (r, loc);

View file

@ -524,11 +524,13 @@ DEFTREECODE (CONSTRAINT_INFO, "constraint_info", tcc_exceptional, 0)
of the wildcard. */
DEFTREECODE (WILDCARD_DECL, "wildcard_decl", tcc_declaration, 0)
/* A requires-expr is a binary expression. The first operand is
/* A requires-expr has three operands. The first operand is
its parameter list (possibly NULL). The second is a list of
requirements, which are denoted by the _REQ* tree codes
below. */
DEFTREECODE (REQUIRES_EXPR, "requires_expr", tcc_expression, 2)
below. The third is a TREE_VEC of template arguments to
be applied when substituting into the parameter list and
requirements, set by tsubst_requires_expr for partial instantiations. */
DEFTREECODE (REQUIRES_EXPR, "requires_expr", tcc_expression, 3)
/* A requirement for an expression. */
DEFTREECODE (SIMPLE_REQ, "simple_req", tcc_expression, 1)

View file

@ -1618,6 +1618,21 @@ check_constraint_info (tree t)
#define CONSTRAINED_PARM_PROTOTYPE(NODE) \
DECL_INITIAL (TYPE_DECL_CHECK (NODE))
/* The list of local parameters introduced by this requires-expression,
in the form of a chain of PARM_DECLs. */
#define REQUIRES_EXPR_PARMS(NODE) \
TREE_OPERAND (TREE_CHECK (NODE, REQUIRES_EXPR), 0)
/* A TREE_LIST of the requirements for this requires-expression.
The requirements are stored in lexical order within the TREE_VALUE
of each TREE_LIST node. The TREE_PURPOSE of each node is unused. */
#define REQUIRES_EXPR_REQS(NODE) \
TREE_OPERAND (TREE_CHECK (NODE, REQUIRES_EXPR), 1)
/* Like PACK_EXPANSION_EXTRA_ARGS, for requires-expressions. */
#define REQUIRES_EXPR_EXTRA_ARGS(NODE) \
TREE_OPERAND (TREE_CHECK (NODE, REQUIRES_EXPR), 2)
enum cp_tree_node_structure_enum {
TS_CP_GENERIC,
TS_CP_IDENTIFIER,
@ -7013,6 +7028,8 @@ extern bool template_guide_p (const_tree);
extern bool builtin_guide_p (const_tree);
extern void store_explicit_specifier (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);
/* in rtti.c */
/* A vector of all tinfo decls that haven't been emitted yet. */

View file

@ -0,0 +1,18 @@
// { dg-do compile { target c++20 } }
template<typename T>
struct S {
using type = T::type; // { dg-bogus "" }
};
template<typename T>
auto f() {
return [] <typename U> (U) {
// Verify that partial instantiation of this generic lambda doesn't cause
// these requirements to get checked out of order.
static_assert(!requires { typename U::type; typename S<T>::type; });
return 0;
};
}
int a = f<int>()(0);

View file

@ -0,0 +1,25 @@
// PR c++/96410
// { dg-do compile { target c++20 } }
struct S { using blah = void; };
template <typename T> constexpr bool trait = !__is_same(T, S);
template <typename T> concept C = trait<T>;
template<typename T>
void foo() noexcept(!__is_same(T, void)) { }
template<typename U>
auto f() {
return []<typename T>(T, bool a = requires { C<T>; }){
static_assert(requires { requires C<U> && (C<T> || C<T>); }); // { dg-error "assert" }
static_assert(requires { C<T>; });
static_assert(requires { { foo<T>() } noexcept -> C; });
static_assert(!requires { typename T::blah; }); // { dg-error "assert" }
return 0;
};
}
auto g = f<int>(); // { dg-bogus "" }
int n = g(0); // { dg-bogus "" }
int m = g(S{}); // { dg-message "required from here" }