c++: requires-expr with dependent extra args [PR101181]

Here we're crashing ultimately because the mechanism for delaying
substitution into a requires-expression (and constexpr if and pack
expansions) doesn't expect to see dependent args.  But we end up
capturing dependent args here during substitution into the default
template argument as part of coerce_template_parms for the dependent
specialization p<T>.

This patch enables the commented out code in add_extra_args for handling
this situation.  This isn't needed for pack expansions (as the
accompanying comment points out), and it doesn't seem strictly necessary
for constexpr if either, but for requires-expressions delaying even
dependent substitution is important for ensuring we don't evaluate
requirements out of order.

It turns out we also need to make a copy of the arguments when capturing
them so that coerce_template_parms doesn't later add to them and form an
unexpected cycle (REQUIRES_EXPR_EXTRA_ARGS (t) would indirectly point to t).
We also need to make tsubst_template_args handle missing template
arguments, since the arguments we capture from coerce_template_parms
and are incomplete at that point.

	PR c++/101181

gcc/cp/ChangeLog:

	* constraint.cc (tsubst_requires_expr): Pass complain/in_decl to
	add_extra_args.
	* cp-tree.h (add_extra_args): Add complain/in_decl parameters.
	* pt.c (build_extra_args): Make a copy of args.
	(add_extra_args): Add complain/in_decl parameters.  Enable the
	code for handling the case where the extra arguments are
	dependent.
	(tsubst_pack_expansion): Pass complain/in_decl to
	add_extra_args.
	(tsubst_template_args): Handle missing template arguments.
	(tsubst_expr) <case IF_STMT>: Pass complain/in_decl to
	add_extra_args.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp2a/concepts-requires26.C: New test.
	* g++.dg/cpp2a/lambda-uneval16.C: New test.
This commit is contained in:
Patrick Palka 2021-07-09 10:20:25 -04:00
parent f53e66019d
commit 2c699fd298
5 changed files with 58 additions and 18 deletions

View File

@ -2266,7 +2266,8 @@ tsubst_requires_expr (tree t, tree args, sat_info info)
/* A requires-expression is an unevaluated context. */
cp_unevaluated u;
args = add_extra_args (REQUIRES_EXPR_EXTRA_ARGS (t), args);
args = add_extra_args (REQUIRES_EXPR_EXTRA_ARGS (t), args,
info.complain, info.in_decl);
if (processing_template_decl)
{
/* We're partially instantiating a generic lambda. Substituting into

View File

@ -7291,7 +7291,7 @@ extern void add_mergeable_specialization (bool is_decl, bool is_alias,
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 add_extra_args (tree, tree, tsubst_flags_t, tree);
extern tree build_extra_args (tree, tree, tsubst_flags_t);
/* in rtti.c */

View File

@ -12907,7 +12907,9 @@ extract_local_specs (tree pattern, tsubst_flags_t complain)
tree
build_extra_args (tree pattern, tree args, tsubst_flags_t complain)
{
tree extra = args;
/* Make a copy of the extra arguments so that they won't get changed
out from under us. */
tree extra = copy_template_args (args);
if (local_specializations)
if (tree locals = extract_local_specs (pattern, complain))
extra = tree_cons (NULL_TREE, extra, locals);
@ -12918,7 +12920,7 @@ build_extra_args (tree pattern, tree args, tsubst_flags_t complain)
normal template args to ARGS. */
tree
add_extra_args (tree extra, tree args)
add_extra_args (tree extra, tree args, tsubst_flags_t complain, tree in_decl)
{
if (extra && TREE_CODE (extra) == TREE_LIST)
{
@ -12938,20 +12940,14 @@ add_extra_args (tree extra, tree args)
gcc_assert (!TREE_PURPOSE (extra));
extra = TREE_VALUE (extra);
}
#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))
if (uses_template_parms (extra))
{
gcc_unreachable ();
/* This can happen after dependent substitution into a
requires-expr or a lambda that uses constexpr if. */
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;
}
@ -12977,7 +12973,7 @@ tsubst_pack_expansion (tree t, tree args, tsubst_flags_t complain,
pattern = PACK_EXPANSION_PATTERN (t);
/* Add in any args remembered from an earlier partial instantiation. */
args = add_extra_args (PACK_EXPANSION_EXTRA_ARGS (t), args);
args = add_extra_args (PACK_EXPANSION_EXTRA_ARGS (t), args, complain, in_decl);
levels = TMPL_ARGS_DEPTH (args);
@ -13349,7 +13345,9 @@ tsubst_template_args (tree t, tree args, tsubst_flags_t complain, tree in_decl)
tree orig_arg = TREE_VEC_ELT (t, i);
tree new_arg;
if (TREE_CODE (orig_arg) == TREE_VEC)
if (!orig_arg)
new_arg = NULL_TREE;
else if (TREE_CODE (orig_arg) == TREE_VEC)
new_arg = tsubst_template_args (orig_arg, args, complain, in_decl);
else if (PACK_EXPANSION_P (orig_arg))
{
@ -13399,8 +13397,9 @@ tsubst_template_args (tree t, tree args, tsubst_flags_t complain, tree in_decl)
}
for (i = 0, out = 0; i < len; i++)
{
if ((PACK_EXPANSION_P (TREE_VEC_ELT (orig_t, i))
|| ARGUMENT_PACK_P (TREE_VEC_ELT (orig_t, i)))
tree orig_arg = TREE_VEC_ELT (orig_t, i);
if (orig_arg
&& (PACK_EXPANSION_P (orig_arg) || ARGUMENT_PACK_P (orig_arg))
&& TREE_CODE (elts[i]) == TREE_VEC)
{
int idx;
@ -18428,7 +18427,7 @@ tsubst_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl,
IF_STMT_CONSTEXPR_P (stmt) = IF_STMT_CONSTEXPR_P (t);
IF_STMT_CONSTEVAL_P (stmt) = IF_STMT_CONSTEVAL_P (t);
if (IF_STMT_CONSTEXPR_P (t))
args = add_extra_args (IF_STMT_EXTRA_ARGS (t), args);
args = add_extra_args (IF_STMT_EXTRA_ARGS (t), args, complain, in_decl);
tmp = RECUR (IF_COND (t));
tmp = finish_if_stmt_cond (tmp, stmt);
if (IF_STMT_CONSTEXPR_P (t)

View File

@ -0,0 +1,18 @@
// PR c++/101181
// { dg-do compile { target c++20 } }
template<class T,
bool = requires { typename T::type; }>
struct p { using type = void; };
template<class T>
struct p<T, true> { using type = typename T::type; };
template<class T> using P = typename p<T>::type;
using type1 = P<int>;
using type1 = void;
struct A { using type = char; };
using type2 = P<A>;
using type2 = char;

View File

@ -0,0 +1,22 @@
// PR c++/101181
// { dg-do compile { target c++20 } }
template<class T,
bool = [] () -> bool {
if constexpr (requires { typename T::type; })
return true;
return false;
}()>
struct p { using type = void; };
template<class T>
struct p<T, true> { using type = typename T::type; };
template<class T> using P = typename p<T>::type;
using type1 = P<int>;
using type = void;
struct A { using type = char; };
using type2 = P<A>;
using type2 = char;