Andrew sent me this patch separately, for handling nested requirements
by normalizing immediately. It still had a regression in
concepts-pr67148.C at that point, due to an issue with
tsubst_pack_expansion: normalizing
requires Same<decltype(f((Args&&)args...)), ResultType<F, Args...> >();
meant substituting the template arguments of the concept through the
requirement, including the (Args&&)args... pack expansion. Here we were
deciding we needed to use PACK_EXPANSION_EXTRA_ARGS, and then getting
confused when we later tried to substitute actual template arguments
into the pack expansion. I first fixed that substitution, but then it
occurred to me that we shouldn't have needed to use
PACK_EXPANSION_EXTRA_ARGS in the first place, we should be able to
substitute directly into the pack expansion pattern. Which worked.
Tested x86_64-pc-linux-gnu, applying to trunk.
commit 05ddc6fb420104088a5dccfcd31e426ffbd2dc17
Author: Andrew Sutton <andrew.n.sut...@gmail.com>
Date: Wed Nov 6 10:12:36 2019 -0500
Use satisfaction with nested requirements.
gcc/cp/
2019-11-06 Andrew Sutton <asut...@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 <ja...@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.
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)
+ 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
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<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" }
+}
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<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" }
+}
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