Use satisfaction with nested requirements.
authorJason Merrill <jason@gcc.gnu.org>
Thu, 7 Nov 2019 00:21:44 +0000 (19:21 -0500)
committerJason Merrill <jason@gcc.gnu.org>
Thu, 7 Nov 2019 00:21:44 +0000 (19:21 -0500)
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

gcc/cp/ChangeLog
gcc/cp/constraint.cc
gcc/cp/parser.c
gcc/cp/pt.c
gcc/testsuite/g++.dg/cpp2a/concepts-requires18.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/concepts-requires19.C [new file with mode: 0644]
gcc/testsuite/lib/prune.exp

index c185af408b0247bfa8fd0aafc4bca2e53b363623..cf3e00a843c043177c10b05de087bd9ed7a8d86c 100644 (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.
index db2a30ced7cc05de1a2babf53df8dbccb7bbf410..00b59a9086812479315f2661a09744e6ce011b4f 100644 (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)
-    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;
-    }
+  gcc_assert (!uses_template_parms (args));
 
-  return finish_nested_requirement (EXPR_LOCATION (t), expr);
+  /* 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;
+  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
index b17e0336e1c988d696da43624fc99e813a54074a..7138aebebced53b0d36f00703c66811c02110b8c 100644 (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);
 
index 313b8073a3c4aff8b4f740eecd7c9e0499b157e7..c8df1d090bc251d0109e94619c9b48a7f120021e 100644 (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
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-requires18.C b/gcc/testsuite/g++.dg/cpp2a/concepts-requires18.C
new file mode 100644 (file)
index 0000000..9d9d0d9
--- /dev/null
@@ -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 (file)
index 0000000..071a838
--- /dev/null
@@ -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" }
+}
index a9beef48ecbbf2d7e19e8eddacc3e847c06ebc32..ae556cad73b9f9b5269a551f628a8296200abcbf 100644 (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