public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH 1/4] c++: Avoid building garbage trees from tsubst_requires_expr
@ 2021-02-08 19:03 Patrick Palka
  2021-02-08 19:03 ` [PATCH 2/4] c++: Preparatory type canonicalization fixes Patrick Palka
                   ` (5 more replies)
  0 siblings, 6 replies; 29+ messages in thread
From: Patrick Palka @ 2021-02-08 19:03 UTC (permalink / raw)
  To: gcc-patches

Since we no longer partially instantiate REQUIRES_EXPRs, we don't need
to rebuild its requirements during tsubst_requires_expr.

gcc/cp/ChangeLog:

	* constraint.cc (tsubst_simple_requirement): Just return
	boolean_true_node on success.
	(tsubst_type_requirement): Likewise.
	(tsubst_compound_requirement): Likewise.
	(tsubst_nested_requirement): Likewise.
	(tsubst_requirement_body): Remove.
	(check_constaint_variables): Rename to ...
	(check_constraint_variables): ... this.
	(tsubst_constraint_variables): Adjust.
	(tsubst_requires_expr): Fold tsubst_requirement_body into here.
---
 gcc/cp/constraint.cc | 46 ++++++++++++++------------------------------
 1 file changed, 14 insertions(+), 32 deletions(-)

diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index 31e0fb5079a..3e599fe8c47 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -1962,7 +1962,7 @@ tsubst_simple_requirement (tree t, tree args, subst_info info)
   tree expr = tsubst_valid_expression_requirement (t0, args, info);
   if (expr == error_mark_node)
     return error_mark_node;
-  return finish_simple_requirement (EXPR_LOCATION (t), expr);
+  return boolean_true_node;
 }
 
 /* Substitute through the type requirement.  */
@@ -1974,7 +1974,7 @@ tsubst_type_requirement (tree t, tree args, subst_info info)
   tree type = tsubst (t0, args, info.complain, info.in_decl);
   if (type == error_mark_node)
     return error_mark_node;
-  return finish_type_requirement (EXPR_LOCATION (t), type);
+  return boolean_true_node;
 }
 
 /* True if TYPE can be deduced from EXPR.  */
@@ -2080,8 +2080,7 @@ tsubst_compound_requirement (tree t, tree args, subst_info info)
 	return error_mark_node;
     }
 
-  return finish_compound_requirement (EXPR_LOCATION (t),
-				      expr, type, noexcept_p);
+  return boolean_true_node;
 }
 
 static tree
@@ -2100,7 +2099,7 @@ tsubst_nested_requirement (tree t, tree args, subst_info info)
     }
   if (result != boolean_true_node)
     return error_mark_node;
-  return result;
+  return boolean_true_node;
 }
 
 /* Substitute ARGS into the requirement T.  */
@@ -2125,24 +2124,6 @@ tsubst_requirement (tree t, tree args, subst_info info)
   gcc_unreachable ();
 }
 
-/* Substitute ARGS into the list of requirements T. Note that
-   substitution failures here result in ill-formed programs. */
-
-static tree
-tsubst_requirement_body (tree t, tree args, subst_info info)
-{
-  tree result = NULL_TREE;
-  while (t)
-    {
-      tree req = tsubst_requirement (TREE_VALUE (t), args, info);
-      if (req == error_mark_node)
-	return error_mark_node;
-      result = tree_cons (NULL_TREE, req, result);
-      t = TREE_CHAIN (t);
-    }
-  return nreverse (result);
-}
-
 static tree
 declare_constraint_vars (tree parms, tree vars)
 {
@@ -2168,7 +2149,7 @@ declare_constraint_vars (tree parms, tree vars)
    if an error occurred.  */
 
 static tree
-check_constaint_variables (tree t, tree args, subst_info info)
+check_constraint_variables (tree t, tree args, subst_info info)
 {
   tree types = NULL_TREE;
   tree p = t;
@@ -2193,7 +2174,7 @@ static tree
 tsubst_constraint_variables (tree t, tree args, subst_info info)
 {
   /* Perform a trial substitution to check for type errors.  */
-  tree parms = check_constaint_variables (t, args, info);
+  tree parms = check_constraint_variables (t, args, info);
   if (parms == error_mark_node)
     return error_mark_node;
 
@@ -2253,19 +2234,20 @@ tsubst_requires_expr (tree t, tree args,
       return t;
     }
 
-  tree parms = REQUIRES_EXPR_PARMS (t);
-  if (parms)
+  if (tree parms = REQUIRES_EXPR_PARMS (t))
     {
       parms = tsubst_constraint_variables (parms, args, info);
       if (parms == error_mark_node)
 	return boolean_false_node;
     }
 
-  tree reqs = REQUIRES_EXPR_REQS (t);
-  reqs = tsubst_requirement_body (reqs, args, info);
-  if (reqs == error_mark_node)
-    return boolean_false_node;
-
+  for (tree reqs = REQUIRES_EXPR_REQS (t); reqs; reqs = TREE_CHAIN (reqs))
+    {
+      tree req = TREE_VALUE (reqs);
+      tree result = tsubst_requirement (req, args, info);
+      if (result == error_mark_node)
+	return boolean_false_node;
+    }
   return boolean_true_node;
 }
 
-- 
2.30.0.452.gfb7fa4a1fd


^ permalink raw reply	[flat|nested] 29+ messages in thread

* [PATCH 2/4] c++: Preparatory type canonicalization fixes
  2021-02-08 19:03 [PATCH 1/4] c++: Avoid building garbage trees from tsubst_requires_expr Patrick Palka
@ 2021-02-08 19:03 ` Patrick Palka
  2021-02-09  5:11   ` Jason Merrill
  2021-02-08 19:03 ` [PATCH 3/4] c++: Delay normalizing nested requirements until satisfaction Patrick Palka
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 29+ messages in thread
From: Patrick Palka @ 2021-02-08 19:03 UTC (permalink / raw)
  To: gcc-patches

The subsequent patches revealed some latent type canonicalization
issues during normalization and satisfaction:

1. In tsubst_parameter_mapping, we're canonicalizing the arguments of a
   TYPE_ARGUMENT_PACK only if 'arg' wasn't a TYPE_ARGUMENT_PACK to begin
   with.

2. We currently set DECL_CONTEXT and CONSTRAINT_VAR_P on each of the
   parameters introduced in a requires-expression _after_ we're done
   processing the requirements.  But meanwhile we may have already
   computed the canonical form of a type that depends on one of these
   PARM_DECLs, which depends on the result of cp_tree_equal, which
   depends on CONSTRAINT_VAR_P and DECL_CONTEXT.  So we must set these
   fields earlier, before processing the requirements.

3. In do_auto_deduction, we use the result of finish_decltype_type later
   as a template argument, so we should canonicalize the result too.
   (While we're here, we should pass 'complain' to finish_decltype_type,
   which fixes the testcase auto1.C below.)

gcc/cp/ChangeLog:

	* constraint.cc (tsubst_parameter_mapping): Canonicalize the
	arguments of a TYPE_ARGUMENT_PACK even if we've started with a
	TYPE_ARGUMENT_PACK.
	(finish_requires_expr): Don't set DECL_CONTEXT and
	CONSTRAINT_VAR_P on each of the introduced parameters here.
	* parser.c (cp_parser_requirement_parameter_list): Instead set
	these fields earlier, here.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp1z/auto1.C: New test.
---
 gcc/cp/constraint.cc               | 25 ++++++++-----------------
 gcc/cp/parser.c                    | 12 ++++++++++++
 gcc/cp/pt.c                        |  8 ++++++--
 gcc/testsuite/g++.dg/cpp1z/auto1.C | 13 +++++++++++++
 4 files changed, 39 insertions(+), 19 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp1z/auto1.C

diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index 3e599fe8c47..39c97986082 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -2319,15 +2319,15 @@ tsubst_parameter_mapping (tree map, tree args, subst_info info)
 	  new_arg = tsubst_template_arg (arg, args, complain, in_decl);
 	  if (TYPE_P (new_arg))
 	    new_arg = canonicalize_type_argument (new_arg, complain);
-	  if (TREE_CODE (new_arg) == TYPE_ARGUMENT_PACK)
+	}
+      if (TREE_CODE (new_arg) == TYPE_ARGUMENT_PACK)
+	{
+	  tree pack_args = ARGUMENT_PACK_ARGS (new_arg);
+	  for (int i = 0; i < TREE_VEC_LENGTH (pack_args); i++)
 	    {
-	      tree pack_args = ARGUMENT_PACK_ARGS (new_arg);
-	      for (int i = 0; i < TREE_VEC_LENGTH (pack_args); i++)
-		{
-		  tree& pack_arg = TREE_VEC_ELT (pack_args, i);
-		  if (TYPE_P (pack_arg))
-		    pack_arg = canonicalize_type_argument (pack_arg, complain);
-		}
+	      tree& pack_arg = TREE_VEC_ELT (pack_args, i);
+	      if (TYPE_P (pack_arg))
+		pack_arg = canonicalize_type_argument (pack_arg, complain);
 	    }
 	}
       if (new_arg == error_mark_node)
@@ -3253,15 +3253,6 @@ evaluate_concept_check (tree check, tsubst_flags_t complain)
 tree
 finish_requires_expr (location_t loc, tree parms, tree reqs)
 {
-  /* Modify the declared parameters by removing their context
-     so they don't refer to the enclosing scope and explicitly
-     indicating that they are constraint variables. */
-  for (tree parm = parms; parm; parm = DECL_CHAIN (parm))
-    {
-      DECL_CONTEXT (parm) = NULL_TREE;
-      CONSTRAINT_VAR_P (parm) = true;
-    }
-
   /* Build the node. */
   tree r = build_min (REQUIRES_EXPR, boolean_type_node, parms, reqs, NULL_TREE);
   TREE_SIDE_EFFECTS (r) = false;
diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c
index 5da8670f0e2..fef10b9661d 100644
--- a/gcc/cp/parser.c
+++ b/gcc/cp/parser.c
@@ -28779,6 +28779,18 @@ cp_parser_requirement_parameter_list (cp_parser *parser)
   if (!parens.require_close (parser))
     return error_mark_node;
 
+  /* Modify the declared parameters by removing their context
+     so they don't refer to the enclosing scope and explicitly
+     indicating that they are constraint variables. */
+  for (tree parm = parms; parm; parm = TREE_CHAIN (parm))
+    {
+      if (parm == void_list_node || parm == explicit_void_list_node)
+	break;
+      tree decl = TREE_VALUE (parm);
+      DECL_CONTEXT (decl) = NULL_TREE;
+      CONSTRAINT_VAR_P (decl) = true;
+    }
+
   return parms;
 }
 
diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
index 3605b67e424..bceb942e79a 100644
--- a/gcc/cp/pt.c
+++ b/gcc/cp/pt.c
@@ -29478,9 +29478,13 @@ do_auto_deduction (tree type, tree init, tree auto_node,
 		 || ((TREE_CODE (init) == COMPONENT_REF
 		      || TREE_CODE (init) == SCOPE_REF)
 		     && !REF_PARENTHESIZED_P (init)));
+      tree deduced = finish_decltype_type (init, id, complain);
+      deduced = canonicalize_type_argument (deduced, complain);
+      if (deduced == error_mark_node)
+	return error_mark_node;
       targs = make_tree_vec (1);
-      TREE_VEC_ELT (targs, 0)
-	= finish_decltype_type (init, id, tf_warning_or_error);
+      TREE_VEC_ELT (targs, 0) = deduced;
+      /* FIXME: These errors ought to be diagnosed at parse time. */
       if (type != auto_node)
 	{
           if (complain & tf_error)
diff --git a/gcc/testsuite/g++.dg/cpp1z/auto1.C b/gcc/testsuite/g++.dg/cpp1z/auto1.C
new file mode 100644
index 00000000000..5cc762a386e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1z/auto1.C
@@ -0,0 +1,13 @@
+// Verify that deduction failure of the decltype(auto) template parameter is
+// a SFINAE error.
+// { dg-do compile { target c++17 } }
+
+template <class> void f();
+template <class> void f(int);
+
+template <class T = int, decltype(auto) = &f<T>> void g();
+template <class = int> void g();
+
+int main() {
+  g();
+}
-- 
2.30.0.452.gfb7fa4a1fd


^ permalink raw reply	[flat|nested] 29+ messages in thread

* [PATCH 3/4] c++: Delay normalizing nested requirements until satisfaction
  2021-02-08 19:03 [PATCH 1/4] c++: Avoid building garbage trees from tsubst_requires_expr Patrick Palka
  2021-02-08 19:03 ` [PATCH 2/4] c++: Preparatory type canonicalization fixes Patrick Palka
@ 2021-02-08 19:03 ` Patrick Palka
  2021-02-09 23:04   ` Jason Merrill
  2021-02-08 19:03 ` [PATCH 4/4] c++: dependent constraint on placeholder 'auto' [PR96443] Patrick Palka
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 29+ messages in thread
From: Patrick Palka @ 2021-02-08 19:03 UTC (permalink / raw)
  To: gcc-patches

This sets up the functionality for controlling the initial set of
template parameters to pass to normalization when dealing with a
constraint-expression that is not associated with some constrained
declaration, for instance when normalizing a nested requirement of a
requires expression, or the constraints on a placeholder type.

The main new ingredient here is the data member norm_info::initial_parms
which can be set by callers of the normalization routines to communicate
the in-scope template parameters for the supplied constraint-expression,
rather than always falling back to using current_template_parms.

This patch then uses this functionality in our handling of nested
requirements so that we can delay normalizing them until needed for
satisfaction.  We currently immediately normalize nested requirements at
parse time, where we have the necessary template context, and cache the
normal form in their TREE_TYPE node.  With this patch, we now delay
normalization until needed (as with other constraint expressions), and
instead store the current value of current_template_parms in their
TREE_TYPE node (which we use to restore the template context at
normalization time).

In the subsequent patch, this functionality will also be used to
normalize placeholder type constraints during auto deduction.

gcc/cp/ChangeLog:

	* constraint.cc (build_parameter_mapping): Rely on the caller to
	determine the in-scope template parameters.
	(norm_info::norm_info): Delegate the one-parameter constructor
	to the two-parameter constructor.  In the two-parameter
	constructor, fold in the definition of make_context, set
	initial_parms appropriately, and don't set the now-removed
	orig_decl member.
	(norm_info::make_context): Remove, now that its only use is
	inlined into the caller.
	(norm_info::update_context): Adjust call to
	build_parameter_mapping to pass in the relevant set of in-scope
	template parameters.
	(norm_info::ctx_parms): Define this member function.
	(norm_info::context): Initialize to NULL_TREE.
	(norm_info::orig_decl): Remove this data member.
	(norm_info::initial_parms): Define this data member.
	(normalize_atom): Adjust call to build_parameter_mapping to pass
	in the relevant set of in-scope template parameters.  Use
	info.initial_parms instead of info.orig_decl.
	(normalize_constraint_expression): Define an overload that takes
	a norm_info object.  Cache the result of normalization.  Define
	the other overload in terms of this one, and handle a NESTED_REQ
	argument by setting info.initial_parms appropriately.
	(tsubst_nested_requirement): Go through
	satisfy_constraint_expression so that we normalize on demand.
	(finish_nested_requirement): Set the TREE_TYPE of the NESTED_REQ
	to current_template_parms.
	(diagnose_nested_requirements): Go through
	satisfy_constraint_expression, as with tsubst_nested_requirement.
---
 gcc/cp/constraint.cc | 140 +++++++++++++++++++++++--------------------
 gcc/cp/cp-tree.h     |   4 +-
 2 files changed, 78 insertions(+), 66 deletions(-)

diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index 39c97986082..56134f8b2bf 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -133,7 +133,7 @@ struct sat_info : subst_info
   bool diagnose_unsatisfaction;
 };
 
-static tree satisfy_constraint (tree, tree, sat_info);
+static tree satisfy_constraint_expression (tree, tree, sat_info);
 
 /* True if T is known to be some type other than bool. Note that this
    is false for dependent types and errors.  */
@@ -594,26 +594,12 @@ map_arguments (tree parms, tree args)
   return parms;
 }
 
-/* Build the parameter mapping for EXPR using ARGS.  */
+/* Build the parameter mapping for EXPR using ARGS, where CTX_PARMS
+   are the template parameters in scope for EXPR.  */
 
 static tree
-build_parameter_mapping (tree expr, tree args, tree decl)
+build_parameter_mapping (tree expr, tree args, tree ctx_parms)
 {
-  tree ctx_parms = NULL_TREE;
-  if (decl)
-    {
-      gcc_assert (TREE_CODE (decl) == TEMPLATE_DECL);
-      ctx_parms = DECL_TEMPLATE_PARMS (decl);
-    }
-  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.  */
-      ctx_parms = current_template_parms;
-    }
-
   tree parms = find_template_parameters (expr, ctx_parms);
   tree map = map_arguments (parms, args);
   return map;
@@ -645,53 +631,63 @@ parameter_mapping_equivalent_p (tree t1, tree t2)
 
 struct norm_info : subst_info
 {
-  explicit norm_info (tsubst_flags_t complain)
-    : subst_info (tf_warning_or_error | complain, NULL_TREE),
-      context()
+  explicit norm_info (tsubst_flags_t cmp)
+    : norm_info (NULL_TREE, cmp)
   {}
 
   /* Construct a top-level context for DECL.  */
 
   norm_info (tree in_decl, tsubst_flags_t complain)
-    : subst_info (tf_warning_or_error | complain, in_decl),
-      context (make_context (in_decl)),
-      orig_decl (in_decl)
-  {}
-
-  bool generate_diagnostics() const
+    : subst_info (tf_warning_or_error | complain, in_decl)
   {
-    return complain & tf_norm;
+    if (in_decl)
+      {
+	initial_parms = DECL_TEMPLATE_PARMS (in_decl);
+	if (generate_diagnostics ())
+	  context = build_tree_list (NULL_TREE, in_decl);
+      }
+    else
+      initial_parms = current_template_parms;
   }
 
-  tree make_context(tree in_decl)
+  bool generate_diagnostics() const
   {
-    if (generate_diagnostics ())
-      return build_tree_list (NULL_TREE, in_decl);
-    return NULL_TREE;
+    return complain & tf_norm;
   }
 
   void update_context(tree expr, tree args)
   {
     if (generate_diagnostics ())
       {
-	tree map = build_parameter_mapping (expr, args, in_decl);
+	tree map = build_parameter_mapping (expr, args, ctx_parms ());
 	context = tree_cons (map, expr, context);
       }
     in_decl = get_concept_check_template (expr);
   }
 
+  /* Returns the template parameters that are in scope for the current
+     normalization context.  */
+
+  tree ctx_parms()
+  {
+    if (in_decl)
+      return DECL_TEMPLATE_PARMS (in_decl);
+    else
+      return initial_parms;
+  }
+
   /* Provides information about the source of a constraint. This is a
      TREE_LIST whose VALUE is either a concept check or a constrained
      declaration. The PURPOSE, for concept checks is a parameter mapping
      for that check.  */
 
-  tree context;
+  tree context = NULL_TREE;
 
   /* The declaration whose constraints we're normalizing.  The targets
      of the parameter mapping of each atom will be in terms of the
      template parameters of ORIG_DECL.  */
 
-  tree orig_decl = NULL_TREE;
+  tree initial_parms = NULL_TREE;
 };
 
 static tree normalize_expression (tree, tree, norm_info);
@@ -773,7 +769,7 @@ normalize_atom (tree t, tree args, norm_info info)
     return normalize_concept_check (t, args, info);
 
   /* Build the parameter mapping for the atom.  */
-  tree map = build_parameter_mapping (t, args, info.in_decl);
+  tree map = build_parameter_mapping (t, args, info.ctx_parms ());
 
   /* Build a new info object for the atom.  */
   tree ci = build_tree_list (t, info.context);
@@ -803,10 +799,8 @@ normalize_atom (tree t, tree args, norm_info info)
 	      tree target = TREE_PURPOSE (node);
 	      TREE_VEC_ELT (targets, i++) = target;
 	    }
-	  tree ctx_parms = (info.orig_decl
-			    ? DECL_TEMPLATE_PARMS (info.orig_decl)
-			    : current_template_parms);
-	  tree target_parms = find_template_parameters (targets, ctx_parms);
+	  tree target_parms = find_template_parameters (targets,
+							info.initial_parms);
 	  TREE_TYPE (map) = target_parms;
 	}
 
@@ -983,17 +977,43 @@ normalize_nontemplate_requirements (tree decl, bool diag = false)
 /* Normalize an EXPR as a constraint.  */
 
 static tree
-normalize_constraint_expression (tree expr, bool diag)
+normalize_constraint_expression (tree expr, norm_info info)
 {
   if (!expr || expr == error_mark_node)
     return expr;
+
+  if (!info.generate_diagnostics ())
+    if (tree *p = hash_map_safe_get (normalized_map, expr))
+      return *p;
+
   ++processing_template_decl;
-  norm_info info (diag ? tf_norm : tf_none);
   tree norm = get_normalized_constraints (expr, info);
   --processing_template_decl;
+
+  if (!info.generate_diagnostics ())
+    hash_map_safe_put<hm_ggc> (normalized_map, expr, norm);
+
   return norm;
 }
 
+/* High-level wrapper for the above.  */
+
+static tree
+normalize_constraint_expression (tree expr, bool diag)
+{
+  norm_info info (diag ? tf_norm : tf_none);
+  if (TREE_CODE (expr) == NESTED_REQ)
+    {
+      /* The TREE_TYPE contains the set of template parameters that were
+	 in scope for this nested requiremen; use them as the initial template
+	 parameters for normalization.  */
+      info.initial_parms = TREE_TYPE (expr);
+      return normalize_constraint_expression (TREE_OPERAND (expr, 0), info);
+    }
+  else
+    return normalize_constraint_expression (expr, info);
+}
+
 /* 17.4.1.2p2. Two constraints are identical if they are formed
    from the same expression and the targets of the parameter mapping
    are equivalent.  */
@@ -2086,16 +2106,14 @@ tsubst_compound_requirement (tree t, tree args, subst_info info)
 static tree
 tsubst_nested_requirement (tree t, tree args, subst_info info)
 {
-  /* Perform satisfaction quietly with the regular normal form.  */
+  /* Perform satisfaction quietly first.  */
   sat_info quiet (tf_none, info.in_decl);
-  tree norm = TREE_VALUE (TREE_TYPE (t));
-  tree diag_norm = TREE_PURPOSE (TREE_TYPE (t));
-  tree result = satisfy_constraint (norm, args, quiet);
+  tree result = satisfy_constraint_expression (t, args, quiet);
   if (result == error_mark_node)
     {
-      /* Replay the error using the diagnostic normal form.  */
+      /* Replay the error.  */
       sat_info noisy (tf_warning_or_error, info.in_decl);
-      satisfy_constraint (diag_norm, args, noisy);
+      satisfy_constraint_expression (t, args, noisy);
     }
   if (result != boolean_true_node)
     return error_mark_node;
@@ -3301,15 +3319,9 @@ finish_compound_requirement (location_t loc, tree expr, tree type, bool noexcept
 tree
 finish_nested_requirement (location_t loc, tree expr)
 {
-  /* We need to normalize the constraints now, at parse time, while
-     we have the necessary template context.  We normalize twice,
-     once without diagnostic information and once with, which we'll
-     later use for quiet and noisy satisfaction respectively.  */
-  tree norm = normalize_constraint_expression (expr, /*diag=*/false);
-  tree diag_norm = normalize_constraint_expression (expr, /*diag=*/true);
-
-  /* Build the constraint, saving its two normalizations as its type.  */
-  tree r = build1 (NESTED_REQ, build_tree_list (diag_norm, norm), expr);
+  /* Build the requirement, saving the set of in-scope template
+     parameters as its type.  */
+  tree r = build1 (NESTED_REQ, current_template_parms, expr);
   SET_EXPR_LOCATION (r, loc);
   return r;
 }
@@ -3710,12 +3722,9 @@ diagnose_type_requirement (tree req, tree args, tree in_decl)
 static void
 diagnose_nested_requirement (tree req, tree args)
 {
-  /* Quietly check for satisfaction first using the regular normal form.
-     We can elaborate details later if needed.  */
-  tree norm = TREE_VALUE (TREE_TYPE (req));
-  tree diag_norm = TREE_PURPOSE (TREE_TYPE (req));
-  sat_info info (tf_none, NULL_TREE);
-  tree result = satisfy_constraint (norm, args, info);
+  /* Quietly check for satisfaction first.  */
+  sat_info quiet (tf_none, NULL_TREE);
+  tree result = satisfy_constraint_expression (req, args, quiet);
   if (result == boolean_true_node)
     return;
 
@@ -3723,10 +3732,11 @@ diagnose_nested_requirement (tree req, tree args)
   location_t loc = cp_expr_location (expr);
   if (diagnosing_failed_constraint::replay_errors_p ())
     {
-      /* Replay the substitution error using the diagnostic normal form.  */
+      /* Replay the substitution error with re-normalized requirements.  */
       inform (loc, "nested requirement %qE is not satisfied, because", expr);
+
       sat_info noisy (tf_warning_or_error, NULL_TREE, /*diag_unsat=*/true);
-      satisfy_constraint (diag_norm, args, noisy);
+      satisfy_constraint_expression (req, args, noisy);
     }
   else
     inform (loc, "nested requirement %qE is not satisfied", expr);
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 970ed5e77bb..26fbf1eb663 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -1587,7 +1587,9 @@ check_constraint_info (tree t)
   TREE_LANG_FLAG_0 (TREE_CHECK (NODE, COMPOUND_REQ))
 
 /* The constraints on an 'auto' placeholder type, used in an argument deduction
-   constraint.  */
+   constraint.  This should usually be set via set_placeholder_type_constraints
+   since we also need to record the relevant set of in-scope template parameters
+   for later normalization. */
 #define PLACEHOLDER_TYPE_CONSTRAINTS(NODE) \
   DECL_SIZE_UNIT (TYPE_NAME (NODE))
 
-- 
2.30.0.452.gfb7fa4a1fd


^ permalink raw reply	[flat|nested] 29+ messages in thread

* [PATCH 4/4] c++: dependent constraint on placeholder 'auto' [PR96443]
  2021-02-08 19:03 [PATCH 1/4] c++: Avoid building garbage trees from tsubst_requires_expr Patrick Palka
  2021-02-08 19:03 ` [PATCH 2/4] c++: Preparatory type canonicalization fixes Patrick Palka
  2021-02-08 19:03 ` [PATCH 3/4] c++: Delay normalizing nested requirements until satisfaction Patrick Palka
@ 2021-02-08 19:03 ` Patrick Palka
  2021-02-08 19:39   ` Patrick Palka
  2021-02-11 16:19   ` Jason Merrill
  2021-02-09  5:09 ` [PATCH 1/4] c++: Avoid building garbage trees from tsubst_requires_expr Jason Merrill
                   ` (2 subsequent siblings)
  5 siblings, 2 replies; 29+ messages in thread
From: Patrick Palka @ 2021-02-08 19:03 UTC (permalink / raw)
  To: gcc-patches

This fixes the way we check satisfaction of constraints on placeholder
types in various contexts, and in particular when the constraint is
dependent.

Firstly, when evaluating the return type requirement of a compound
requirement, we currently substitute the outer template arguments into
the constraint before checking satisfaction. But we should instead be
passing in the complete set of template arguments to satisfaction and
not do a prior separate substitution.  Our current approach leads to us
incorrectly rejecting the testcase concepts-return-req2.C below.

Secondly, when checking the constraints on a placeholder variable or
return type, we don't substitute the template arguments of the enclosing
context at all.  This leads to bogus errors during satisfaction when the
constraint is dependent as in the testcase concepts-placeholder3.C
below.

In order to fix these two issues, we need to be able to properly
normalize the constraints on a placeholder 'auto', which in turn
requires us to know the template parameters that were in-scope where an
'auto' was introduced.  This information currently doesn't seem to be
easily available when we need it, so this patch adds an auxiliary hash
table that keeps track of the value of current_template_parms when each
constrained 'auto' was formed.

This patch also removes some seemingly wrong handling of placeholder
type arguments from tsubst_parameter_mapping.  The code doesn't trigger
with the example used in the comments, because type_uses_auto doesn't
look inside non-deduced contexts such as the operand of decltype.  And
the call to do_auto_deduction seems confused because if 'arg' is a type,
then so is 'parm', and therefore 'init' too is a type, but
do_auto_deduction expects it to be an expression.  Before this patch,
this code was dead (as far as our testsuite can tell), but now it breaks
other parts of this patch, so let's remove it.

gcc/cp/ChangeLog:

	PR c++/96443
	* constraint.cc (type_deducible_p): Don't substitute into the
	constraints, and instead just pass 'args' to do_auto_deduction
	as the outer template arguments.
	(tsubst_parameter_mapping): Remove confused code for handling
	placeholder type arguments.
	(normalize_placeholder_type_constraint): Define.
	(satisfy_constraint_expression): Use it to handle placeholder
	'auto' types.
	* cp-tree.h (get_constrained_auto_context): Declare.
	* pt.c (constrained_auto_context_map): Define.
	(get_placeholder_type_constraint_context): Define.
	(set_placeholder_type_constraints): Define.
	(copy_placeholder_type_constraints): Define.
	(tsubst) <case TEMPLATE_TYPE_PARM>: Use
	copy_placeholder_type_constraints.
	(make_constrained_placeholder_type): Use
	set_placeholder_type_constraints.
	(do_auto_deduction): Clarify comments about the outer_targs
	parameter.  Rework satisfaction of a placeholder type constraint
	to pass in the complete set of template arguments directly to
	constraints_satisfied_p.
	(splice_late_return_type): Use copy_placeholder_type_constraints.

gcc/testsuite/ChangeLog:

	PR c++/96443
	* g++.dg/cpp2a/concepts-placeholder3.C: New test.
	* g++.dg/cpp2a/concepts-return-req2.C: New test.
	* g++.dg/concepts-ts1.C: Add dg-bogus directive to the call to
	f15 that we expect to accept.
---
 gcc/cp/constraint.cc                          | 106 ++++++++----------
 gcc/cp/cp-tree.h                              |   1 +
 gcc/cp/pt.c                                   | 101 +++++++++++------
 .../g++.dg/cpp2a/concepts-placeholder3.C      |  19 ++++
 .../g++.dg/cpp2a/concepts-return-req2.C       |  13 +++
 gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C     |   2 +-
 6 files changed, 146 insertions(+), 96 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-placeholder3.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-return-req2.C

diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index 56134f8b2bf..53588047d44 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -2007,39 +2007,19 @@ type_deducible_p (tree expr, tree type, tree placeholder, tree args,
      references are preserved in the result.  */
   expr = force_paren_expr_uneval (expr);
 
-  /* Replace the constraints with the instantiated constraints. This
-     substitutes args into any template parameters in the trailing
-     result type.  */
-  tree saved_constr = PLACEHOLDER_TYPE_CONSTRAINTS (placeholder);
-  tree subst_constr
-    = tsubst_constraint (saved_constr,
-			 args,
-			 info.complain | tf_partial,
-			 info.in_decl);
-
-  if (subst_constr == error_mark_node)
-    return false;
-
-  PLACEHOLDER_TYPE_CONSTRAINTS (placeholder) = subst_constr;
-
-  /* Temporarily unlink the canonical type.  */
-  tree saved_type = TYPE_CANONICAL (placeholder);
-  TYPE_CANONICAL (placeholder) = NULL_TREE;
-
-  tree deduced_type
-    = do_auto_deduction (type,
-			 expr,
-			 placeholder,
-			 info.complain,
-			 adc_requirement);
-
-  PLACEHOLDER_TYPE_CONSTRAINTS (placeholder) = saved_constr;
-  TYPE_CANONICAL (placeholder) = saved_type;
+  /* When args is empty, we're evaluating a non-templated requires expression,
+     but even those are parsed under processing_template_decl == 1, and so the
+     placeholder 'auto' inside this return-type-requirement has level 2.  In
+     order to have all parms and arguments match up for satisfaction, we need
+     to pass a single level as OUTER_TARGS in this case.  */
+  if (!args)
+    args = make_tree_vec (0);
 
-  if (deduced_type == error_mark_node)
-    return false;
+  tree deduced_type = do_auto_deduction (type, expr, placeholder,
+					 info.complain, adc_requirement,
+					 /*outer_targs=*/args);
 
-  return true;
+  return deduced_type != error_mark_node;
 }
 
 /* True if EXPR can not be converted to TYPE.  */
@@ -2304,35 +2284,10 @@ tsubst_parameter_mapping (tree map, tree args, subst_info info)
         return error_mark_node;
       tree parm = TREE_VALUE (p);
       tree arg = TREE_PURPOSE (p);
-      tree new_arg = NULL_TREE;
-      if (TYPE_P (arg))
-        {
-          /* If a template parameter is declared with a placeholder, we can
-             get those in the argument list if decltype is applied to the
-             placeholder. For example:
-
-		template<auto T>
-		  requires C<decltype(T)>
-		void f() { }
-
-	     The normalized argument for C will be an auto type, so we'll
-             need to deduce the actual argument from the corresponding
-             initializer (whatever argument is provided for T), and use
-             that result in the instantiated parameter mapping.  */
-          if (tree auto_node = type_uses_auto (arg))
-            {
-              int level;
-              int index;
-	      template_parm_level_and_index (parm, &level, &index);
-	      tree init = TMPL_ARG (args, level, index);
-              new_arg = do_auto_deduction (arg, init, auto_node,
-					   complain, adc_variable_type,
-					   make_tree_vec (0));
-            }
-        }
-      else if (ARGUMENT_PACK_P (arg))
+      tree new_arg;
+      if (ARGUMENT_PACK_P (arg))
 	new_arg = tsubst_argument_pack (arg, args, complain, in_decl);
-      if (!new_arg)
+      else
 	{
 	  new_arg = tsubst_template_arg (arg, args, complain, in_decl);
 	  if (TYPE_P (new_arg))
@@ -3038,6 +2993,31 @@ satisfy_associated_constraints (tree t, tree args, sat_info info)
   return satisfy_constraint (t, args, info);
 }
 
+/* Normalize the constraints on the placeholder 'auto' type T.  */
+
+tree
+normalize_placeholder_type_constraints (tree t, bool diag)
+{
+  gcc_assert (is_auto (t));
+  tree constr = PLACEHOLDER_TYPE_CONSTRAINTS (t);
+  if (!constr)
+    return NULL_TREE;
+
+  tree initial_parms = get_constrained_auto_context (t);
+  /* The 'auto' itself is used as the first argument in its own constraints,
+     and its level is one greater than its template context, so in order to
+     capture all used template parameters we need to add an extra level of
+     template parameters to the context; a dummy level suffices.  */
+  initial_parms
+    = tree_cons (size_int (initial_parms
+			   ? TMPL_PARMS_DEPTH (initial_parms) + 1 : 1),
+		 make_tree_vec (0), initial_parms);
+
+  norm_info info (diag ? tf_norm : tf_none);
+  info.initial_parms = initial_parms;
+  return normalize_constraint_expression (constr, info);
+}
+
 /* Evaluate EXPR as a constraint expression using ARGS, returning a
    satisfaction value. */
 
@@ -3047,8 +3027,6 @@ satisfy_constraint_expression (tree t, tree args, sat_info info)
   if (t == error_mark_node)
     return error_mark_node;
 
-  gcc_assert (EXPR_P (t));
-
   /* Get the normalized constraints.  */
   tree norm;
   if (args == NULL_TREE && concept_check_p (t))
@@ -3058,8 +3036,12 @@ satisfy_constraint_expression (tree t, tree args, sat_info info)
       tree tmpl = get_concept_check_template (id);
       norm = normalize_concept_definition (tmpl, info.noisy ());
     }
-  else
+  else if (EXPR_P (t))
     norm = normalize_constraint_expression (t, info.noisy ());
+  else if (is_auto (t))
+    norm = normalize_placeholder_type_constraints (t, info.noisy ());
+  else
+    gcc_unreachable ();
 
   /* Perform satisfaction.  */
   return satisfy_constraint (norm, args, info);
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 26fbf1eb663..ca5550ccb6d 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7090,6 +7090,7 @@ extern tree make_auto				(void);
 extern tree make_decltype_auto			(void);
 extern tree make_constrained_auto		(tree, tree);
 extern tree make_constrained_decltype_auto	(tree, tree);
+extern tree get_constrained_auto_context	(tree);
 extern tree make_template_placeholder		(tree);
 extern bool template_placeholder_p		(tree);
 extern bool ctad_template_p			(tree);
diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
index bceb942e79a..bcf9c9a3d9a 100644
--- a/gcc/cp/pt.c
+++ b/gcc/cp/pt.c
@@ -15364,6 +15364,46 @@ tsubst_tree_list (tree t, tree args, tsubst_flags_t complain, tree in_decl)
   return chain;
 }
 
+/* A hash table mapping a constrained 'auto' to the set of in-scope
+   template parameters from where the 'auto' was introduced.  */
+
+static GTY((cache)) decl_tree_cache_map *constrained_auto_context_map;
+
+/* Return the set of in-scope template parameters for the constrained 'auto'
+   placeholder type TYPE.  */
+
+tree
+get_constrained_auto_context (tree type)
+{
+  gcc_assert (is_constrained_auto (type));
+  return *constrained_auto_context_map->get (TYPE_NAME (type));
+}
+
+/* Set the constraints on TYPE, an 'auto' placeholder type, to CONSTR.
+   TPARMS is the set of template parameters that are in scope.  */
+
+static void
+set_placeholder_type_constraints (tree type, tree constr, tree tparms)
+{
+  gcc_assert (is_auto (type));
+  PLACEHOLDER_TYPE_CONSTRAINTS (type) = constr;
+  hash_map_safe_put<hm_ggc> (constrained_auto_context_map,
+			     TYPE_NAME (type), tparms);
+}
+
+/* Copies the constraints on the 'auto' placeholder type FROM over to TO.  */
+
+static void
+copy_placeholder_type_constraints (tree to, tree from)
+{
+  gcc_assert (is_auto (from));
+  if (tree constr = PLACEHOLDER_TYPE_CONSTRAINTS (from))
+    {
+      tree tparms = get_constrained_auto_context (from);
+      set_placeholder_type_constraints (to, constr, tparms);
+    }
+}
+
 /* Take the tree structure T and replace template parameters used
    therein with the argument vector ARGS.  IN_DECL is an associated
    decl for diagnostics.  If an error occurs, returns ERROR_MARK_NODE.
@@ -15723,8 +15763,8 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
 		  {
 		    /* Propagate constraints on placeholders since they are
 		       only instantiated during satisfaction.  */
-		    if (tree constr = PLACEHOLDER_TYPE_CONSTRAINTS (t))
-		      PLACEHOLDER_TYPE_CONSTRAINTS (r) = constr;
+		    if (PLACEHOLDER_TYPE_CONSTRAINTS (t))
+		      copy_placeholder_type_constraints (r, t);
 		    else if (tree pl = CLASS_PLACEHOLDER_TEMPLATE (t))
 		      {
 			pl = tsubst_copy (pl, args, complain, in_decl);
@@ -28145,7 +28185,7 @@ make_constrained_placeholder_type (tree type, tree con, tree args)
   expr = build_concept_check (expr, type, args, tf_warning_or_error);
   --processing_template_decl;
 
-  PLACEHOLDER_TYPE_CONSTRAINTS (type) = expr;
+  set_placeholder_type_constraints (type, expr, current_template_parms);
 
   /* Our canonical type depends on the constraint.  */
   TYPE_CANONICAL (type) = canonical_type_parameter (type);
@@ -29384,9 +29424,11 @@ do_class_deduction (tree ptype, tree tmpl, tree init,
    from INIT.  AUTO_NODE is the TEMPLATE_TYPE_PARM used for 'auto' in TYPE.
    The CONTEXT determines the context in which auto deduction is performed
    and is used to control error diagnostics.  FLAGS are the LOOKUP_* flags.
-   OUTER_TARGS are used during template argument deduction
-   (context == adc_unify) to properly substitute the result, and is ignored
-   in other contexts.
+
+   OUTER_TARGS is used during template argument deduction (context == adc_unify)
+   to properly substitute the result.  It's also used in the adc_unify and
+   adc_requirement contexts to communicate the the necessary template arguments
+   to satisfaction.  OUTER_TARGS is ignored in other contexts.
 
    For partial-concept-ids, extra args may be appended to the list of deduced
    template arguments prior to determining constraint satisfaction.  */
@@ -29547,30 +29589,21 @@ do_auto_deduction (tree type, tree init, tree auto_node,
     }
 
   /* Check any placeholder constraints against the deduced type. */
-  if (flag_concepts && !processing_template_decl)
-    if (tree check = NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
+  if (flag_concepts)
+    if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
       {
-        /* Use the deduced type to check the associated constraints. If we
-           have a partial-concept-id, rebuild the argument list so that
-           we check using the extra arguments. */
-	check = unpack_concept_check (check);
-	gcc_assert (TREE_CODE (check) == TEMPLATE_ID_EXPR);
-	tree cdecl = TREE_OPERAND (check, 0);
-	if (OVL_P (cdecl))
-	  cdecl = OVL_FIRST (cdecl);
-        tree cargs = TREE_OPERAND (check, 1);
-        if (TREE_VEC_LENGTH (cargs) > 1)
-          {
-            cargs = copy_node (cargs);
-            TREE_VEC_ELT (cargs, 0) = TREE_VEC_ELT (targs, 0);
-          }
-        else
-          cargs = targs;
+	if (processing_template_decl)
+	  /* In general we can't check satisfaction until we know all
+	     template arguments.  */
+	  return type;
 
-	/* Rebuild the check using the deduced arguments.  */
-	check = build_concept_check (cdecl, cargs, tf_none);
+	if ((context == adc_return_type || context == adc_variable_type)
+	    && current_function_decl
+	    && DECL_TEMPLATE_INFO (current_function_decl))
+	  outer_targs = DECL_TI_ARGS (current_function_decl);
 
-	if (!constraints_satisfied_p (check))
+	tree complete_targs = add_to_template_args (outer_targs, targs);
+	if (!constraints_satisfied_p (auto_node, complete_targs))
           {
             if (complain & tf_warning_or_error)
               {
@@ -29595,15 +29628,18 @@ do_auto_deduction (tree type, tree init, tree auto_node,
                            "placeholder constraints");
                     break;
                   }
-		diagnose_constraints (input_location, check, targs);
+		diagnose_constraints (input_location, auto_node, complete_targs);
               }
             return error_mark_node;
           }
       }
 
-  if (processing_template_decl && context != adc_unify)
-    outer_targs = current_template_args ();
-  targs = add_to_template_args (outer_targs, targs);
+  if (context == adc_unify)
+    targs = add_to_template_args (outer_targs, targs);
+  else if (processing_template_decl)
+    targs = add_to_template_args (current_template_args (), targs);
+  else
+    /* Outer templates arguments should have already been substituted in.  */;
   return tsubst (type, targs, complain, NULL_TREE);
 }
 
@@ -29628,8 +29664,7 @@ splice_late_return_type (tree type, tree late_return_type)
 	     with a function template when we saw the auto return type, so update
 	     it to have the correct level.  */
 	  tree new_auto = make_auto_1 (TYPE_IDENTIFIER (*auto_node), false);
-	  PLACEHOLDER_TYPE_CONSTRAINTS (new_auto)
-	    = PLACEHOLDER_TYPE_CONSTRAINTS (*auto_node);
+	  copy_placeholder_type_constraints (new_auto, *auto_node);
 	  TYPE_CANONICAL (new_auto) = canonical_type_parameter (new_auto);
 	  new_auto = cp_build_qualified_type (new_auto, TYPE_QUALS (*auto_node));
 	  *auto_node = new_auto;
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder3.C b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder3.C
new file mode 100644
index 00000000000..8bfc2db3837
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder3.C
@@ -0,0 +1,19 @@
+// PR c++/96443
+// { dg-do compile { target c++20 } }
+
+template <class T, class U> concept same_as = __is_same(T, U);
+
+auto f(auto x) -> same_as<decltype(x)> auto { return 0; }; // { dg-error "constraints" }
+void g(auto x) { same_as<decltype(x)> auto y = 0; } // { dg-error "constraints" }
+auto h(auto x) -> same_as<decltype(x.missing)> auto { return 0; } // { dg-error "constraints|missing" }
+template <typename T, same_as<T> auto N> void i() {}
+
+int main() {
+  f(0); // { dg-bogus "" }
+  f(true); // { dg-message "required from here" }
+  g(0); // { dg-bogus "" }
+  g(true); // { dg-message "required from here" }
+  h(0); // { dg-message "required from here" }
+  i<int, 0>(); // { dg-bogus "" }
+  i<int, true>(); // { dg-error "no match|constraints" }
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-return-req2.C b/gcc/testsuite/g++.dg/cpp2a/concepts-return-req2.C
new file mode 100644
index 00000000000..26872ffc3d0
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-return-req2.C
@@ -0,0 +1,13 @@
+// Verify we check return-type-requirements by passing the entire set of
+// template arguments to normalization rather than first substituting into
+// the constraint.  The former approach would induce a substitution failure and
+// cause the requires-expression to evaluate to false here.
+// { dg-do compile { target c++20 } }
+
+template <class, class>
+concept C1 = true;
+
+template <class T>
+concept C2 = requires { { 0 } -> C1<typename T::type>; };
+
+static_assert(C2<int>);
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C b/gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C
index 1cefe3b243f..a116cac4ea4 100644
--- a/gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C
@@ -40,7 +40,7 @@ void driver()
   f3('a'); // { dg-error "" }
   f4(0, 0);
   f4(0, 'a'); // { dg-error "" }
-  f15(0);
+  f15(0); // { dg-bogus "" }
   f15('a'); // { dg-message "" }
 }
 
-- 
2.30.0.452.gfb7fa4a1fd


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 4/4] c++: dependent constraint on placeholder 'auto' [PR96443]
  2021-02-08 19:03 ` [PATCH 4/4] c++: dependent constraint on placeholder 'auto' [PR96443] Patrick Palka
@ 2021-02-08 19:39   ` Patrick Palka
  2021-02-11 16:19   ` Jason Merrill
  1 sibling, 0 replies; 29+ messages in thread
From: Patrick Palka @ 2021-02-08 19:39 UTC (permalink / raw)
  To: Patrick Palka; +Cc: gcc-patches, jason

On Mon, 8 Feb 2021, Patrick Palka wrote:

> This fixes the way we check satisfaction of constraints on placeholder
> types in various contexts, and in particular when the constraint is
> dependent.
> 
> Firstly, when evaluating the return type requirement of a compound
> requirement, we currently substitute the outer template arguments into
> the constraint before checking satisfaction. But we should instead be
> passing in the complete set of template arguments to satisfaction and
> not do a prior separate substitution.  Our current approach leads to us
> incorrectly rejecting the testcase concepts-return-req2.C below.
> 
> Secondly, when checking the constraints on a placeholder variable or
> return type, we don't substitute the template arguments of the enclosing
> context at all.  This leads to bogus errors during satisfaction when the
> constraint is dependent as in the testcase concepts-placeholder3.C
> below.
> 
> In order to fix these two issues, we need to be able to properly
> normalize the constraints on a placeholder 'auto', which in turn
> requires us to know the template parameters that were in-scope where an
> 'auto' was introduced.  This information currently doesn't seem to be
> easily available when we need it, so this patch adds an auxiliary hash
> table that keeps track of the value of current_template_parms when each
> constrained 'auto' was formed.
> 
> This patch also removes some seemingly wrong handling of placeholder
> type arguments from tsubst_parameter_mapping.  The code doesn't trigger
> with the example used in the comments, because type_uses_auto doesn't
> look inside non-deduced contexts such as the operand of decltype.  And
> the call to do_auto_deduction seems confused because if 'arg' is a type,
> then so is 'parm', and therefore 'init' too is a type, but
> do_auto_deduction expects it to be an expression.  Before this patch,
> this code was dead (as far as our testsuite can tell), but now it breaks
> other parts of this patch, so let's remove it.

This series was bootstrapped and regtested on x86_64-pc-linux-gnu and
tested on range-v3 and cmcstl2, with and without --enable-checking=release.


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/4] c++: Avoid building garbage trees from tsubst_requires_expr
  2021-02-08 19:03 [PATCH 1/4] c++: Avoid building garbage trees from tsubst_requires_expr Patrick Palka
                   ` (2 preceding siblings ...)
  2021-02-08 19:03 ` [PATCH 4/4] c++: dependent constraint on placeholder 'auto' [PR96443] Patrick Palka
@ 2021-02-09  5:09 ` Jason Merrill
  2021-02-28 17:58 ` [PATCH 5/6] c++: Clean up normalization / satisfaction routines Patrick Palka
  2021-02-28 17:59 ` [PATCH 6/6] c++: Consolidate REQUIRES_EXPR evaluation/diagnostic routines Patrick Palka
  5 siblings, 0 replies; 29+ messages in thread
From: Jason Merrill @ 2021-02-09  5:09 UTC (permalink / raw)
  To: Patrick Palka, gcc-patches

On 2/8/21 2:03 PM, Patrick Palka wrote:
> Since we no longer partially instantiate REQUIRES_EXPRs, we don't need
> to rebuild its requirements during tsubst_requires_expr.

OK.

> gcc/cp/ChangeLog:
> 
> 	* constraint.cc (tsubst_simple_requirement): Just return
> 	boolean_true_node on success.
> 	(tsubst_type_requirement): Likewise.
> 	(tsubst_compound_requirement): Likewise.
> 	(tsubst_nested_requirement): Likewise.
> 	(tsubst_requirement_body): Remove.
> 	(check_constaint_variables): Rename to ...
> 	(check_constraint_variables): ... this.
> 	(tsubst_constraint_variables): Adjust.
> 	(tsubst_requires_expr): Fold tsubst_requirement_body into here.
> ---
>   gcc/cp/constraint.cc | 46 ++++++++++++++------------------------------
>   1 file changed, 14 insertions(+), 32 deletions(-)
> 
> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> index 31e0fb5079a..3e599fe8c47 100644
> --- a/gcc/cp/constraint.cc
> +++ b/gcc/cp/constraint.cc
> @@ -1962,7 +1962,7 @@ tsubst_simple_requirement (tree t, tree args, subst_info info)
>     tree expr = tsubst_valid_expression_requirement (t0, args, info);
>     if (expr == error_mark_node)
>       return error_mark_node;
> -  return finish_simple_requirement (EXPR_LOCATION (t), expr);
> +  return boolean_true_node;
>   }
>   
>   /* Substitute through the type requirement.  */
> @@ -1974,7 +1974,7 @@ tsubst_type_requirement (tree t, tree args, subst_info info)
>     tree type = tsubst (t0, args, info.complain, info.in_decl);
>     if (type == error_mark_node)
>       return error_mark_node;
> -  return finish_type_requirement (EXPR_LOCATION (t), type);
> +  return boolean_true_node;
>   }
>   
>   /* True if TYPE can be deduced from EXPR.  */
> @@ -2080,8 +2080,7 @@ tsubst_compound_requirement (tree t, tree args, subst_info info)
>   	return error_mark_node;
>       }
>   
> -  return finish_compound_requirement (EXPR_LOCATION (t),
> -				      expr, type, noexcept_p);
> +  return boolean_true_node;
>   }
>   
>   static tree
> @@ -2100,7 +2099,7 @@ tsubst_nested_requirement (tree t, tree args, subst_info info)
>       }
>     if (result != boolean_true_node)
>       return error_mark_node;
> -  return result;
> +  return boolean_true_node;
>   }
>   
>   /* Substitute ARGS into the requirement T.  */
> @@ -2125,24 +2124,6 @@ tsubst_requirement (tree t, tree args, subst_info info)
>     gcc_unreachable ();
>   }
>   
> -/* Substitute ARGS into the list of requirements T. Note that
> -   substitution failures here result in ill-formed programs. */
> -
> -static tree
> -tsubst_requirement_body (tree t, tree args, subst_info info)
> -{
> -  tree result = NULL_TREE;
> -  while (t)
> -    {
> -      tree req = tsubst_requirement (TREE_VALUE (t), args, info);
> -      if (req == error_mark_node)
> -	return error_mark_node;
> -      result = tree_cons (NULL_TREE, req, result);
> -      t = TREE_CHAIN (t);
> -    }
> -  return nreverse (result);
> -}
> -
>   static tree
>   declare_constraint_vars (tree parms, tree vars)
>   {
> @@ -2168,7 +2149,7 @@ declare_constraint_vars (tree parms, tree vars)
>      if an error occurred.  */
>   
>   static tree
> -check_constaint_variables (tree t, tree args, subst_info info)
> +check_constraint_variables (tree t, tree args, subst_info info)
>   {
>     tree types = NULL_TREE;
>     tree p = t;
> @@ -2193,7 +2174,7 @@ static tree
>   tsubst_constraint_variables (tree t, tree args, subst_info info)
>   {
>     /* Perform a trial substitution to check for type errors.  */
> -  tree parms = check_constaint_variables (t, args, info);
> +  tree parms = check_constraint_variables (t, args, info);
>     if (parms == error_mark_node)
>       return error_mark_node;
>   
> @@ -2253,19 +2234,20 @@ tsubst_requires_expr (tree t, tree args,
>         return t;
>       }
>   
> -  tree parms = REQUIRES_EXPR_PARMS (t);
> -  if (parms)
> +  if (tree parms = REQUIRES_EXPR_PARMS (t))
>       {
>         parms = tsubst_constraint_variables (parms, args, info);
>         if (parms == error_mark_node)
>   	return boolean_false_node;
>       }
>   
> -  tree reqs = REQUIRES_EXPR_REQS (t);
> -  reqs = tsubst_requirement_body (reqs, args, info);
> -  if (reqs == error_mark_node)
> -    return boolean_false_node;
> -
> +  for (tree reqs = REQUIRES_EXPR_REQS (t); reqs; reqs = TREE_CHAIN (reqs))
> +    {
> +      tree req = TREE_VALUE (reqs);
> +      tree result = tsubst_requirement (req, args, info);
> +      if (result == error_mark_node)
> +	return boolean_false_node;
> +    }
>     return boolean_true_node;
>   }
>   
> 


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 2/4] c++: Preparatory type canonicalization fixes
  2021-02-08 19:03 ` [PATCH 2/4] c++: Preparatory type canonicalization fixes Patrick Palka
@ 2021-02-09  5:11   ` Jason Merrill
  0 siblings, 0 replies; 29+ messages in thread
From: Jason Merrill @ 2021-02-09  5:11 UTC (permalink / raw)
  To: Patrick Palka, gcc-patches

On 2/8/21 2:03 PM, Patrick Palka wrote:
> The subsequent patches revealed some latent type canonicalization
> issues during normalization and satisfaction:
> 
> 1. In tsubst_parameter_mapping, we're canonicalizing the arguments of a
>     TYPE_ARGUMENT_PACK only if 'arg' wasn't a TYPE_ARGUMENT_PACK to begin
>     with.
> 
> 2. We currently set DECL_CONTEXT and CONSTRAINT_VAR_P on each of the
>     parameters introduced in a requires-expression _after_ we're done
>     processing the requirements.  But meanwhile we may have already
>     computed the canonical form of a type that depends on one of these
>     PARM_DECLs, which depends on the result of cp_tree_equal, which
>     depends on CONSTRAINT_VAR_P and DECL_CONTEXT.  So we must set these
>     fields earlier, before processing the requirements.
> 
> 3. In do_auto_deduction, we use the result of finish_decltype_type later
>     as a template argument, so we should canonicalize the result too.
>     (While we're here, we should pass 'complain' to finish_decltype_type,
>     which fixes the testcase auto1.C below.)
> 
> gcc/cp/ChangeLog:
> 
> 	* constraint.cc (tsubst_parameter_mapping): Canonicalize the
> 	arguments of a TYPE_ARGUMENT_PACK even if we've started with a
> 	TYPE_ARGUMENT_PACK.
> 	(finish_requires_expr): Don't set DECL_CONTEXT and
> 	CONSTRAINT_VAR_P on each of the introduced parameters here.
> 	* parser.c (cp_parser_requirement_parameter_list): Instead set
> 	these fields earlier, here.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/cpp1z/auto1.C: New test.
> ---
>   gcc/cp/constraint.cc               | 25 ++++++++-----------------
>   gcc/cp/parser.c                    | 12 ++++++++++++
>   gcc/cp/pt.c                        |  8 ++++++--
>   gcc/testsuite/g++.dg/cpp1z/auto1.C | 13 +++++++++++++
>   4 files changed, 39 insertions(+), 19 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/cpp1z/auto1.C
> 
> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> index 3e599fe8c47..39c97986082 100644
> --- a/gcc/cp/constraint.cc
> +++ b/gcc/cp/constraint.cc
> @@ -2319,15 +2319,15 @@ tsubst_parameter_mapping (tree map, tree args, subst_info info)
>   	  new_arg = tsubst_template_arg (arg, args, complain, in_decl);
>   	  if (TYPE_P (new_arg))
>   	    new_arg = canonicalize_type_argument (new_arg, complain);
> -	  if (TREE_CODE (new_arg) == TYPE_ARGUMENT_PACK)
> +	}
> +      if (TREE_CODE (new_arg) == TYPE_ARGUMENT_PACK)
> +	{
> +	  tree pack_args = ARGUMENT_PACK_ARGS (new_arg);
> +	  for (int i = 0; i < TREE_VEC_LENGTH (pack_args); i++)
>   	    {
> -	      tree pack_args = ARGUMENT_PACK_ARGS (new_arg);
> -	      for (int i = 0; i < TREE_VEC_LENGTH (pack_args); i++)
> -		{
> -		  tree& pack_arg = TREE_VEC_ELT (pack_args, i);
> -		  if (TYPE_P (pack_arg))
> -		    pack_arg = canonicalize_type_argument (pack_arg, complain);
> -		}
> +	      tree& pack_arg = TREE_VEC_ELT (pack_args, i);
> +	      if (TYPE_P (pack_arg))
> +		pack_arg = canonicalize_type_argument (pack_arg, complain);
>   	    }
>   	}
>         if (new_arg == error_mark_node)
> @@ -3253,15 +3253,6 @@ evaluate_concept_check (tree check, tsubst_flags_t complain)
>   tree
>   finish_requires_expr (location_t loc, tree parms, tree reqs)
>   {
> -  /* Modify the declared parameters by removing their context
> -     so they don't refer to the enclosing scope and explicitly
> -     indicating that they are constraint variables. */
> -  for (tree parm = parms; parm; parm = DECL_CHAIN (parm))
> -    {
> -      DECL_CONTEXT (parm) = NULL_TREE;
> -      CONSTRAINT_VAR_P (parm) = true;
> -    }
> -
>     /* Build the node. */
>     tree r = build_min (REQUIRES_EXPR, boolean_type_node, parms, reqs, NULL_TREE);
>     TREE_SIDE_EFFECTS (r) = false;
> diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c
> index 5da8670f0e2..fef10b9661d 100644
> --- a/gcc/cp/parser.c
> +++ b/gcc/cp/parser.c
> @@ -28779,6 +28779,18 @@ cp_parser_requirement_parameter_list (cp_parser *parser)
>     if (!parens.require_close (parser))
>       return error_mark_node;
>   
> +  /* Modify the declared parameters by removing their context
> +     so they don't refer to the enclosing scope and explicitly
> +     indicating that they are constraint variables. */
> +  for (tree parm = parms; parm; parm = TREE_CHAIN (parm))
> +    {
> +      if (parm == void_list_node || parm == explicit_void_list_node)
> +	break;
> +      tree decl = TREE_VALUE (parm);
> +      DECL_CONTEXT (decl) = NULL_TREE;
> +      CONSTRAINT_VAR_P (decl) = true;
> +    }
> +
>     return parms;
>   }
>   
> diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
> index 3605b67e424..bceb942e79a 100644
> --- a/gcc/cp/pt.c
> +++ b/gcc/cp/pt.c
> @@ -29478,9 +29478,13 @@ do_auto_deduction (tree type, tree init, tree auto_node,
>   		 || ((TREE_CODE (init) == COMPONENT_REF
>   		      || TREE_CODE (init) == SCOPE_REF)
>   		     && !REF_PARENTHESIZED_P (init)));
> +      tree deduced = finish_decltype_type (init, id, complain);
> +      deduced = canonicalize_type_argument (deduced, complain);
> +      if (deduced == error_mark_node)
> +	return error_mark_node;
>         targs = make_tree_vec (1);
> -      TREE_VEC_ELT (targs, 0)
> -	= finish_decltype_type (init, id, tf_warning_or_error);
> +      TREE_VEC_ELT (targs, 0) = deduced;
> +      /* FIXME: These errors ought to be diagnosed at parse time. */

Perhaps in grokdeclarator.

The patch is OK.

>         if (type != auto_node)
>   	{
>             if (complain & tf_error)
> diff --git a/gcc/testsuite/g++.dg/cpp1z/auto1.C b/gcc/testsuite/g++.dg/cpp1z/auto1.C
> new file mode 100644
> index 00000000000..5cc762a386e
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp1z/auto1.C
> @@ -0,0 +1,13 @@
> +// Verify that deduction failure of the decltype(auto) template parameter is
> +// a SFINAE error.
> +// { dg-do compile { target c++17 } }
> +
> +template <class> void f();
> +template <class> void f(int);
> +
> +template <class T = int, decltype(auto) = &f<T>> void g();
> +template <class = int> void g();
> +
> +int main() {
> +  g();
> +}
> 


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 3/4] c++: Delay normalizing nested requirements until satisfaction
  2021-02-08 19:03 ` [PATCH 3/4] c++: Delay normalizing nested requirements until satisfaction Patrick Palka
@ 2021-02-09 23:04   ` Jason Merrill
  2021-02-10  0:52     ` Jason Merrill
  2021-02-10 14:41     ` Patrick Palka
  0 siblings, 2 replies; 29+ messages in thread
From: Jason Merrill @ 2021-02-09 23:04 UTC (permalink / raw)
  To: Patrick Palka, gcc-patches

On 2/8/21 2:03 PM, Patrick Palka wrote:
> This sets up the functionality for controlling the initial set of
> template parameters to pass to normalization when dealing with a
> constraint-expression that is not associated with some constrained
> declaration, for instance when normalizing a nested requirement of a
> requires expression, or the constraints on a placeholder type.
> 
> The main new ingredient here is the data member norm_info::initial_parms
> which can be set by callers of the normalization routines to communicate
> the in-scope template parameters for the supplied constraint-expression,
> rather than always falling back to using current_template_parms.
> 
> This patch then uses this functionality in our handling of nested
> requirements so that we can delay normalizing them until needed for
> satisfaction.  We currently immediately normalize nested requirements at
> parse time, where we have the necessary template context, and cache the
> normal form in their TREE_TYPE node.  With this patch, we now delay
> normalization until needed (as with other constraint expressions), and
> instead store the current value of current_template_parms in their
> TREE_TYPE node (which we use to restore the template context at
> normalization time).
> 
> In the subsequent patch, this functionality will also be used to
> normalize placeholder type constraints during auto deduction.
> 
> gcc/cp/ChangeLog:
> 
> 	* constraint.cc (build_parameter_mapping): Rely on the caller to
> 	determine the in-scope template parameters.
> 	(norm_info::norm_info): Delegate the one-parameter constructor
> 	to the two-parameter constructor.  In the two-parameter
> 	constructor, fold in the definition of make_context, set
> 	initial_parms appropriately, and don't set the now-removed
> 	orig_decl member.
> 	(norm_info::make_context): Remove, now that its only use is
> 	inlined into the caller.
> 	(norm_info::update_context): Adjust call to
> 	build_parameter_mapping to pass in the relevant set of in-scope
> 	template parameters.
> 	(norm_info::ctx_parms): Define this member function.
> 	(norm_info::context): Initialize to NULL_TREE.
> 	(norm_info::orig_decl): Remove this data member.
> 	(norm_info::initial_parms): Define this data member.
> 	(normalize_atom): Adjust call to build_parameter_mapping to pass
> 	in the relevant set of in-scope template parameters.  Use
> 	info.initial_parms instead of info.orig_decl.
> 	(normalize_constraint_expression): Define an overload that takes
> 	a norm_info object.  Cache the result of normalization.  Define
> 	the other overload in terms of this one, and handle a NESTED_REQ
> 	argument by setting info.initial_parms appropriately.
> 	(tsubst_nested_requirement): Go through
> 	satisfy_constraint_expression so that we normalize on demand.
> 	(finish_nested_requirement): Set the TREE_TYPE of the NESTED_REQ
> 	to current_template_parms.
> 	(diagnose_nested_requirements): Go through
> 	satisfy_constraint_expression, as with tsubst_nested_requirement.
> ---
>   gcc/cp/constraint.cc | 140 +++++++++++++++++++++++--------------------
>   gcc/cp/cp-tree.h     |   4 +-
>   2 files changed, 78 insertions(+), 66 deletions(-)
> 
> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> index 39c97986082..56134f8b2bf 100644
> --- a/gcc/cp/constraint.cc
> +++ b/gcc/cp/constraint.cc
> @@ -133,7 +133,7 @@ struct sat_info : subst_info
>     bool diagnose_unsatisfaction;
>   };
>   
> -static tree satisfy_constraint (tree, tree, sat_info);
> +static tree satisfy_constraint_expression (tree, tree, sat_info);
>   
>   /* True if T is known to be some type other than bool. Note that this
>      is false for dependent types and errors.  */
> @@ -594,26 +594,12 @@ map_arguments (tree parms, tree args)
>     return parms;
>   }
>   
> -/* Build the parameter mapping for EXPR using ARGS.  */
> +/* Build the parameter mapping for EXPR using ARGS, where CTX_PARMS
> +   are the template parameters in scope for EXPR.  */
>   
>   static tree
> -build_parameter_mapping (tree expr, tree args, tree decl)
> +build_parameter_mapping (tree expr, tree args, tree ctx_parms)
>   {
> -  tree ctx_parms = NULL_TREE;
> -  if (decl)
> -    {
> -      gcc_assert (TREE_CODE (decl) == TEMPLATE_DECL);
> -      ctx_parms = DECL_TEMPLATE_PARMS (decl);
> -    }
> -  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.  */
> -      ctx_parms = current_template_parms;
> -    }
> -
>     tree parms = find_template_parameters (expr, ctx_parms);
>     tree map = map_arguments (parms, args);
>     return map;
> @@ -645,53 +631,63 @@ parameter_mapping_equivalent_p (tree t1, tree t2)
>   
>   struct norm_info : subst_info
>   {
> -  explicit norm_info (tsubst_flags_t complain)
> -    : subst_info (tf_warning_or_error | complain, NULL_TREE),
> -      context()
> +  explicit norm_info (tsubst_flags_t cmp)
> +    : norm_info (NULL_TREE, cmp)
>     {}
>   
>     /* Construct a top-level context for DECL.  */
>   
>     norm_info (tree in_decl, tsubst_flags_t complain)
> -    : subst_info (tf_warning_or_error | complain, in_decl),
> -      context (make_context (in_decl)),
> -      orig_decl (in_decl)
> -  {}
> -
> -  bool generate_diagnostics() const
> +    : subst_info (tf_warning_or_error | complain, in_decl)
>     {
> -    return complain & tf_norm;
> +    if (in_decl)
> +      {
> +	initial_parms = DECL_TEMPLATE_PARMS (in_decl);
> +	if (generate_diagnostics ())
> +	  context = build_tree_list (NULL_TREE, in_decl);
> +      }
> +    else
> +      initial_parms = current_template_parms;
>     }
>   
> -  tree make_context(tree in_decl)
> +  bool generate_diagnostics() const
>     {
> -    if (generate_diagnostics ())
> -      return build_tree_list (NULL_TREE, in_decl);
> -    return NULL_TREE;
> +    return complain & tf_norm;
>     }
>   
>     void update_context(tree expr, tree args)
>     {
>       if (generate_diagnostics ())
>         {
> -	tree map = build_parameter_mapping (expr, args, in_decl);
> +	tree map = build_parameter_mapping (expr, args, ctx_parms ());
>   	context = tree_cons (map, expr, context);
>         }
>       in_decl = get_concept_check_template (expr);
>     }
>   
> +  /* Returns the template parameters that are in scope for the current
> +     normalization context.  */
> +
> +  tree ctx_parms()
> +  {
> +    if (in_decl)
> +      return DECL_TEMPLATE_PARMS (in_decl);
> +    else
> +      return initial_parms;
> +  }
> +
>     /* Provides information about the source of a constraint. This is a
>        TREE_LIST whose VALUE is either a concept check or a constrained
>        declaration. The PURPOSE, for concept checks is a parameter mapping
>        for that check.  */
>   
> -  tree context;
> +  tree context = NULL_TREE;
>   
>     /* The declaration whose constraints we're normalizing.  The targets
>        of the parameter mapping of each atom will be in terms of the
>        template parameters of ORIG_DECL.  */
>   
> -  tree orig_decl = NULL_TREE;
> +  tree initial_parms = NULL_TREE;
>   };
>   
>   static tree normalize_expression (tree, tree, norm_info);
> @@ -773,7 +769,7 @@ normalize_atom (tree t, tree args, norm_info info)
>       return normalize_concept_check (t, args, info);
>   
>     /* Build the parameter mapping for the atom.  */
> -  tree map = build_parameter_mapping (t, args, info.in_decl);
> +  tree map = build_parameter_mapping (t, args, info.ctx_parms ());
>   
>     /* Build a new info object for the atom.  */
>     tree ci = build_tree_list (t, info.context);
> @@ -803,10 +799,8 @@ normalize_atom (tree t, tree args, norm_info info)
>   	      tree target = TREE_PURPOSE (node);
>   	      TREE_VEC_ELT (targets, i++) = target;
>   	    }
> -	  tree ctx_parms = (info.orig_decl
> -			    ? DECL_TEMPLATE_PARMS (info.orig_decl)
> -			    : current_template_parms);
> -	  tree target_parms = find_template_parameters (targets, ctx_parms);
> +	  tree target_parms = find_template_parameters (targets,
> +							info.initial_parms);
>   	  TREE_TYPE (map) = target_parms;
>   	}
>   
> @@ -983,17 +977,43 @@ normalize_nontemplate_requirements (tree decl, bool diag = false)
>   /* Normalize an EXPR as a constraint.  */
>   
>   static tree
> -normalize_constraint_expression (tree expr, bool diag)
> +normalize_constraint_expression (tree expr, norm_info info)
>   {
>     if (!expr || expr == error_mark_node)
>       return expr;
> +
> +  if (!info.generate_diagnostics ())
> +    if (tree *p = hash_map_safe_get (normalized_map, expr))
> +      return *p;

It seems like we only want this for NESTED_REQ.

>     ++processing_template_decl;
> -  norm_info info (diag ? tf_norm : tf_none);
>     tree norm = get_normalized_constraints (expr, info);
>     --processing_template_decl;
> +
> +  if (!info.generate_diagnostics ())
> +    hash_map_safe_put<hm_ggc> (normalized_map, expr, norm);
> +
>     return norm;
>   }
>   
> +/* High-level wrapper for the above.  */
> +
> +static tree
> +normalize_constraint_expression (tree expr, bool diag)
> +{
> +  norm_info info (diag ? tf_norm : tf_none);

I wonder if we want to add a norm_info constructor taking a sat_info so 
we don't need to mediate passing from one into the other with bool 
"diag" parameters in various functions.  That doesn't need to happen in 
this patch.

> +  if (TREE_CODE (expr) == NESTED_REQ)
> +    {
> +      /* The TREE_TYPE contains the set of template parameters that were
> +	 in scope for this nested requiremen; use them as the initial template
> +	 parameters for normalization.  */
> +      info.initial_parms = TREE_TYPE (expr);
> +      return normalize_constraint_expression (TREE_OPERAND (expr, 0), info);
> +    }
> +  else
> +    return normalize_constraint_expression (expr, info);
> +}

It seems like you don't want any other code to call the first overload, 
so let's give the first one a different name to make that clearer.  The 
differences in functionality don't follow naturally from the parameter 
types.

Maybe this year we can cull/refactor the confusing set of overlapping 
and ambiguously named functions that the concepts code already has:

satisfy_constraint
satisfy_associated_constraints
satisfy_constraint_expression x2
constraint_satisfaction_value x2
constraints_satisfied_p x2
satisfy_declaration_constraints x2
evaluate_concept_check

>   /* 17.4.1.2p2. Two constraints are identical if they are formed
>      from the same expression and the targets of the parameter mapping
>      are equivalent.  */
> @@ -2086,16 +2106,14 @@ tsubst_compound_requirement (tree t, tree args, subst_info info)
>   static tree
>   tsubst_nested_requirement (tree t, tree args, subst_info info)
>   {
> -  /* Perform satisfaction quietly with the regular normal form.  */
> +  /* Perform satisfaction quietly first.  */
>     sat_info quiet (tf_none, info.in_decl);
> -  tree norm = TREE_VALUE (TREE_TYPE (t));
> -  tree diag_norm = TREE_PURPOSE (TREE_TYPE (t));
> -  tree result = satisfy_constraint (norm, args, quiet);
> +  tree result = satisfy_constraint_expression (t, args, quiet);
>     if (result == error_mark_node)
>       {
> -      /* Replay the error using the diagnostic normal form.  */
> +      /* Replay the error.  */
>         sat_info noisy (tf_warning_or_error, info.in_decl);
> -      satisfy_constraint (diag_norm, args, noisy);
> +      satisfy_constraint_expression (t, args, noisy);
>       }
>     if (result != boolean_true_node)
>       return error_mark_node;
> @@ -3301,15 +3319,9 @@ finish_compound_requirement (location_t loc, tree expr, tree type, bool noexcept
>   tree
>   finish_nested_requirement (location_t loc, tree expr)
>   {
> -  /* We need to normalize the constraints now, at parse time, while
> -     we have the necessary template context.  We normalize twice,
> -     once without diagnostic information and once with, which we'll
> -     later use for quiet and noisy satisfaction respectively.  */
> -  tree norm = normalize_constraint_expression (expr, /*diag=*/false);
> -  tree diag_norm = normalize_constraint_expression (expr, /*diag=*/true);
> -
> -  /* Build the constraint, saving its two normalizations as its type.  */
> -  tree r = build1 (NESTED_REQ, build_tree_list (diag_norm, norm), expr);
> +  /* Build the requirement, saving the set of in-scope template
> +     parameters as its type.  */
> +  tree r = build1 (NESTED_REQ, current_template_parms, expr);
>     SET_EXPR_LOCATION (r, loc);
>     return r;
>   }
> @@ -3710,12 +3722,9 @@ diagnose_type_requirement (tree req, tree args, tree in_decl)
>   static void
>   diagnose_nested_requirement (tree req, tree args)
>   {
> -  /* Quietly check for satisfaction first using the regular normal form.
> -     We can elaborate details later if needed.  */
> -  tree norm = TREE_VALUE (TREE_TYPE (req));
> -  tree diag_norm = TREE_PURPOSE (TREE_TYPE (req));
> -  sat_info info (tf_none, NULL_TREE);
> -  tree result = satisfy_constraint (norm, args, info);
> +  /* Quietly check for satisfaction first.  */
> +  sat_info quiet (tf_none, NULL_TREE);
> +  tree result = satisfy_constraint_expression (req, args, quiet);
>     if (result == boolean_true_node)
>       return;
>   
> @@ -3723,10 +3732,11 @@ diagnose_nested_requirement (tree req, tree args)
>     location_t loc = cp_expr_location (expr);
>     if (diagnosing_failed_constraint::replay_errors_p ())
>       {
> -      /* Replay the substitution error using the diagnostic normal form.  */
> +      /* Replay the substitution error with re-normalized requirements.  */
>         inform (loc, "nested requirement %qE is not satisfied, because", expr);
> +
>         sat_info noisy (tf_warning_or_error, NULL_TREE, /*diag_unsat=*/true);
> -      satisfy_constraint (diag_norm, args, noisy);
> +      satisfy_constraint_expression (req, args, noisy);
>       }
>     else
>       inform (loc, "nested requirement %qE is not satisfied", expr);
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index 970ed5e77bb..26fbf1eb663 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -1587,7 +1587,9 @@ check_constraint_info (tree t)
>     TREE_LANG_FLAG_0 (TREE_CHECK (NODE, COMPOUND_REQ))
>   
>   /* The constraints on an 'auto' placeholder type, used in an argument deduction
> -   constraint.  */
> +   constraint.  This should usually be set via set_placeholder_type_constraints
> +   since we also need to record the relevant set of in-scope template parameters
> +   for later normalization. */
>   #define PLACEHOLDER_TYPE_CONSTRAINTS(NODE) \
>     DECL_SIZE_UNIT (TYPE_NAME (NODE))
>   
> 


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 3/4] c++: Delay normalizing nested requirements until satisfaction
  2021-02-09 23:04   ` Jason Merrill
@ 2021-02-10  0:52     ` Jason Merrill
  2021-02-10 14:41     ` Patrick Palka
  1 sibling, 0 replies; 29+ messages in thread
From: Jason Merrill @ 2021-02-10  0:52 UTC (permalink / raw)
  To: Patrick Palka, gcc-patches

On 2/9/21 6:04 PM, Jason Merrill wrote:
> On 2/8/21 2:03 PM, Patrick Palka wrote:
>> This sets up the functionality for controlling the initial set of
>> template parameters to pass to normalization when dealing with a
>> constraint-expression that is not associated with some constrained
>> declaration, for instance when normalizing a nested requirement of a
>> requires expression, or the constraints on a placeholder type.
>>
>> The main new ingredient here is the data member norm_info::initial_parms
>> which can be set by callers of the normalization routines to communicate
>> the in-scope template parameters for the supplied constraint-expression,
>> rather than always falling back to using current_template_parms.
>>
>> This patch then uses this functionality in our handling of nested
>> requirements so that we can delay normalizing them until needed for
>> satisfaction.  We currently immediately normalize nested requirements at
>> parse time, where we have the necessary template context, and cache the
>> normal form in their TREE_TYPE node.  With this patch, we now delay
>> normalization until needed (as with other constraint expressions), and
>> instead store the current value of current_template_parms in their
>> TREE_TYPE node (which we use to restore the template context at
>> normalization time).
>>
>> In the subsequent patch, this functionality will also be used to
>> normalize placeholder type constraints during auto deduction.
>>
>> gcc/cp/ChangeLog:
>>
>>     * constraint.cc (build_parameter_mapping): Rely on the caller to
>>     determine the in-scope template parameters.
>>     (norm_info::norm_info): Delegate the one-parameter constructor
>>     to the two-parameter constructor.  In the two-parameter
>>     constructor, fold in the definition of make_context, set
>>     initial_parms appropriately, and don't set the now-removed
>>     orig_decl member.
>>     (norm_info::make_context): Remove, now that its only use is
>>     inlined into the caller.
>>     (norm_info::update_context): Adjust call to
>>     build_parameter_mapping to pass in the relevant set of in-scope
>>     template parameters.
>>     (norm_info::ctx_parms): Define this member function.
>>     (norm_info::context): Initialize to NULL_TREE.
>>     (norm_info::orig_decl): Remove this data member.
>>     (norm_info::initial_parms): Define this data member.
>>     (normalize_atom): Adjust call to build_parameter_mapping to pass
>>     in the relevant set of in-scope template parameters.  Use
>>     info.initial_parms instead of info.orig_decl.
>>     (normalize_constraint_expression): Define an overload that takes
>>     a norm_info object.  Cache the result of normalization.  Define
>>     the other overload in terms of this one, and handle a NESTED_REQ
>>     argument by setting info.initial_parms appropriately.
>>     (tsubst_nested_requirement): Go through
>>     satisfy_constraint_expression so that we normalize on demand.
>>     (finish_nested_requirement): Set the TREE_TYPE of the NESTED_REQ
>>     to current_template_parms.
>>     (diagnose_nested_requirements): Go through
>>     satisfy_constraint_expression, as with tsubst_nested_requirement.
>> ---
>>   gcc/cp/constraint.cc | 140 +++++++++++++++++++++++--------------------
>>   gcc/cp/cp-tree.h     |   4 +-
>>   2 files changed, 78 insertions(+), 66 deletions(-)
>>
>> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
>> index 39c97986082..56134f8b2bf 100644
>> --- a/gcc/cp/constraint.cc
>> +++ b/gcc/cp/constraint.cc
>> @@ -133,7 +133,7 @@ struct sat_info : subst_info
>>     bool diagnose_unsatisfaction;
>>   };
>> -static tree satisfy_constraint (tree, tree, sat_info);
>> +static tree satisfy_constraint_expression (tree, tree, sat_info);
>>   /* True if T is known to be some type other than bool. Note that this
>>      is false for dependent types and errors.  */
>> @@ -594,26 +594,12 @@ map_arguments (tree parms, tree args)
>>     return parms;
>>   }
>> -/* Build the parameter mapping for EXPR using ARGS.  */
>> +/* Build the parameter mapping for EXPR using ARGS, where CTX_PARMS
>> +   are the template parameters in scope for EXPR.  */
>>   static tree
>> -build_parameter_mapping (tree expr, tree args, tree decl)
>> +build_parameter_mapping (tree expr, tree args, tree ctx_parms)
>>   {
>> -  tree ctx_parms = NULL_TREE;
>> -  if (decl)
>> -    {
>> -      gcc_assert (TREE_CODE (decl) == TEMPLATE_DECL);
>> -      ctx_parms = DECL_TEMPLATE_PARMS (decl);
>> -    }
>> -  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.  */
>> -      ctx_parms = current_template_parms;
>> -    }
>> -
>>     tree parms = find_template_parameters (expr, ctx_parms);
>>     tree map = map_arguments (parms, args);
>>     return map;
>> @@ -645,53 +631,63 @@ parameter_mapping_equivalent_p (tree t1, tree t2)
>>   struct norm_info : subst_info
>>   {
>> -  explicit norm_info (tsubst_flags_t complain)
>> -    : subst_info (tf_warning_or_error | complain, NULL_TREE),
>> -      context()
>> +  explicit norm_info (tsubst_flags_t cmp)
>> +    : norm_info (NULL_TREE, cmp)
>>     {}
>>     /* Construct a top-level context for DECL.  */
>>     norm_info (tree in_decl, tsubst_flags_t complain)
>> -    : subst_info (tf_warning_or_error | complain, in_decl),
>> -      context (make_context (in_decl)),
>> -      orig_decl (in_decl)
>> -  {}
>> -
>> -  bool generate_diagnostics() const
>> +    : subst_info (tf_warning_or_error | complain, in_decl)
>>     {
>> -    return complain & tf_norm;
>> +    if (in_decl)
>> +      {
>> +    initial_parms = DECL_TEMPLATE_PARMS (in_decl);
>> +    if (generate_diagnostics ())
>> +      context = build_tree_list (NULL_TREE, in_decl);
>> +      }
>> +    else
>> +      initial_parms = current_template_parms;
>>     }
>> -  tree make_context(tree in_decl)
>> +  bool generate_diagnostics() const
>>     {
>> -    if (generate_diagnostics ())
>> -      return build_tree_list (NULL_TREE, in_decl);
>> -    return NULL_TREE;
>> +    return complain & tf_norm;
>>     }
>>     void update_context(tree expr, tree args)
>>     {
>>       if (generate_diagnostics ())
>>         {
>> -    tree map = build_parameter_mapping (expr, args, in_decl);
>> +    tree map = build_parameter_mapping (expr, args, ctx_parms ());
>>       context = tree_cons (map, expr, context);
>>         }
>>       in_decl = get_concept_check_template (expr);
>>     }
>> +  /* Returns the template parameters that are in scope for the current
>> +     normalization context.  */
>> +
>> +  tree ctx_parms()
>> +  {
>> +    if (in_decl)
>> +      return DECL_TEMPLATE_PARMS (in_decl);
>> +    else
>> +      return initial_parms;
>> +  }
>> +
>>     /* Provides information about the source of a constraint. This is a
>>        TREE_LIST whose VALUE is either a concept check or a constrained
>>        declaration. The PURPOSE, for concept checks is a parameter 
>> mapping
>>        for that check.  */
>> -  tree context;
>> +  tree context = NULL_TREE;
>>     /* The declaration whose constraints we're normalizing.  The targets
>>        of the parameter mapping of each atom will be in terms of the
>>        template parameters of ORIG_DECL.  */
>> -  tree orig_decl = NULL_TREE;
>> +  tree initial_parms = NULL_TREE;
>>   };
>>   static tree normalize_expression (tree, tree, norm_info);
>> @@ -773,7 +769,7 @@ normalize_atom (tree t, tree args, norm_info info)
>>       return normalize_concept_check (t, args, info);
>>     /* Build the parameter mapping for the atom.  */
>> -  tree map = build_parameter_mapping (t, args, info.in_decl);
>> +  tree map = build_parameter_mapping (t, args, info.ctx_parms ());
>>     /* Build a new info object for the atom.  */
>>     tree ci = build_tree_list (t, info.context);
>> @@ -803,10 +799,8 @@ normalize_atom (tree t, tree args, norm_info info)
>>             tree target = TREE_PURPOSE (node);
>>             TREE_VEC_ELT (targets, i++) = target;
>>           }
>> -      tree ctx_parms = (info.orig_decl
>> -                ? DECL_TEMPLATE_PARMS (info.orig_decl)
>> -                : current_template_parms);
>> -      tree target_parms = find_template_parameters (targets, ctx_parms);
>> +      tree target_parms = find_template_parameters (targets,
>> +                            info.initial_parms);
>>         TREE_TYPE (map) = target_parms;
>>       }
>> @@ -983,17 +977,43 @@ normalize_nontemplate_requirements (tree decl, 
>> bool diag = false)
>>   /* Normalize an EXPR as a constraint.  */
>>   static tree
>> -normalize_constraint_expression (tree expr, bool diag)
>> +normalize_constraint_expression (tree expr, norm_info info)
>>   {
>>     if (!expr || expr == error_mark_node)
>>       return expr;
>> +
>> +  if (!info.generate_diagnostics ())
>> +    if (tree *p = hash_map_safe_get (normalized_map, expr))
>> +      return *p;
> 
> It seems like we only want this for NESTED_REQ.
> 
>>     ++processing_template_decl;
>> -  norm_info info (diag ? tf_norm : tf_none);
>>     tree norm = get_normalized_constraints (expr, info);
>>     --processing_template_decl;
>> +
>> +  if (!info.generate_diagnostics ())
>> +    hash_map_safe_put<hm_ggc> (normalized_map, expr, norm);
>> +
>>     return norm;
>>   }
>> +/* High-level wrapper for the above.  */
>> +
>> +static tree
>> +normalize_constraint_expression (tree expr, bool diag)
>> +{
>> +  norm_info info (diag ? tf_norm : tf_none);
> 
> I wonder if we want to add a norm_info constructor taking a sat_info so 
> we don't need to mediate passing from one into the other with bool 
> "diag" parameters in various functions.  That doesn't need to happen in 
> this patch.
> 
>> +  if (TREE_CODE (expr) == NESTED_REQ)
>> +    {
>> +      /* The TREE_TYPE contains the set of template parameters that were
>> +     in scope for this nested requiremen; use them as the initial 
>> template
>> +     parameters for normalization.  */
>> +      info.initial_parms = TREE_TYPE (expr);
>> +      return normalize_constraint_expression (TREE_OPERAND (expr, 0), 
>> info);
>> +    }
>> +  else
>> +    return normalize_constraint_expression (expr, info);
>> +}
> 
> It seems like you don't want any other code to call the first overload, 
> so let's give the first one a different name to make that clearer.  The 
> differences in functionality don't follow naturally from the parameter 
> types.

Clearly I should have looked at the last patch before commenting on this 
one, nevermind...

> Maybe this year we can cull/refactor the confusing set of overlapping 
> and ambiguously named functions that the concepts code already has:
> 
> satisfy_constraint
> satisfy_associated_constraints
> satisfy_constraint_expression x2
> constraint_satisfaction_value x2
> constraints_satisfied_p x2
> satisfy_declaration_constraints x2
> evaluate_concept_check
> 
>>   /* 17.4.1.2p2. Two constraints are identical if they are formed
>>      from the same expression and the targets of the parameter mapping
>>      are equivalent.  */
>> @@ -2086,16 +2106,14 @@ tsubst_compound_requirement (tree t, tree 
>> args, subst_info info)
>>   static tree
>>   tsubst_nested_requirement (tree t, tree args, subst_info info)
>>   {
>> -  /* Perform satisfaction quietly with the regular normal form.  */
>> +  /* Perform satisfaction quietly first.  */
>>     sat_info quiet (tf_none, info.in_decl);
>> -  tree norm = TREE_VALUE (TREE_TYPE (t));
>> -  tree diag_norm = TREE_PURPOSE (TREE_TYPE (t));
>> -  tree result = satisfy_constraint (norm, args, quiet);
>> +  tree result = satisfy_constraint_expression (t, args, quiet);
>>     if (result == error_mark_node)
>>       {
>> -      /* Replay the error using the diagnostic normal form.  */
>> +      /* Replay the error.  */
>>         sat_info noisy (tf_warning_or_error, info.in_decl);
>> -      satisfy_constraint (diag_norm, args, noisy);
>> +      satisfy_constraint_expression (t, args, noisy);
>>       }
>>     if (result != boolean_true_node)
>>       return error_mark_node;
>> @@ -3301,15 +3319,9 @@ finish_compound_requirement (location_t loc, 
>> tree expr, tree type, bool noexcept
>>   tree
>>   finish_nested_requirement (location_t loc, tree expr)
>>   {
>> -  /* We need to normalize the constraints now, at parse time, while
>> -     we have the necessary template context.  We normalize twice,
>> -     once without diagnostic information and once with, which we'll
>> -     later use for quiet and noisy satisfaction respectively.  */
>> -  tree norm = normalize_constraint_expression (expr, /*diag=*/false);
>> -  tree diag_norm = normalize_constraint_expression (expr, 
>> /*diag=*/true);
>> -
>> -  /* Build the constraint, saving its two normalizations as its 
>> type.  */
>> -  tree r = build1 (NESTED_REQ, build_tree_list (diag_norm, norm), expr);
>> +  /* Build the requirement, saving the set of in-scope template
>> +     parameters as its type.  */
>> +  tree r = build1 (NESTED_REQ, current_template_parms, expr);
>>     SET_EXPR_LOCATION (r, loc);
>>     return r;
>>   }
>> @@ -3710,12 +3722,9 @@ diagnose_type_requirement (tree req, tree args, 
>> tree in_decl)
>>   static void
>>   diagnose_nested_requirement (tree req, tree args)
>>   {
>> -  /* Quietly check for satisfaction first using the regular normal form.
>> -     We can elaborate details later if needed.  */
>> -  tree norm = TREE_VALUE (TREE_TYPE (req));
>> -  tree diag_norm = TREE_PURPOSE (TREE_TYPE (req));
>> -  sat_info info (tf_none, NULL_TREE);
>> -  tree result = satisfy_constraint (norm, args, info);
>> +  /* Quietly check for satisfaction first.  */
>> +  sat_info quiet (tf_none, NULL_TREE);
>> +  tree result = satisfy_constraint_expression (req, args, quiet);
>>     if (result == boolean_true_node)
>>       return;
>> @@ -3723,10 +3732,11 @@ diagnose_nested_requirement (tree req, tree args)
>>     location_t loc = cp_expr_location (expr);
>>     if (diagnosing_failed_constraint::replay_errors_p ())
>>       {
>> -      /* Replay the substitution error using the diagnostic normal 
>> form.  */
>> +      /* Replay the substitution error with re-normalized 
>> requirements.  */
>>         inform (loc, "nested requirement %qE is not satisfied, 
>> because", expr);
>> +
>>         sat_info noisy (tf_warning_or_error, NULL_TREE, 
>> /*diag_unsat=*/true);
>> -      satisfy_constraint (diag_norm, args, noisy);
>> +      satisfy_constraint_expression (req, args, noisy);
>>       }
>>     else
>>       inform (loc, "nested requirement %qE is not satisfied", expr);
>> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
>> index 970ed5e77bb..26fbf1eb663 100644
>> --- a/gcc/cp/cp-tree.h
>> +++ b/gcc/cp/cp-tree.h
>> @@ -1587,7 +1587,9 @@ check_constraint_info (tree t)
>>     TREE_LANG_FLAG_0 (TREE_CHECK (NODE, COMPOUND_REQ))
>>   /* The constraints on an 'auto' placeholder type, used in an 
>> argument deduction
>> -   constraint.  */
>> +   constraint.  This should usually be set via 
>> set_placeholder_type_constraints
>> +   since we also need to record the relevant set of in-scope template 
>> parameters
>> +   for later normalization. */
>>   #define PLACEHOLDER_TYPE_CONSTRAINTS(NODE) \
>>     DECL_SIZE_UNIT (TYPE_NAME (NODE))
>>
> 


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 3/4] c++: Delay normalizing nested requirements until satisfaction
  2021-02-09 23:04   ` Jason Merrill
  2021-02-10  0:52     ` Jason Merrill
@ 2021-02-10 14:41     ` Patrick Palka
  2021-02-12 19:11       ` Jason Merrill
  1 sibling, 1 reply; 29+ messages in thread
From: Patrick Palka @ 2021-02-10 14:41 UTC (permalink / raw)
  To: Jason Merrill; +Cc: Patrick Palka, gcc-patches

On Tue, 9 Feb 2021, Jason Merrill wrote:

> On 2/8/21 2:03 PM, Patrick Palka wrote:
> > This sets up the functionality for controlling the initial set of
> > template parameters to pass to normalization when dealing with a
> > constraint-expression that is not associated with some constrained
> > declaration, for instance when normalizing a nested requirement of a
> > requires expression, or the constraints on a placeholder type.
> > 
> > The main new ingredient here is the data member norm_info::initial_parms
> > which can be set by callers of the normalization routines to communicate
> > the in-scope template parameters for the supplied constraint-expression,
> > rather than always falling back to using current_template_parms.
> > 
> > This patch then uses this functionality in our handling of nested
> > requirements so that we can delay normalizing them until needed for
> > satisfaction.  We currently immediately normalize nested requirements at
> > parse time, where we have the necessary template context, and cache the
> > normal form in their TREE_TYPE node.  With this patch, we now delay
> > normalization until needed (as with other constraint expressions), and
> > instead store the current value of current_template_parms in their
> > TREE_TYPE node (which we use to restore the template context at
> > normalization time).
> > 
> > In the subsequent patch, this functionality will also be used to
> > normalize placeholder type constraints during auto deduction.
> > 
> > gcc/cp/ChangeLog:
> > 
> > 	* constraint.cc (build_parameter_mapping): Rely on the caller to
> > 	determine the in-scope template parameters.
> > 	(norm_info::norm_info): Delegate the one-parameter constructor
> > 	to the two-parameter constructor.  In the two-parameter
> > 	constructor, fold in the definition of make_context, set
> > 	initial_parms appropriately, and don't set the now-removed
> > 	orig_decl member.
> > 	(norm_info::make_context): Remove, now that its only use is
> > 	inlined into the caller.
> > 	(norm_info::update_context): Adjust call to
> > 	build_parameter_mapping to pass in the relevant set of in-scope
> > 	template parameters.
> > 	(norm_info::ctx_parms): Define this member function.
> > 	(norm_info::context): Initialize to NULL_TREE.
> > 	(norm_info::orig_decl): Remove this data member.
> > 	(norm_info::initial_parms): Define this data member.
> > 	(normalize_atom): Adjust call to build_parameter_mapping to pass
> > 	in the relevant set of in-scope template parameters.  Use
> > 	info.initial_parms instead of info.orig_decl.
> > 	(normalize_constraint_expression): Define an overload that takes
> > 	a norm_info object.  Cache the result of normalization.  Define
> > 	the other overload in terms of this one, and handle a NESTED_REQ
> > 	argument by setting info.initial_parms appropriately.
> > 	(tsubst_nested_requirement): Go through
> > 	satisfy_constraint_expression so that we normalize on demand.
> > 	(finish_nested_requirement): Set the TREE_TYPE of the NESTED_REQ
> > 	to current_template_parms.
> > 	(diagnose_nested_requirements): Go through
> > 	satisfy_constraint_expression, as with tsubst_nested_requirement.
> > ---
> >   gcc/cp/constraint.cc | 140 +++++++++++++++++++++++--------------------
> >   gcc/cp/cp-tree.h     |   4 +-
> >   2 files changed, 78 insertions(+), 66 deletions(-)
> > 
> > diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> > index 39c97986082..56134f8b2bf 100644
> > --- a/gcc/cp/constraint.cc
> > +++ b/gcc/cp/constraint.cc
> > @@ -133,7 +133,7 @@ struct sat_info : subst_info
> >     bool diagnose_unsatisfaction;
> >   };
> >   -static tree satisfy_constraint (tree, tree, sat_info);
> > +static tree satisfy_constraint_expression (tree, tree, sat_info);
> >     /* True if T is known to be some type other than bool. Note that this
> >      is false for dependent types and errors.  */
> > @@ -594,26 +594,12 @@ map_arguments (tree parms, tree args)
> >     return parms;
> >   }
> >   -/* Build the parameter mapping for EXPR using ARGS.  */
> > +/* Build the parameter mapping for EXPR using ARGS, where CTX_PARMS
> > +   are the template parameters in scope for EXPR.  */
> >     static tree
> > -build_parameter_mapping (tree expr, tree args, tree decl)
> > +build_parameter_mapping (tree expr, tree args, tree ctx_parms)
> >   {
> > -  tree ctx_parms = NULL_TREE;
> > -  if (decl)
> > -    {
> > -      gcc_assert (TREE_CODE (decl) == TEMPLATE_DECL);
> > -      ctx_parms = DECL_TEMPLATE_PARMS (decl);
> > -    }
> > -  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.  */
> > -      ctx_parms = current_template_parms;
> > -    }
> > -
> >     tree parms = find_template_parameters (expr, ctx_parms);
> >     tree map = map_arguments (parms, args);
> >     return map;
> > @@ -645,53 +631,63 @@ parameter_mapping_equivalent_p (tree t1, tree t2)
> >     struct norm_info : subst_info
> >   {
> > -  explicit norm_info (tsubst_flags_t complain)
> > -    : subst_info (tf_warning_or_error | complain, NULL_TREE),
> > -      context()
> > +  explicit norm_info (tsubst_flags_t cmp)
> > +    : norm_info (NULL_TREE, cmp)
> >     {}
> >       /* Construct a top-level context for DECL.  */
> >       norm_info (tree in_decl, tsubst_flags_t complain)
> > -    : subst_info (tf_warning_or_error | complain, in_decl),
> > -      context (make_context (in_decl)),
> > -      orig_decl (in_decl)
> > -  {}
> > -
> > -  bool generate_diagnostics() const
> > +    : subst_info (tf_warning_or_error | complain, in_decl)
> >     {
> > -    return complain & tf_norm;
> > +    if (in_decl)
> > +      {
> > +	initial_parms = DECL_TEMPLATE_PARMS (in_decl);
> > +	if (generate_diagnostics ())
> > +	  context = build_tree_list (NULL_TREE, in_decl);
> > +      }
> > +    else
> > +      initial_parms = current_template_parms;
> >     }
> >   -  tree make_context(tree in_decl)
> > +  bool generate_diagnostics() const
> >     {
> > -    if (generate_diagnostics ())
> > -      return build_tree_list (NULL_TREE, in_decl);
> > -    return NULL_TREE;
> > +    return complain & tf_norm;
> >     }
> >       void update_context(tree expr, tree args)
> >     {
> >       if (generate_diagnostics ())
> >         {
> > -	tree map = build_parameter_mapping (expr, args, in_decl);
> > +	tree map = build_parameter_mapping (expr, args, ctx_parms ());
> >   	context = tree_cons (map, expr, context);
> >         }
> >       in_decl = get_concept_check_template (expr);
> >     }
> >   +  /* Returns the template parameters that are in scope for the current
> > +     normalization context.  */
> > +
> > +  tree ctx_parms()
> > +  {
> > +    if (in_decl)
> > +      return DECL_TEMPLATE_PARMS (in_decl);
> > +    else
> > +      return initial_parms;
> > +  }
> > +
> >     /* Provides information about the source of a constraint. This is a
> >        TREE_LIST whose VALUE is either a concept check or a constrained
> >        declaration. The PURPOSE, for concept checks is a parameter mapping
> >        for that check.  */
> >   -  tree context;
> > +  tree context = NULL_TREE;
> >       /* The declaration whose constraints we're normalizing.  The targets
> >        of the parameter mapping of each atom will be in terms of the
> >        template parameters of ORIG_DECL.  */
> >   -  tree orig_decl = NULL_TREE;
> > +  tree initial_parms = NULL_TREE;
> >   };
> >     static tree normalize_expression (tree, tree, norm_info);
> > @@ -773,7 +769,7 @@ normalize_atom (tree t, tree args, norm_info info)
> >       return normalize_concept_check (t, args, info);
> >       /* Build the parameter mapping for the atom.  */
> > -  tree map = build_parameter_mapping (t, args, info.in_decl);
> > +  tree map = build_parameter_mapping (t, args, info.ctx_parms ());
> >       /* Build a new info object for the atom.  */
> >     tree ci = build_tree_list (t, info.context);
> > @@ -803,10 +799,8 @@ normalize_atom (tree t, tree args, norm_info info)
> >   	      tree target = TREE_PURPOSE (node);
> >   	      TREE_VEC_ELT (targets, i++) = target;
> >   	    }
> > -	  tree ctx_parms = (info.orig_decl
> > -			    ? DECL_TEMPLATE_PARMS (info.orig_decl)
> > -			    : current_template_parms);
> > -	  tree target_parms = find_template_parameters (targets, ctx_parms);
> > +	  tree target_parms = find_template_parameters (targets,
> > +							info.initial_parms);
> >   	  TREE_TYPE (map) = target_parms;
> >   	}
> >   @@ -983,17 +977,43 @@ normalize_nontemplate_requirements (tree decl, bool
> > diag = false)
> >   /* Normalize an EXPR as a constraint.  */
> >     static tree
> > -normalize_constraint_expression (tree expr, bool diag)
> > +normalize_constraint_expression (tree expr, norm_info info)
> >   {
> >     if (!expr || expr == error_mark_node)
> >       return expr;
> > +
> > +  if (!info.generate_diagnostics ())
> > +    if (tree *p = hash_map_safe_get (normalized_map, expr))
> > +      return *p;
> 
> It seems like we only want this for NESTED_REQ.

I figured it'd also be beneficial to cache the normal form of a
placeholder type constraint, which will also goes through this overload.
I haven't done any careful benchmarking though.

> 
> >     ++processing_template_decl;
> > -  norm_info info (diag ? tf_norm : tf_none);
> >     tree norm = get_normalized_constraints (expr, info);
> >     --processing_template_decl;
> > +
> > +  if (!info.generate_diagnostics ())
> > +    hash_map_safe_put<hm_ggc> (normalized_map, expr, norm);
> > +
> >     return norm;
> >   }
> >   +/* High-level wrapper for the above.  */
> > +
> > +static tree
> > +normalize_constraint_expression (tree expr, bool diag)
> > +{
> > +  norm_info info (diag ? tf_norm : tf_none);
> 
> I wonder if we want to add a norm_info constructor taking a sat_info so we
> don't need to mediate passing from one into the other with bool "diag"
> parameters in various functions.  That doesn't need to happen in this patch.

Sounds good.  I think such a constructor would let us eliminate the
bool-taking overload of normalize_constraint_expression altogether, if
we move the special handling of NESTED_REQ to the norm_info-taking
overload or to satisfy_constraint_expression.

> 
> > +  if (TREE_CODE (expr) == NESTED_REQ)
> > +    {
> > +      /* The TREE_TYPE contains the set of template parameters that were
> > +	 in scope for this nested requiremen; use them as the initial template
> > +	 parameters for normalization.  */
> > +      info.initial_parms = TREE_TYPE (expr);
> > +      return normalize_constraint_expression (TREE_OPERAND (expr, 0),
> > info);
> > +    }
> > +  else
> > +    return normalize_constraint_expression (expr, info);
> > +}
> 
> It seems like you don't want any other code to call the first overload, so
> let's give the first one a different name to make that clearer.  The
> differences in functionality don't follow naturally from the parameter types.

In the next patch, a new function normalize_placeholder_type_constraints
will also call this norm_info-taking overload.  But I think we could
remove the bool-taking overload at least, as mentioned above.

> 
> Maybe this year we can cull/refactor the confusing set of overlapping and
> ambiguously named functions that the concepts code already has:
> 
> satisfy_constraint
> satisfy_associated_constraints
> satisfy_constraint_expression x2
> constraint_satisfaction_value x2
> constraints_satisfied_p x2
> satisfy_declaration_constraints x2
> evaluate_concept_check

Sounds good.  Some ideas:

satisfy_constraint could be renamed to something like
satisfy_normalized_constraint or satisfy_normal_form.

satisfy_associated_constraints is just a small wrapper over
satisfy_constraint, so we could remove it and have its two callers could
check for type-dependence of the supplied template arguments themselves.

satisfy_constraint_expression should probably be renamed to something
like satisfy_nondeclaration_constraints since it'd now handle more than
just constraint-expressions (e.g. NESTED_REQs and placeholder types).
The one-parameter version of satisfy_constraint_expression has just one
caller, and this use could be replaced with tsubst_requires_expr I think.

It seems we could straightforwardly merge the two
constraint_satisfaction_value overloads, which would also let us merge
the constraints_satisfied_p overloads.

We could also merge tsubst_requires_expr routines with the
diagnose_requires_expr ones.

> 
> >   /* 17.4.1.2p2. Two constraints are identical if they are formed
> >      from the same expression and the targets of the parameter mapping
> >      are equivalent.  */
> > @@ -2086,16 +2106,14 @@ tsubst_compound_requirement (tree t, tree args,
> > subst_info info)
> >   static tree
> >   tsubst_nested_requirement (tree t, tree args, subst_info info)
> >   {
> > -  /* Perform satisfaction quietly with the regular normal form.  */
> > +  /* Perform satisfaction quietly first.  */
> >     sat_info quiet (tf_none, info.in_decl);
> > -  tree norm = TREE_VALUE (TREE_TYPE (t));
> > -  tree diag_norm = TREE_PURPOSE (TREE_TYPE (t));
> > -  tree result = satisfy_constraint (norm, args, quiet);
> > +  tree result = satisfy_constraint_expression (t, args, quiet);
> >     if (result == error_mark_node)
> >       {
> > -      /* Replay the error using the diagnostic normal form.  */
> > +      /* Replay the error.  */
> >         sat_info noisy (tf_warning_or_error, info.in_decl);
> > -      satisfy_constraint (diag_norm, args, noisy);
> > +      satisfy_constraint_expression (t, args, noisy);
> >       }
> >     if (result != boolean_true_node)
> >       return error_mark_node;
> > @@ -3301,15 +3319,9 @@ finish_compound_requirement (location_t loc, tree
> > expr, tree type, bool noexcept
> >   tree
> >   finish_nested_requirement (location_t loc, tree expr)
> >   {
> > -  /* We need to normalize the constraints now, at parse time, while
> > -     we have the necessary template context.  We normalize twice,
> > -     once without diagnostic information and once with, which we'll
> > -     later use for quiet and noisy satisfaction respectively.  */
> > -  tree norm = normalize_constraint_expression (expr, /*diag=*/false);
> > -  tree diag_norm = normalize_constraint_expression (expr, /*diag=*/true);
> > -
> > -  /* Build the constraint, saving its two normalizations as its type.  */
> > -  tree r = build1 (NESTED_REQ, build_tree_list (diag_norm, norm), expr);
> > +  /* Build the requirement, saving the set of in-scope template
> > +     parameters as its type.  */
> > +  tree r = build1 (NESTED_REQ, current_template_parms, expr);
> >     SET_EXPR_LOCATION (r, loc);
> >     return r;
> >   }
> > @@ -3710,12 +3722,9 @@ diagnose_type_requirement (tree req, tree args, tree
> > in_decl)
> >   static void
> >   diagnose_nested_requirement (tree req, tree args)
> >   {
> > -  /* Quietly check for satisfaction first using the regular normal form.
> > -     We can elaborate details later if needed.  */
> > -  tree norm = TREE_VALUE (TREE_TYPE (req));
> > -  tree diag_norm = TREE_PURPOSE (TREE_TYPE (req));
> > -  sat_info info (tf_none, NULL_TREE);
> > -  tree result = satisfy_constraint (norm, args, info);
> > +  /* Quietly check for satisfaction first.  */
> > +  sat_info quiet (tf_none, NULL_TREE);
> > +  tree result = satisfy_constraint_expression (req, args, quiet);
> >     if (result == boolean_true_node)
> >       return;
> >   @@ -3723,10 +3732,11 @@ diagnose_nested_requirement (tree req, tree args)
> >     location_t loc = cp_expr_location (expr);
> >     if (diagnosing_failed_constraint::replay_errors_p ())
> >       {
> > -      /* Replay the substitution error using the diagnostic normal form.
> > */
> > +      /* Replay the substitution error with re-normalized requirements.  */
> >         inform (loc, "nested requirement %qE is not satisfied, because",
> > expr);
> > +
> >         sat_info noisy (tf_warning_or_error, NULL_TREE,
> > /*diag_unsat=*/true);
> > -      satisfy_constraint (diag_norm, args, noisy);
> > +      satisfy_constraint_expression (req, args, noisy);
> >       }
> >     else
> >       inform (loc, "nested requirement %qE is not satisfied", expr);
> > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> > index 970ed5e77bb..26fbf1eb663 100644
> > --- a/gcc/cp/cp-tree.h
> > +++ b/gcc/cp/cp-tree.h
> > @@ -1587,7 +1587,9 @@ check_constraint_info (tree t)
> >     TREE_LANG_FLAG_0 (TREE_CHECK (NODE, COMPOUND_REQ))
> >     /* The constraints on an 'auto' placeholder type, used in an argument
> > deduction
> > -   constraint.  */
> > +   constraint.  This should usually be set via
> > set_placeholder_type_constraints
> > +   since we also need to record the relevant set of in-scope template
> > parameters
> > +   for later normalization. */
> >   #define PLACEHOLDER_TYPE_CONSTRAINTS(NODE) \
> >     DECL_SIZE_UNIT (TYPE_NAME (NODE))
> >   
> 
> 


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 4/4] c++: dependent constraint on placeholder 'auto' [PR96443]
  2021-02-08 19:03 ` [PATCH 4/4] c++: dependent constraint on placeholder 'auto' [PR96443] Patrick Palka
  2021-02-08 19:39   ` Patrick Palka
@ 2021-02-11 16:19   ` Jason Merrill
  2021-02-11 22:14     ` Patrick Palka
  1 sibling, 1 reply; 29+ messages in thread
From: Jason Merrill @ 2021-02-11 16:19 UTC (permalink / raw)
  To: Patrick Palka, gcc-patches

On 2/8/21 2:03 PM, Patrick Palka wrote:
> This fixes the way we check satisfaction of constraints on placeholder
> types in various contexts, and in particular when the constraint is
> dependent.
> 
> Firstly, when evaluating the return type requirement of a compound
> requirement, we currently substitute the outer template arguments into
> the constraint before checking satisfaction. But we should instead be
> passing in the complete set of template arguments to satisfaction and
> not do a prior separate substitution.  Our current approach leads to us
> incorrectly rejecting the testcase concepts-return-req2.C below.
> 
> Secondly, when checking the constraints on a placeholder variable or
> return type, we don't substitute the template arguments of the enclosing
> context at all.  This leads to bogus errors during satisfaction when the
> constraint is dependent as in the testcase concepts-placeholder3.C
> below.
> 
> In order to fix these two issues, we need to be able to properly
> normalize the constraints on a placeholder 'auto', which in turn
> requires us to know the template parameters that were in-scope where an
> 'auto' was introduced.  This information currently doesn't seem to be
> easily available when we need it, so this patch adds an auxiliary hash
> table that keeps track of the value of current_template_parms when each
> constrained 'auto' was formed.
> 
> This patch also removes some seemingly wrong handling of placeholder
> type arguments from tsubst_parameter_mapping.  The code doesn't trigger
> with the example used in the comments, because type_uses_auto doesn't
> look inside non-deduced contexts such as the operand of decltype.  And
> the call to do_auto_deduction seems confused because if 'arg' is a type,
> then so is 'parm', and therefore 'init' too is a type, but
> do_auto_deduction expects it to be an expression.  Before this patch,
> this code was dead (as far as our testsuite can tell), but now it breaks
> other parts of this patch, so let's remove it.
> 
> gcc/cp/ChangeLog:
> 
> 	PR c++/96443
> 	* constraint.cc (type_deducible_p): Don't substitute into the
> 	constraints, and instead just pass 'args' to do_auto_deduction
> 	as the outer template arguments.
> 	(tsubst_parameter_mapping): Remove confused code for handling
> 	placeholder type arguments.
> 	(normalize_placeholder_type_constraint): Define.
> 	(satisfy_constraint_expression): Use it to handle placeholder
> 	'auto' types.
> 	* cp-tree.h (get_constrained_auto_context): Declare.
> 	* pt.c (constrained_auto_context_map): Define.
> 	(get_placeholder_type_constraint_context): Define.
> 	(set_placeholder_type_constraints): Define.
> 	(copy_placeholder_type_constraints): Define.
> 	(tsubst) <case TEMPLATE_TYPE_PARM>: Use
> 	copy_placeholder_type_constraints.
> 	(make_constrained_placeholder_type): Use
> 	set_placeholder_type_constraints.
> 	(do_auto_deduction): Clarify comments about the outer_targs
> 	parameter.  Rework satisfaction of a placeholder type constraint
> 	to pass in the complete set of template arguments directly to
> 	constraints_satisfied_p.
> 	(splice_late_return_type): Use copy_placeholder_type_constraints.
> 
> gcc/testsuite/ChangeLog:
> 
> 	PR c++/96443
> 	* g++.dg/cpp2a/concepts-placeholder3.C: New test.
> 	* g++.dg/cpp2a/concepts-return-req2.C: New test.
> 	* g++.dg/concepts-ts1.C: Add dg-bogus directive to the call to
> 	f15 that we expect to accept.
> ---
>   gcc/cp/constraint.cc                          | 106 ++++++++----------
>   gcc/cp/cp-tree.h                              |   1 +
>   gcc/cp/pt.c                                   | 101 +++++++++++------
>   .../g++.dg/cpp2a/concepts-placeholder3.C      |  19 ++++
>   .../g++.dg/cpp2a/concepts-return-req2.C       |  13 +++
>   gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C     |   2 +-
>   6 files changed, 146 insertions(+), 96 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-placeholder3.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-return-req2.C
> 
> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> index 56134f8b2bf..53588047d44 100644
> --- a/gcc/cp/constraint.cc
> +++ b/gcc/cp/constraint.cc
> @@ -2007,39 +2007,19 @@ type_deducible_p (tree expr, tree type, tree placeholder, tree args,
>        references are preserved in the result.  */
>     expr = force_paren_expr_uneval (expr);
>   
> -  /* Replace the constraints with the instantiated constraints. This
> -     substitutes args into any template parameters in the trailing
> -     result type.  */
> -  tree saved_constr = PLACEHOLDER_TYPE_CONSTRAINTS (placeholder);
> -  tree subst_constr
> -    = tsubst_constraint (saved_constr,
> -			 args,
> -			 info.complain | tf_partial,
> -			 info.in_decl);
> -
> -  if (subst_constr == error_mark_node)
> -    return false;
> -
> -  PLACEHOLDER_TYPE_CONSTRAINTS (placeholder) = subst_constr;
> -
> -  /* Temporarily unlink the canonical type.  */
> -  tree saved_type = TYPE_CANONICAL (placeholder);
> -  TYPE_CANONICAL (placeholder) = NULL_TREE;
> -
> -  tree deduced_type
> -    = do_auto_deduction (type,
> -			 expr,
> -			 placeholder,
> -			 info.complain,
> -			 adc_requirement);
> -
> -  PLACEHOLDER_TYPE_CONSTRAINTS (placeholder) = saved_constr;
> -  TYPE_CANONICAL (placeholder) = saved_type;
> +  /* When args is empty, we're evaluating a non-templated requires expression,
> +     but even those are parsed under processing_template_decl == 1, and so the
> +     placeholder 'auto' inside this return-type-requirement has level 2.  In
> +     order to have all parms and arguments match up for satisfaction, we need
> +     to pass a single level as OUTER_TARGS in this case.  */
> +  if (!args)
> +    args = make_tree_vec (0);
>   
> -  if (deduced_type == error_mark_node)
> -    return false;
> +  tree deduced_type = do_auto_deduction (type, expr, placeholder,
> +					 info.complain, adc_requirement,
> +					 /*outer_targs=*/args);
>   
> -  return true;
> +  return deduced_type != error_mark_node;
>   }
>   
>   /* True if EXPR can not be converted to TYPE.  */
> @@ -2304,35 +2284,10 @@ tsubst_parameter_mapping (tree map, tree args, subst_info info)
>           return error_mark_node;
>         tree parm = TREE_VALUE (p);
>         tree arg = TREE_PURPOSE (p);
> -      tree new_arg = NULL_TREE;
> -      if (TYPE_P (arg))
> -        {
> -          /* If a template parameter is declared with a placeholder, we can
> -             get those in the argument list if decltype is applied to the
> -             placeholder. For example:
> -
> -		template<auto T>
> -		  requires C<decltype(T)>
> -		void f() { }
> -
> -	     The normalized argument for C will be an auto type, so we'll
> -             need to deduce the actual argument from the corresponding
> -             initializer (whatever argument is provided for T), and use
> -             that result in the instantiated parameter mapping.  */
> -          if (tree auto_node = type_uses_auto (arg))
> -            {
> -              int level;
> -              int index;
> -	      template_parm_level_and_index (parm, &level, &index);
> -	      tree init = TMPL_ARG (args, level, index);
> -              new_arg = do_auto_deduction (arg, init, auto_node,
> -					   complain, adc_variable_type,
> -					   make_tree_vec (0));
> -            }
> -        }
> -      else if (ARGUMENT_PACK_P (arg))
> +      tree new_arg;
> +      if (ARGUMENT_PACK_P (arg))
>   	new_arg = tsubst_argument_pack (arg, args, complain, in_decl);
> -      if (!new_arg)
> +      else
>   	{
>   	  new_arg = tsubst_template_arg (arg, args, complain, in_decl);
>   	  if (TYPE_P (new_arg))
> @@ -3038,6 +2993,31 @@ satisfy_associated_constraints (tree t, tree args, sat_info info)
>     return satisfy_constraint (t, args, info);
>   }
>   
> +/* Normalize the constraints on the placeholder 'auto' type T.  */
> +
> +tree
> +normalize_placeholder_type_constraints (tree t, bool diag)
> +{
> +  gcc_assert (is_auto (t));
> +  tree constr = PLACEHOLDER_TYPE_CONSTRAINTS (t);
> +  if (!constr)
> +    return NULL_TREE;
> +
> +  tree initial_parms = get_constrained_auto_context (t);
> +  /* The 'auto' itself is used as the first argument in its own constraints,
> +     and its level is one greater than its template context, so in order to
> +     capture all used template parameters we need to add an extra level of
> +     template parameters to the context; a dummy level suffices.  */
> +  initial_parms
> +    = tree_cons (size_int (initial_parms
> +			   ? TMPL_PARMS_DEPTH (initial_parms) + 1 : 1),
> +		 make_tree_vec (0), initial_parms);
> +
> +  norm_info info (diag ? tf_norm : tf_none);
> +  info.initial_parms = initial_parms;
> +  return normalize_constraint_expression (constr, info);
> +}
> +
>   /* Evaluate EXPR as a constraint expression using ARGS, returning a
>      satisfaction value. */
>   
> @@ -3047,8 +3027,6 @@ satisfy_constraint_expression (tree t, tree args, sat_info info)
>     if (t == error_mark_node)
>       return error_mark_node;
>   
> -  gcc_assert (EXPR_P (t));
> -
>     /* Get the normalized constraints.  */
>     tree norm;
>     if (args == NULL_TREE && concept_check_p (t))
> @@ -3058,8 +3036,12 @@ satisfy_constraint_expression (tree t, tree args, sat_info info)
>         tree tmpl = get_concept_check_template (id);
>         norm = normalize_concept_definition (tmpl, info.noisy ());
>       }
> -  else
> +  else if (EXPR_P (t))
>       norm = normalize_constraint_expression (t, info.noisy ());
> +  else if (is_auto (t))
> +    norm = normalize_placeholder_type_constraints (t, info.noisy ());
> +  else
> +    gcc_unreachable ();
>   
>     /* Perform satisfaction.  */
>     return satisfy_constraint (norm, args, info);
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index 26fbf1eb663..ca5550ccb6d 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -7090,6 +7090,7 @@ extern tree make_auto				(void);
>   extern tree make_decltype_auto			(void);
>   extern tree make_constrained_auto		(tree, tree);
>   extern tree make_constrained_decltype_auto	(tree, tree);
> +extern tree get_constrained_auto_context	(tree);
>   extern tree make_template_placeholder		(tree);
>   extern bool template_placeholder_p		(tree);
>   extern bool ctad_template_p			(tree);
> diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
> index bceb942e79a..bcf9c9a3d9a 100644
> --- a/gcc/cp/pt.c
> +++ b/gcc/cp/pt.c
> @@ -15364,6 +15364,46 @@ tsubst_tree_list (tree t, tree args, tsubst_flags_t complain, tree in_decl)
>     return chain;
>   }
>   
> +/* A hash table mapping a constrained 'auto' to the set of in-scope
> +   template parameters from where the 'auto' was introduced.  */
> +
> +static GTY((cache)) decl_tree_cache_map *constrained_auto_context_map;

How about turning PLACEHOLDER_TYPE_CONSTRAINTS into a TREE_LIST instead 
of introducing a new hash table?

> +/* Return the set of in-scope template parameters for the constrained 'auto'
> +   placeholder type TYPE.  */
> +
> +tree
> +get_constrained_auto_context (tree type)
> +{
> +  gcc_assert (is_constrained_auto (type));
> +  return *constrained_auto_context_map->get (TYPE_NAME (type));
> +}
> +
> +/* Set the constraints on TYPE, an 'auto' placeholder type, to CONSTR.
> +   TPARMS is the set of template parameters that are in scope.  */
> +
> +static void
> +set_placeholder_type_constraints (tree type, tree constr, tree tparms)
> +{
> +  gcc_assert (is_auto (type));
> +  PLACEHOLDER_TYPE_CONSTRAINTS (type) = constr;
> +  hash_map_safe_put<hm_ggc> (constrained_auto_context_map,
> +			     TYPE_NAME (type), tparms);
> +}
> +
> +/* Copies the constraints on the 'auto' placeholder type FROM over to TO.  */
> +
> +static void
> +copy_placeholder_type_constraints (tree to, tree from)
> +{
> +  gcc_assert (is_auto (from));
> +  if (tree constr = PLACEHOLDER_TYPE_CONSTRAINTS (from))
> +    {
> +      tree tparms = get_constrained_auto_context (from);
> +      set_placeholder_type_constraints (to, constr, tparms);
> +    }
> +}

>   /* Take the tree structure T and replace template parameters used
>      therein with the argument vector ARGS.  IN_DECL is an associated
>      decl for diagnostics.  If an error occurs, returns ERROR_MARK_NODE.
> @@ -15723,8 +15763,8 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
>   		  {
>   		    /* Propagate constraints on placeholders since they are
>   		       only instantiated during satisfaction.  */
> -		    if (tree constr = PLACEHOLDER_TYPE_CONSTRAINTS (t))
> -		      PLACEHOLDER_TYPE_CONSTRAINTS (r) = constr;
> +		    if (PLACEHOLDER_TYPE_CONSTRAINTS (t))
> +		      copy_placeholder_type_constraints (r, t);
>   		    else if (tree pl = CLASS_PLACEHOLDER_TEMPLATE (t))
>   		      {
>   			pl = tsubst_copy (pl, args, complain, in_decl);
> @@ -28145,7 +28185,7 @@ make_constrained_placeholder_type (tree type, tree con, tree args)
>     expr = build_concept_check (expr, type, args, tf_warning_or_error);
>     --processing_template_decl;
>   
> -  PLACEHOLDER_TYPE_CONSTRAINTS (type) = expr;
> +  set_placeholder_type_constraints (type, expr, current_template_parms);
>   
>     /* Our canonical type depends on the constraint.  */
>     TYPE_CANONICAL (type) = canonical_type_parameter (type);
> @@ -29384,9 +29424,11 @@ do_class_deduction (tree ptype, tree tmpl, tree init,
>      from INIT.  AUTO_NODE is the TEMPLATE_TYPE_PARM used for 'auto' in TYPE.
>      The CONTEXT determines the context in which auto deduction is performed
>      and is used to control error diagnostics.  FLAGS are the LOOKUP_* flags.
> -   OUTER_TARGS are used during template argument deduction
> -   (context == adc_unify) to properly substitute the result, and is ignored
> -   in other contexts.
> +
> +   OUTER_TARGS is used during template argument deduction (context == adc_unify)
> +   to properly substitute the result.  It's also used in the adc_unify and
> +   adc_requirement contexts to communicate the the necessary template arguments
> +   to satisfaction.  OUTER_TARGS is ignored in other contexts.
>   
>      For partial-concept-ids, extra args may be appended to the list of deduced
>      template arguments prior to determining constraint satisfaction.  */
> @@ -29547,30 +29589,21 @@ do_auto_deduction (tree type, tree init, tree auto_node,
>       }
>   
>     /* Check any placeholder constraints against the deduced type. */
> -  if (flag_concepts && !processing_template_decl)
> -    if (tree check = NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
> +  if (flag_concepts)
> +    if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
>         {
> -        /* Use the deduced type to check the associated constraints. If we
> -           have a partial-concept-id, rebuild the argument list so that
> -           we check using the extra arguments. */
> -	check = unpack_concept_check (check);
> -	gcc_assert (TREE_CODE (check) == TEMPLATE_ID_EXPR);
> -	tree cdecl = TREE_OPERAND (check, 0);
> -	if (OVL_P (cdecl))
> -	  cdecl = OVL_FIRST (cdecl);
> -        tree cargs = TREE_OPERAND (check, 1);
> -        if (TREE_VEC_LENGTH (cargs) > 1)
> -          {
> -            cargs = copy_node (cargs);
> -            TREE_VEC_ELT (cargs, 0) = TREE_VEC_ELT (targs, 0);
> -          }
> -        else
> -          cargs = targs;
> +	if (processing_template_decl)
> +	  /* In general we can't check satisfaction until we know all
> +	     template arguments.  */
> +	  return type;
>   
> -	/* Rebuild the check using the deduced arguments.  */
> -	check = build_concept_check (cdecl, cargs, tf_none);
> +	if ((context == adc_return_type || context == adc_variable_type)
> +	    && current_function_decl
> +	    && DECL_TEMPLATE_INFO (current_function_decl))
> +	  outer_targs = DECL_TI_ARGS (current_function_decl);
>   
> -	if (!constraints_satisfied_p (check))
> +	tree complete_targs = add_to_template_args (outer_targs, targs);
> +	if (!constraints_satisfied_p (auto_node, complete_targs))
>             {
>               if (complain & tf_warning_or_error)
>                 {
> @@ -29595,15 +29628,18 @@ do_auto_deduction (tree type, tree init, tree auto_node,
>                              "placeholder constraints");
>                       break;
>                     }
> -		diagnose_constraints (input_location, check, targs);
> +		diagnose_constraints (input_location, auto_node, complete_targs);
>                 }
>               return error_mark_node;
>             }
>         }
>   
> -  if (processing_template_decl && context != adc_unify)
> -    outer_targs = current_template_args ();
> -  targs = add_to_template_args (outer_targs, targs);
> +  if (context == adc_unify)
> +    targs = add_to_template_args (outer_targs, targs);
> +  else if (processing_template_decl)
> +    targs = add_to_template_args (current_template_args (), targs);
> +  else
> +    /* Outer templates arguments should have already been substituted in.  */;
>     return tsubst (type, targs, complain, NULL_TREE);
>   }
>   
> @@ -29628,8 +29664,7 @@ splice_late_return_type (tree type, tree late_return_type)
>   	     with a function template when we saw the auto return type, so update
>   	     it to have the correct level.  */
>   	  tree new_auto = make_auto_1 (TYPE_IDENTIFIER (*auto_node), false);
> -	  PLACEHOLDER_TYPE_CONSTRAINTS (new_auto)
> -	    = PLACEHOLDER_TYPE_CONSTRAINTS (*auto_node);
> +	  copy_placeholder_type_constraints (new_auto, *auto_node);
>   	  TYPE_CANONICAL (new_auto) = canonical_type_parameter (new_auto);
>   	  new_auto = cp_build_qualified_type (new_auto, TYPE_QUALS (*auto_node));
>   	  *auto_node = new_auto;
> diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder3.C b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder3.C
> new file mode 100644
> index 00000000000..8bfc2db3837
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder3.C
> @@ -0,0 +1,19 @@
> +// PR c++/96443
> +// { dg-do compile { target c++20 } }
> +
> +template <class T, class U> concept same_as = __is_same(T, U);
> +
> +auto f(auto x) -> same_as<decltype(x)> auto { return 0; }; // { dg-error "constraints" }
> +void g(auto x) { same_as<decltype(x)> auto y = 0; } // { dg-error "constraints" }
> +auto h(auto x) -> same_as<decltype(x.missing)> auto { return 0; } // { dg-error "constraints|missing" }
> +template <typename T, same_as<T> auto N> void i() {}
> +
> +int main() {
> +  f(0); // { dg-bogus "" }
> +  f(true); // { dg-message "required from here" }
> +  g(0); // { dg-bogus "" }
> +  g(true); // { dg-message "required from here" }
> +  h(0); // { dg-message "required from here" }
> +  i<int, 0>(); // { dg-bogus "" }
> +  i<int, true>(); // { dg-error "no match|constraints" }
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-return-req2.C b/gcc/testsuite/g++.dg/cpp2a/concepts-return-req2.C
> new file mode 100644
> index 00000000000..26872ffc3d0
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-return-req2.C
> @@ -0,0 +1,13 @@
> +// Verify we check return-type-requirements by passing the entire set of
> +// template arguments to normalization rather than first substituting into
> +// the constraint.  The former approach would induce a substitution failure and
> +// cause the requires-expression to evaluate to false here.
> +// { dg-do compile { target c++20 } }
> +
> +template <class, class>
> +concept C1 = true;
> +
> +template <class T>
> +concept C2 = requires { { 0 } -> C1<typename T::type>; };
> +
> +static_assert(C2<int>);
> diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C b/gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C
> index 1cefe3b243f..a116cac4ea4 100644
> --- a/gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C
> +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C
> @@ -40,7 +40,7 @@ void driver()
>     f3('a'); // { dg-error "" }
>     f4(0, 0);
>     f4(0, 'a'); // { dg-error "" }
> -  f15(0);
> +  f15(0); // { dg-bogus "" }
>     f15('a'); // { dg-message "" }
>   }
>   
> 


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 4/4] c++: dependent constraint on placeholder 'auto' [PR96443]
  2021-02-11 16:19   ` Jason Merrill
@ 2021-02-11 22:14     ` Patrick Palka
  2021-02-12 19:14       ` Jason Merrill
  0 siblings, 1 reply; 29+ messages in thread
From: Patrick Palka @ 2021-02-11 22:14 UTC (permalink / raw)
  To: Jason Merrill; +Cc: Patrick Palka, gcc-patches

On Thu, 11 Feb 2021, Jason Merrill wrote:

> On 2/8/21 2:03 PM, Patrick Palka wrote:
> > This fixes the way we check satisfaction of constraints on placeholder
> > types in various contexts, and in particular when the constraint is
> > dependent.
> > 
> > Firstly, when evaluating the return type requirement of a compound
> > requirement, we currently substitute the outer template arguments into
> > the constraint before checking satisfaction. But we should instead be
> > passing in the complete set of template arguments to satisfaction and
> > not do a prior separate substitution.  Our current approach leads to us
> > incorrectly rejecting the testcase concepts-return-req2.C below.
> > 
> > Secondly, when checking the constraints on a placeholder variable or
> > return type, we don't substitute the template arguments of the enclosing
> > context at all.  This leads to bogus errors during satisfaction when the
> > constraint is dependent as in the testcase concepts-placeholder3.C
> > below.
> > 
> > In order to fix these two issues, we need to be able to properly
> > normalize the constraints on a placeholder 'auto', which in turn
> > requires us to know the template parameters that were in-scope where an
> > 'auto' was introduced.  This information currently doesn't seem to be
> > easily available when we need it, so this patch adds an auxiliary hash
> > table that keeps track of the value of current_template_parms when each
> > constrained 'auto' was formed.
> > 
> > This patch also removes some seemingly wrong handling of placeholder
> > type arguments from tsubst_parameter_mapping.  The code doesn't trigger
> > with the example used in the comments, because type_uses_auto doesn't
> > look inside non-deduced contexts such as the operand of decltype.  And
> > the call to do_auto_deduction seems confused because if 'arg' is a type,
> > then so is 'parm', and therefore 'init' too is a type, but
> > do_auto_deduction expects it to be an expression.  Before this patch,
> > this code was dead (as far as our testsuite can tell), but now it breaks
> > other parts of this patch, so let's remove it.
> > 
> > gcc/cp/ChangeLog:
> > 
> > 	PR c++/96443
> > 	* constraint.cc (type_deducible_p): Don't substitute into the
> > 	constraints, and instead just pass 'args' to do_auto_deduction
> > 	as the outer template arguments.
> > 	(tsubst_parameter_mapping): Remove confused code for handling
> > 	placeholder type arguments.
> > 	(normalize_placeholder_type_constraint): Define.
> > 	(satisfy_constraint_expression): Use it to handle placeholder
> > 	'auto' types.
> > 	* cp-tree.h (get_constrained_auto_context): Declare.
> > 	* pt.c (constrained_auto_context_map): Define.
> > 	(get_placeholder_type_constraint_context): Define.
> > 	(set_placeholder_type_constraints): Define.
> > 	(copy_placeholder_type_constraints): Define.
> > 	(tsubst) <case TEMPLATE_TYPE_PARM>: Use
> > 	copy_placeholder_type_constraints.
> > 	(make_constrained_placeholder_type): Use
> > 	set_placeholder_type_constraints.
> > 	(do_auto_deduction): Clarify comments about the outer_targs
> > 	parameter.  Rework satisfaction of a placeholder type constraint
> > 	to pass in the complete set of template arguments directly to
> > 	constraints_satisfied_p.
> > 	(splice_late_return_type): Use copy_placeholder_type_constraints.
> > 
> > gcc/testsuite/ChangeLog:
> > 
> > 	PR c++/96443
> > 	* g++.dg/cpp2a/concepts-placeholder3.C: New test.
> > 	* g++.dg/cpp2a/concepts-return-req2.C: New test.
> > 	* g++.dg/concepts-ts1.C: Add dg-bogus directive to the call to
> > 	f15 that we expect to accept.
> > ---
> >   gcc/cp/constraint.cc                          | 106 ++++++++----------
> >   gcc/cp/cp-tree.h                              |   1 +
> >   gcc/cp/pt.c                                   | 101 +++++++++++------
> >   .../g++.dg/cpp2a/concepts-placeholder3.C      |  19 ++++
> >   .../g++.dg/cpp2a/concepts-return-req2.C       |  13 +++
> >   gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C     |   2 +-
> >   6 files changed, 146 insertions(+), 96 deletions(-)
> >   create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-placeholder3.C
> >   create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-return-req2.C
> > 
> > diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> > index 56134f8b2bf..53588047d44 100644
> > --- a/gcc/cp/constraint.cc
> > +++ b/gcc/cp/constraint.cc
> > @@ -2007,39 +2007,19 @@ type_deducible_p (tree expr, tree type, tree
> > placeholder, tree args,
> >        references are preserved in the result.  */
> >     expr = force_paren_expr_uneval (expr);
> >   -  /* Replace the constraints with the instantiated constraints. This
> > -     substitutes args into any template parameters in the trailing
> > -     result type.  */
> > -  tree saved_constr = PLACEHOLDER_TYPE_CONSTRAINTS (placeholder);
> > -  tree subst_constr
> > -    = tsubst_constraint (saved_constr,
> > -			 args,
> > -			 info.complain | tf_partial,
> > -			 info.in_decl);
> > -
> > -  if (subst_constr == error_mark_node)
> > -    return false;
> > -
> > -  PLACEHOLDER_TYPE_CONSTRAINTS (placeholder) = subst_constr;
> > -
> > -  /* Temporarily unlink the canonical type.  */
> > -  tree saved_type = TYPE_CANONICAL (placeholder);
> > -  TYPE_CANONICAL (placeholder) = NULL_TREE;
> > -
> > -  tree deduced_type
> > -    = do_auto_deduction (type,
> > -			 expr,
> > -			 placeholder,
> > -			 info.complain,
> > -			 adc_requirement);
> > -
> > -  PLACEHOLDER_TYPE_CONSTRAINTS (placeholder) = saved_constr;
> > -  TYPE_CANONICAL (placeholder) = saved_type;
> > +  /* When args is empty, we're evaluating a non-templated requires
> > expression,
> > +     but even those are parsed under processing_template_decl == 1, and so
> > the
> > +     placeholder 'auto' inside this return-type-requirement has level 2.
> > In
> > +     order to have all parms and arguments match up for satisfaction, we
> > need
> > +     to pass a single level as OUTER_TARGS in this case.  */
> > +  if (!args)
> > +    args = make_tree_vec (0);
> >   -  if (deduced_type == error_mark_node)
> > -    return false;
> > +  tree deduced_type = do_auto_deduction (type, expr, placeholder,
> > +					 info.complain, adc_requirement,
> > +					 /*outer_targs=*/args);
> >   -  return true;
> > +  return deduced_type != error_mark_node;
> >   }
> >     /* True if EXPR can not be converted to TYPE.  */
> > @@ -2304,35 +2284,10 @@ tsubst_parameter_mapping (tree map, tree args,
> > subst_info info)
> >           return error_mark_node;
> >         tree parm = TREE_VALUE (p);
> >         tree arg = TREE_PURPOSE (p);
> > -      tree new_arg = NULL_TREE;
> > -      if (TYPE_P (arg))
> > -        {
> > -          /* If a template parameter is declared with a placeholder, we can
> > -             get those in the argument list if decltype is applied to the
> > -             placeholder. For example:
> > -
> > -		template<auto T>
> > -		  requires C<decltype(T)>
> > -		void f() { }
> > -
> > -	     The normalized argument for C will be an auto type, so we'll
> > -             need to deduce the actual argument from the corresponding
> > -             initializer (whatever argument is provided for T), and use
> > -             that result in the instantiated parameter mapping.  */
> > -          if (tree auto_node = type_uses_auto (arg))
> > -            {
> > -              int level;
> > -              int index;
> > -	      template_parm_level_and_index (parm, &level, &index);
> > -	      tree init = TMPL_ARG (args, level, index);
> > -              new_arg = do_auto_deduction (arg, init, auto_node,
> > -					   complain, adc_variable_type,
> > -					   make_tree_vec (0));
> > -            }
> > -        }
> > -      else if (ARGUMENT_PACK_P (arg))
> > +      tree new_arg;
> > +      if (ARGUMENT_PACK_P (arg))
> >   	new_arg = tsubst_argument_pack (arg, args, complain, in_decl);
> > -      if (!new_arg)
> > +      else
> >   	{
> >   	  new_arg = tsubst_template_arg (arg, args, complain, in_decl);
> >   	  if (TYPE_P (new_arg))
> > @@ -3038,6 +2993,31 @@ satisfy_associated_constraints (tree t, tree args,
> > sat_info info)
> >     return satisfy_constraint (t, args, info);
> >   }
> >   +/* Normalize the constraints on the placeholder 'auto' type T.  */
> > +
> > +tree
> > +normalize_placeholder_type_constraints (tree t, bool diag)
> > +{
> > +  gcc_assert (is_auto (t));
> > +  tree constr = PLACEHOLDER_TYPE_CONSTRAINTS (t);
> > +  if (!constr)
> > +    return NULL_TREE;
> > +
> > +  tree initial_parms = get_constrained_auto_context (t);
> > +  /* The 'auto' itself is used as the first argument in its own
> > constraints,
> > +     and its level is one greater than its template context, so in order to
> > +     capture all used template parameters we need to add an extra level of
> > +     template parameters to the context; a dummy level suffices.  */
> > +  initial_parms
> > +    = tree_cons (size_int (initial_parms
> > +			   ? TMPL_PARMS_DEPTH (initial_parms) + 1 : 1),
> > +		 make_tree_vec (0), initial_parms);
> > +
> > +  norm_info info (diag ? tf_norm : tf_none);
> > +  info.initial_parms = initial_parms;
> > +  return normalize_constraint_expression (constr, info);
> > +}
> > +
> >   /* Evaluate EXPR as a constraint expression using ARGS, returning a
> >      satisfaction value. */
> >   @@ -3047,8 +3027,6 @@ satisfy_constraint_expression (tree t, tree args,
> > sat_info info)
> >     if (t == error_mark_node)
> >       return error_mark_node;
> >   -  gcc_assert (EXPR_P (t));
> > -
> >     /* Get the normalized constraints.  */
> >     tree norm;
> >     if (args == NULL_TREE && concept_check_p (t))
> > @@ -3058,8 +3036,12 @@ satisfy_constraint_expression (tree t, tree args,
> > sat_info info)
> >         tree tmpl = get_concept_check_template (id);
> >         norm = normalize_concept_definition (tmpl, info.noisy ());
> >       }
> > -  else
> > +  else if (EXPR_P (t))
> >       norm = normalize_constraint_expression (t, info.noisy ());
> > +  else if (is_auto (t))
> > +    norm = normalize_placeholder_type_constraints (t, info.noisy ());
> > +  else
> > +    gcc_unreachable ();
> >       /* Perform satisfaction.  */
> >     return satisfy_constraint (norm, args, info);
> > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> > index 26fbf1eb663..ca5550ccb6d 100644
> > --- a/gcc/cp/cp-tree.h
> > +++ b/gcc/cp/cp-tree.h
> > @@ -7090,6 +7090,7 @@ extern tree make_auto
> > (void);
> >   extern tree make_decltype_auto			(void);
> >   extern tree make_constrained_auto		(tree, tree);
> >   extern tree make_constrained_decltype_auto	(tree, tree);
> > +extern tree get_constrained_auto_context	(tree);
> >   extern tree make_template_placeholder		(tree);
> >   extern bool template_placeholder_p		(tree);
> >   extern bool ctad_template_p			(tree);
> > diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
> > index bceb942e79a..bcf9c9a3d9a 100644
> > --- a/gcc/cp/pt.c
> > +++ b/gcc/cp/pt.c
> > @@ -15364,6 +15364,46 @@ tsubst_tree_list (tree t, tree args, tsubst_flags_t
> > complain, tree in_decl)
> >     return chain;
> >   }
> >   +/* A hash table mapping a constrained 'auto' to the set of in-scope
> > +   template parameters from where the 'auto' was introduced.  */
> > +
> > +static GTY((cache)) decl_tree_cache_map *constrained_auto_context_map;
> 
> How about turning PLACEHOLDER_TYPE_CONSTRAINTS into a TREE_LIST instead of
> introducing a new hash table?

Nice, this cleaner approach works well, and was quite straightforward to
implement.  How does this look?  Tested on x86_64-pc-linux-gnu,
range-v3  and cmcstl2.

-- >8 --

Subject: [PATCH] c++: Fix satisfaction of placeholder type constraints
 [PR96443]

This fixes the way we check satisfaction of constraints on placeholder
types in various deduction contexts, and in particular when the
constraint is dependent.

Firstly, when evaluating the return type requirement of a compound
requirement, we currently substitute the outer template arguments into
the constraint before checking satisfaction. But we should instead be
passing in the complete set of template arguments to satisfaction and
not do a prior separate substitution.  Our current approach leads to us
incorrectly rejecting the testcase concepts-return-req2.C below.

Secondly, when checking the constraints on a placeholder variable or
return type, we don't consider the template arguments of the enclosing
context at all.  This leads to bogus errors during satisfaction when the
constraint is dependent as in the testcase concepts-placeholder3.C
below.

In order to fix these two issues, we need to be able to normalize the
constraints on a placeholder 'auto', which in turn requires us to know
the template parameters that were in scope where the 'auto' was
introduced.  This information currently doesn't seem to be easily
available when we need it, so this patch turns PLACEHOLDER_TYPE_CONSTRAINTS
into a TREE_LIST whose TREE_PURPOSE holds the value of
current_template_parms whence a constrained 'auto' was formed.

This patch also removes some seemingly wrong handling of placeholder
type arguments from tsubst_parameter_mapping.  The code doesn't trigger
with the example used in the comments, because type_uses_auto doesn't
look inside non-deduced contexts such as the operand of decltype.  And
the call to do_auto_deduction seems confused because if 'arg' is a type,
then so is 'parm', and therefore 'init' too is a type, but
do_auto_deduction expects it to be an expression.  Before this patch,
this code was dead (as far as our testsuite can tell), but now it breaks
other parts of this patch, so let's remove it.

gcc/cp/ChangeLog:

	PR c++/96443
	* constraint.cc (type_deducible_p): Don't substitute into the
	constraints, and instead just pass 'args' to do_auto_deduction
	as the outer template arguments.
	(tsubst_parameter_mapping): Remove confused code for handling
	placeholder type arguments.
	(normalize_placeholder_type_constraint): Define.
	(satisfy_constraint_expression): Use it to handle placeholder
	'auto' types.
	* cp-tree.h (PLACEHOLDER_TYPE_CONSTRAINTS_INFO): Define.
	(PLACEHOLDER_TYPE_CONSTRAINTS): Redefine in terms of the above.
	* pt.c (tsubst) <case TEMPLATE_TYPE_PARM>: Use
	PLACEHOLDER_TYPE_CONSTRAINTS_INFO instead.
	(make_constrained_placeholder_type): Set
	PLACEHOLDER_TYPE_CONSTRAINTS_INFO instead.
	(do_auto_deduction): Clarify comments about the outer_targs
	parameter.  Rework satisfaction of a placeholder type constraint
	to pass in the complete set of template arguments directly to
	constraints_satisfied_p.
	(splice_late_return_type): Use PLACEHOLDER_TYPE_CONSTRAINTS_INFO
	instead.

gcc/testsuite/ChangeLog:

	PR c++/96443
	* g++.dg/cpp2a/concepts-placeholder3.C: New test.
	* g++.dg/cpp2a/concepts-return-req2.C: New test.
	* g++.dg/concepts-ts1.C: Add dg-bogus directive to the call to
	f15 that we expect to accept.
---
 gcc/cp/constraint.cc                          | 108 ++++++++----------
 gcc/cp/cp-tree.h                              |  18 ++-
 gcc/cp/pt.c                                   |  67 +++++------
 .../g++.dg/cpp2a/concepts-placeholder3.C      |  19 +++
 .../g++.dg/cpp2a/concepts-return-req2.C       |  13 +++
 gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C     |   2 +-
 6 files changed, 122 insertions(+), 105 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-placeholder3.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-return-req2.C

diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index a58b428c067..c26e3fae114 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -2007,39 +2007,19 @@ type_deducible_p (tree expr, tree type, tree placeholder, tree args,
      references are preserved in the result.  */
   expr = force_paren_expr_uneval (expr);
 
-  /* Replace the constraints with the instantiated constraints. This
-     substitutes args into any template parameters in the trailing
-     result type.  */
-  tree saved_constr = PLACEHOLDER_TYPE_CONSTRAINTS (placeholder);
-  tree subst_constr
-    = tsubst_constraint (saved_constr,
-			 args,
-			 info.complain | tf_partial,
-			 info.in_decl);
-
-  if (subst_constr == error_mark_node)
-    return false;
-
-  PLACEHOLDER_TYPE_CONSTRAINTS (placeholder) = subst_constr;
-
-  /* Temporarily unlink the canonical type.  */
-  tree saved_type = TYPE_CANONICAL (placeholder);
-  TYPE_CANONICAL (placeholder) = NULL_TREE;
-
-  tree deduced_type
-    = do_auto_deduction (type,
-			 expr,
-			 placeholder,
-			 info.complain,
-			 adc_requirement);
-
-  PLACEHOLDER_TYPE_CONSTRAINTS (placeholder) = saved_constr;
-  TYPE_CANONICAL (placeholder) = saved_type;
+  /* When args is empty, we're evaluating a non-templated requires expression,
+     but even those are parsed under processing_template_decl == 1, and so the
+     placeholder 'auto' inside this return-type-requirement has level 2.  In
+     order to have all parms and arguments match up for satisfaction, we need
+     to pass a single level of OUTER_TARGS in this case.  */
+  if (!args)
+    args = make_tree_vec (0);
 
-  if (deduced_type == error_mark_node)
-    return false;
+  tree deduced_type = do_auto_deduction (type, expr, placeholder,
+					 info.complain, adc_requirement,
+					 /*outer_targs=*/args);
 
-  return true;
+  return deduced_type != error_mark_node;
 }
 
 /* True if EXPR can not be converted to TYPE.  */
@@ -2304,35 +2284,10 @@ tsubst_parameter_mapping (tree map, tree args, subst_info info)
         return error_mark_node;
       tree parm = TREE_VALUE (p);
       tree arg = TREE_PURPOSE (p);
-      tree new_arg = NULL_TREE;
-      if (TYPE_P (arg))
-        {
-          /* If a template parameter is declared with a placeholder, we can
-             get those in the argument list if decltype is applied to the
-             placeholder. For example:
-
-		template<auto T>
-		  requires C<decltype(T)>
-		void f() { }
-
-	     The normalized argument for C will be an auto type, so we'll
-             need to deduce the actual argument from the corresponding
-             initializer (whatever argument is provided for T), and use
-             that result in the instantiated parameter mapping.  */
-          if (tree auto_node = type_uses_auto (arg))
-            {
-              int level;
-              int index;
-	      template_parm_level_and_index (parm, &level, &index);
-	      tree init = TMPL_ARG (args, level, index);
-              new_arg = do_auto_deduction (arg, init, auto_node,
-					   complain, adc_variable_type,
-					   make_tree_vec (0));
-            }
-        }
-      else if (ARGUMENT_PACK_P (arg))
+      tree new_arg;
+      if (ARGUMENT_PACK_P (arg))
 	new_arg = tsubst_argument_pack (arg, args, complain, in_decl);
-      if (!new_arg)
+      else
 	{
 	  new_arg = tsubst_template_arg (arg, args, complain, in_decl);
 	  if (TYPE_P (new_arg))
@@ -3038,6 +2993,33 @@ satisfy_associated_constraints (tree t, tree args, sat_info info)
   return satisfy_constraint (t, args, info);
 }
 
+/* Return the normal form of the constraints on the placeholder 'auto'
+   type T.  */
+
+tree
+normalize_placeholder_type_constraints (tree t, bool diag)
+{
+  gcc_assert (is_auto (t));
+  tree ci = PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t);
+  if (!ci)
+    return NULL_TREE;
+
+  tree constr = TREE_VALUE (ci);
+  tree initial_parms = TREE_PURPOSE (ci);
+  /* The 'auto' itself is used as the first argument in its own constraints,
+     and its level is one greater than its template depth.  So in order to
+     capture all used template parameters, we need to add an extra level of
+     template parameters to the context; a dummy level suffices.  */
+  initial_parms
+    = tree_cons (size_int (initial_parms
+			   ? TMPL_PARMS_DEPTH (initial_parms) + 1 : 1),
+		 make_tree_vec (0), initial_parms);
+
+  norm_info info (diag ? tf_norm : tf_none);
+  info.initial_parms = initial_parms;
+  return normalize_constraint_expression (constr, info);
+}
+
 /* Evaluate EXPR as a constraint expression using ARGS, returning a
    satisfaction value. */
 
@@ -3047,8 +3029,6 @@ satisfy_constraint_expression (tree t, tree args, sat_info info)
   if (t == error_mark_node)
     return error_mark_node;
 
-  gcc_assert (EXPR_P (t));
-
   /* Get the normalized constraints.  */
   tree norm;
   if (args == NULL_TREE && concept_check_p (t))
@@ -3058,8 +3038,12 @@ satisfy_constraint_expression (tree t, tree args, sat_info info)
       tree tmpl = get_concept_check_template (id);
       norm = normalize_concept_definition (tmpl, info.noisy ());
     }
-  else
+  else if (EXPR_P (t))
     norm = normalize_constraint_expression (t, info.noisy ());
+  else if (is_auto (t))
+    norm = normalize_placeholder_type_constraints (t, info.noisy ());
+  else
+    gcc_unreachable ();
 
   /* Perform satisfaction.  */
   return satisfy_constraint (norm, args, info);
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index ce24247fc5b..af2a592470c 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -1588,13 +1588,19 @@ check_constraint_info (tree t)
 #define COMPOUND_REQ_NOEXCEPT_P(NODE) \
   TREE_LANG_FLAG_0 (TREE_CHECK (NODE, COMPOUND_REQ))
 
-/* The constraints on an 'auto' placeholder type, used in an argument deduction
-   constraint.  This should usually be set via set_placeholder_type_constraints
-   since we also need to record the relevant set of in-scope template parameters
-   for later normalization. */
-#define PLACEHOLDER_TYPE_CONSTRAINTS(NODE) \
+/* A TREE_LIST whose TREE_VALUE is the constraints on the 'auto' placeholder
+   type NODE, used in an argument deduction constraint.  The TREE_PURPOSE
+   holds the set of template parameters that were in-scope when this 'auto'
+   was formed.  */
+#define PLACEHOLDER_TYPE_CONSTRAINTS_INFO(NODE) \
   DECL_SIZE_UNIT (TYPE_NAME (NODE))
 
+/* The constraints on the 'auto' placeholder type NODE.  */
+#define PLACEHOLDER_TYPE_CONSTRAINTS(NODE)		   \
+  (PLACEHOLDER_TYPE_CONSTRAINTS_INFO (NODE)		   \
+   ? TREE_VALUE (PLACEHOLDER_TYPE_CONSTRAINTS_INFO (NODE)) \
+   : NULL_TREE)
+
 /* True if NODE is a constraint.  */
 #define CONSTR_P(NODE)                  \
   (TREE_CODE (NODE) == ATOMIC_CONSTR    \
@@ -8433,7 +8439,7 @@ set_implicit_rvalue_p (tree ot)
 inline bool
 is_constrained_auto (const_tree t)
 {
-  return is_auto (t) && PLACEHOLDER_TYPE_CONSTRAINTS (t);
+  return is_auto (t) && PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t);
 }
 
 /* RAII class to push/pop class scope T; if T is not a class, do nothing.  */
diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
index c6c9c1dd4b1..15ef6d7565f 100644
--- a/gcc/cp/pt.c
+++ b/gcc/cp/pt.c
@@ -15692,15 +15692,15 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
 			       ? tf_ignore_bad_quals : 0));
 	      }
 	    else if (TREE_CODE (t) == TEMPLATE_TYPE_PARM
-		     && PLACEHOLDER_TYPE_CONSTRAINTS (t)
+		     && PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t)
 		     && (r = (TEMPLATE_PARM_DESCENDANTS
 			      (TEMPLATE_TYPE_PARM_INDEX (t))))
 		     && (r = TREE_TYPE (r))
-		     && !PLACEHOLDER_TYPE_CONSTRAINTS (r))
+		     && !PLACEHOLDER_TYPE_CONSTRAINTS_INFO (r))
 	      /* Break infinite recursion when substituting the constraints
 		 of a constrained placeholder.  */;
 	    else if (TREE_CODE (t) == TEMPLATE_TYPE_PARM
-		     && !PLACEHOLDER_TYPE_CONSTRAINTS (t)
+		     && !PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t)
 		     && !CLASS_PLACEHOLDER_TEMPLATE (t)
 		     && (arg = TEMPLATE_TYPE_PARM_INDEX (t),
 			 r = TEMPLATE_PARM_DESCENDANTS (arg))
@@ -15723,8 +15723,8 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
 		  {
 		    /* Propagate constraints on placeholders since they are
 		       only instantiated during satisfaction.  */
-		    if (tree constr = PLACEHOLDER_TYPE_CONSTRAINTS (t))
-		      PLACEHOLDER_TYPE_CONSTRAINTS (r) = constr;
+		    if (tree ci = PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t))
+		      PLACEHOLDER_TYPE_CONSTRAINTS_INFO (r) = ci;
 		    else if (tree pl = CLASS_PLACEHOLDER_TEMPLATE (t))
 		      {
 			pl = tsubst_copy (pl, args, complain, in_decl);
@@ -28155,7 +28155,8 @@ make_constrained_placeholder_type (tree type, tree con, tree args)
   expr = build_concept_check (expr, type, args, tf_warning_or_error);
   --processing_template_decl;
 
-  PLACEHOLDER_TYPE_CONSTRAINTS (type) = expr;
+  PLACEHOLDER_TYPE_CONSTRAINTS_INFO (type)
+    = build_tree_list (current_template_parms, expr);
 
   /* Our canonical type depends on the constraint.  */
   TYPE_CANONICAL (type) = canonical_type_parameter (type);
@@ -29394,9 +29395,11 @@ do_class_deduction (tree ptype, tree tmpl, tree init,
    from INIT.  AUTO_NODE is the TEMPLATE_TYPE_PARM used for 'auto' in TYPE.
    The CONTEXT determines the context in which auto deduction is performed
    and is used to control error diagnostics.  FLAGS are the LOOKUP_* flags.
-   OUTER_TARGS are used during template argument deduction
-   (context == adc_unify) to properly substitute the result, and is ignored
-   in other contexts.
+
+   OUTER_TARGS is used during template argument deduction (context == adc_unify)
+   to properly substitute the result.  It's also used in the adc_unify and
+   adc_requirement contexts to communicate the the necessary template arguments
+   to satisfaction.  OUTER_TARGS is ignored in other contexts.
 
    For partial-concept-ids, extra args may be appended to the list of deduced
    template arguments prior to determining constraint satisfaction.  */
@@ -29557,30 +29560,21 @@ do_auto_deduction (tree type, tree init, tree auto_node,
     }
 
   /* Check any placeholder constraints against the deduced type. */
-  if (flag_concepts && !processing_template_decl)
-    if (tree check = NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
+  if (flag_concepts)
+    if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
       {
-        /* Use the deduced type to check the associated constraints. If we
-           have a partial-concept-id, rebuild the argument list so that
-           we check using the extra arguments. */
-	check = unpack_concept_check (check);
-	gcc_assert (TREE_CODE (check) == TEMPLATE_ID_EXPR);
-	tree cdecl = TREE_OPERAND (check, 0);
-	if (OVL_P (cdecl))
-	  cdecl = OVL_FIRST (cdecl);
-        tree cargs = TREE_OPERAND (check, 1);
-        if (TREE_VEC_LENGTH (cargs) > 1)
-          {
-            cargs = copy_node (cargs);
-            TREE_VEC_ELT (cargs, 0) = TREE_VEC_ELT (targs, 0);
-          }
-        else
-          cargs = targs;
+	if (processing_template_decl)
+	  /* In general we can't check satisfaction until we know all
+	     template arguments.  */
+	  return type;
 
-	/* Rebuild the check using the deduced arguments.  */
-	check = build_concept_check (cdecl, cargs, tf_none);
+	if ((context == adc_return_type || context == adc_variable_type)
+	    && current_function_decl
+	    && DECL_TEMPLATE_INFO (current_function_decl))
+	  outer_targs = DECL_TI_ARGS (current_function_decl);
 
-	if (!constraints_satisfied_p (check))
+	tree complete_targs = add_to_template_args (outer_targs, targs);
+	if (!constraints_satisfied_p (auto_node, complete_targs))
           {
             if (complain & tf_warning_or_error)
               {
@@ -29605,15 +29599,16 @@ do_auto_deduction (tree type, tree init, tree auto_node,
                            "placeholder constraints");
                     break;
                   }
-		diagnose_constraints (input_location, check, targs);
+		diagnose_constraints (input_location, auto_node, complete_targs);
               }
             return error_mark_node;
           }
       }
 
-  if (processing_template_decl && context != adc_unify)
-    outer_targs = current_template_args ();
-  targs = add_to_template_args (outer_targs, targs);
+  if (context == adc_unify)
+    targs = add_to_template_args (outer_targs, targs);
+  else if (processing_template_decl)
+    targs = add_to_template_args (current_template_args (), targs);
   return tsubst (type, targs, complain, NULL_TREE);
 }
 
@@ -29638,8 +29633,8 @@ splice_late_return_type (tree type, tree late_return_type)
 	     with a function template when we saw the auto return type, so update
 	     it to have the correct level.  */
 	  tree new_auto = make_auto_1 (TYPE_IDENTIFIER (*auto_node), false);
-	  PLACEHOLDER_TYPE_CONSTRAINTS (new_auto)
-	    = PLACEHOLDER_TYPE_CONSTRAINTS (*auto_node);
+	  PLACEHOLDER_TYPE_CONSTRAINTS_INFO (new_auto)
+	    = PLACEHOLDER_TYPE_CONSTRAINTS_INFO (*auto_node);
 	  TYPE_CANONICAL (new_auto) = canonical_type_parameter (new_auto);
 	  new_auto = cp_build_qualified_type (new_auto, TYPE_QUALS (*auto_node));
 	  *auto_node = new_auto;
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder3.C b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder3.C
new file mode 100644
index 00000000000..87e3c093e28
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder3.C
@@ -0,0 +1,19 @@
+// PR c++/96443
+// { dg-do compile { target c++20 } }
+
+template <class T, class U> concept same_as = __is_same(T, U);
+
+auto f(auto x) -> same_as<decltype(x)> auto { return 0; }; // { dg-error "constraints" }
+void g(auto x) { same_as<decltype(x)> auto y = 0; } // { dg-error "constraints" }
+auto h(auto x) -> same_as<decltype(x.missing)> auto { return 0; } // { dg-error "constraints|missing" }
+template <class T, same_as<T> auto N> void i() {}
+
+int main() {
+  f(0); // { dg-bogus "" }
+  f(true); // { dg-message "required from here" }
+  g(0); // { dg-bogus "" }
+  g(true); // { dg-message "required from here" }
+  h(0); // { dg-message "required from here" }
+  i<int, 0>(); // { dg-bogus "" }
+  i<int, true>(); // { dg-error "no match|constraints" }
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-return-req2.C b/gcc/testsuite/g++.dg/cpp2a/concepts-return-req2.C
new file mode 100644
index 00000000000..77208bb7069
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-return-req2.C
@@ -0,0 +1,13 @@
+// Verify we check return-type-requirements by passing the entire set of
+// template arguments to normalization rather than first substituting into
+// the constraint.  The latter approach would induce a substitution failure and
+// cause the requires-expression to evaluate to false here.
+// { dg-do compile { target c++20 } }
+
+template <class, class>
+concept C1 = true;
+
+template <class T>
+concept C2 = requires { { 0 } -> C1<typename T::type>; };
+
+static_assert(C2<int>);
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C b/gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C
index 1cefe3b243f..a116cac4ea4 100644
--- a/gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C
@@ -40,7 +40,7 @@ void driver()
   f3('a'); // { dg-error "" }
   f4(0, 0);
   f4(0, 'a'); // { dg-error "" }
-  f15(0);
+  f15(0); // { dg-bogus "" }
   f15('a'); // { dg-message "" }
 }
 
-- 
2.30.0.452.gfb7fa4a1fd


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 3/4] c++: Delay normalizing nested requirements until satisfaction
  2021-02-10 14:41     ` Patrick Palka
@ 2021-02-12 19:11       ` Jason Merrill
  2021-02-28 17:40         ` Patrick Palka
  0 siblings, 1 reply; 29+ messages in thread
From: Jason Merrill @ 2021-02-12 19:11 UTC (permalink / raw)
  To: Patrick Palka; +Cc: gcc-patches

On 2/10/21 9:41 AM, Patrick Palka wrote:
> On Tue, 9 Feb 2021, Jason Merrill wrote:
> 
>> On 2/8/21 2:03 PM, Patrick Palka wrote:
>>> This sets up the functionality for controlling the initial set of
>>> template parameters to pass to normalization when dealing with a
>>> constraint-expression that is not associated with some constrained
>>> declaration, for instance when normalizing a nested requirement of a
>>> requires expression, or the constraints on a placeholder type.
>>>
>>> The main new ingredient here is the data member norm_info::initial_parms
>>> which can be set by callers of the normalization routines to communicate
>>> the in-scope template parameters for the supplied constraint-expression,
>>> rather than always falling back to using current_template_parms.
>>>
>>> This patch then uses this functionality in our handling of nested
>>> requirements so that we can delay normalizing them until needed for
>>> satisfaction.  We currently immediately normalize nested requirements at
>>> parse time, where we have the necessary template context, and cache the
>>> normal form in their TREE_TYPE node.  With this patch, we now delay
>>> normalization until needed (as with other constraint expressions), and
>>> instead store the current value of current_template_parms in their
>>> TREE_TYPE node (which we use to restore the template context at
>>> normalization time).
>>>
>>> In the subsequent patch, this functionality will also be used to
>>> normalize placeholder type constraints during auto deduction.
>>>
>>> gcc/cp/ChangeLog:
>>>
>>> 	* constraint.cc (build_parameter_mapping): Rely on the caller to
>>> 	determine the in-scope template parameters.
>>> 	(norm_info::norm_info): Delegate the one-parameter constructor
>>> 	to the two-parameter constructor.  In the two-parameter
>>> 	constructor, fold in the definition of make_context, set
>>> 	initial_parms appropriately, and don't set the now-removed
>>> 	orig_decl member.
>>> 	(norm_info::make_context): Remove, now that its only use is
>>> 	inlined into the caller.
>>> 	(norm_info::update_context): Adjust call to
>>> 	build_parameter_mapping to pass in the relevant set of in-scope
>>> 	template parameters.
>>> 	(norm_info::ctx_parms): Define this member function.
>>> 	(norm_info::context): Initialize to NULL_TREE.
>>> 	(norm_info::orig_decl): Remove this data member.
>>> 	(norm_info::initial_parms): Define this data member.
>>> 	(normalize_atom): Adjust call to build_parameter_mapping to pass
>>> 	in the relevant set of in-scope template parameters.  Use
>>> 	info.initial_parms instead of info.orig_decl.
>>> 	(normalize_constraint_expression): Define an overload that takes
>>> 	a norm_info object.  Cache the result of normalization.  Define
>>> 	the other overload in terms of this one, and handle a NESTED_REQ
>>> 	argument by setting info.initial_parms appropriately.
>>> 	(tsubst_nested_requirement): Go through
>>> 	satisfy_constraint_expression so that we normalize on demand.
>>> 	(finish_nested_requirement): Set the TREE_TYPE of the NESTED_REQ
>>> 	to current_template_parms.
>>> 	(diagnose_nested_requirements): Go through
>>> 	satisfy_constraint_expression, as with tsubst_nested_requirement.
>>> ---
>>>    gcc/cp/constraint.cc | 140 +++++++++++++++++++++++--------------------
>>>    gcc/cp/cp-tree.h     |   4 +-
>>>    2 files changed, 78 insertions(+), 66 deletions(-)
>>>
>>> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
>>> index 39c97986082..56134f8b2bf 100644
>>> --- a/gcc/cp/constraint.cc
>>> +++ b/gcc/cp/constraint.cc
>>> @@ -133,7 +133,7 @@ struct sat_info : subst_info
>>>      bool diagnose_unsatisfaction;
>>>    };
>>>    -static tree satisfy_constraint (tree, tree, sat_info);
>>> +static tree satisfy_constraint_expression (tree, tree, sat_info);
>>>      /* True if T is known to be some type other than bool. Note that this
>>>       is false for dependent types and errors.  */
>>> @@ -594,26 +594,12 @@ map_arguments (tree parms, tree args)
>>>      return parms;
>>>    }
>>>    -/* Build the parameter mapping for EXPR using ARGS.  */
>>> +/* Build the parameter mapping for EXPR using ARGS, where CTX_PARMS
>>> +   are the template parameters in scope for EXPR.  */
>>>      static tree
>>> -build_parameter_mapping (tree expr, tree args, tree decl)
>>> +build_parameter_mapping (tree expr, tree args, tree ctx_parms)
>>>    {
>>> -  tree ctx_parms = NULL_TREE;
>>> -  if (decl)
>>> -    {
>>> -      gcc_assert (TREE_CODE (decl) == TEMPLATE_DECL);
>>> -      ctx_parms = DECL_TEMPLATE_PARMS (decl);
>>> -    }
>>> -  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.  */
>>> -      ctx_parms = current_template_parms;
>>> -    }
>>> -
>>>      tree parms = find_template_parameters (expr, ctx_parms);
>>>      tree map = map_arguments (parms, args);
>>>      return map;
>>> @@ -645,53 +631,63 @@ parameter_mapping_equivalent_p (tree t1, tree t2)
>>>      struct norm_info : subst_info
>>>    {
>>> -  explicit norm_info (tsubst_flags_t complain)
>>> -    : subst_info (tf_warning_or_error | complain, NULL_TREE),
>>> -      context()
>>> +  explicit norm_info (tsubst_flags_t cmp)
>>> +    : norm_info (NULL_TREE, cmp)
>>>      {}
>>>        /* Construct a top-level context for DECL.  */
>>>        norm_info (tree in_decl, tsubst_flags_t complain)
>>> -    : subst_info (tf_warning_or_error | complain, in_decl),
>>> -      context (make_context (in_decl)),
>>> -      orig_decl (in_decl)
>>> -  {}
>>> -
>>> -  bool generate_diagnostics() const
>>> +    : subst_info (tf_warning_or_error | complain, in_decl)
>>>      {
>>> -    return complain & tf_norm;
>>> +    if (in_decl)
>>> +      {
>>> +	initial_parms = DECL_TEMPLATE_PARMS (in_decl);
>>> +	if (generate_diagnostics ())
>>> +	  context = build_tree_list (NULL_TREE, in_decl);
>>> +      }
>>> +    else
>>> +      initial_parms = current_template_parms;
>>>      }
>>>    -  tree make_context(tree in_decl)
>>> +  bool generate_diagnostics() const
>>>      {
>>> -    if (generate_diagnostics ())
>>> -      return build_tree_list (NULL_TREE, in_decl);
>>> -    return NULL_TREE;
>>> +    return complain & tf_norm;
>>>      }
>>>        void update_context(tree expr, tree args)
>>>      {
>>>        if (generate_diagnostics ())
>>>          {
>>> -	tree map = build_parameter_mapping (expr, args, in_decl);
>>> +	tree map = build_parameter_mapping (expr, args, ctx_parms ());
>>>    	context = tree_cons (map, expr, context);
>>>          }
>>>        in_decl = get_concept_check_template (expr);
>>>      }
>>>    +  /* Returns the template parameters that are in scope for the current
>>> +     normalization context.  */
>>> +
>>> +  tree ctx_parms()
>>> +  {
>>> +    if (in_decl)
>>> +      return DECL_TEMPLATE_PARMS (in_decl);
>>> +    else
>>> +      return initial_parms;
>>> +  }
>>> +
>>>      /* Provides information about the source of a constraint. This is a
>>>         TREE_LIST whose VALUE is either a concept check or a constrained
>>>         declaration. The PURPOSE, for concept checks is a parameter mapping
>>>         for that check.  */
>>>    -  tree context;
>>> +  tree context = NULL_TREE;
>>>        /* The declaration whose constraints we're normalizing.  The targets
>>>         of the parameter mapping of each atom will be in terms of the
>>>         template parameters of ORIG_DECL.  */
>>>    -  tree orig_decl = NULL_TREE;
>>> +  tree initial_parms = NULL_TREE;
>>>    };
>>>      static tree normalize_expression (tree, tree, norm_info);
>>> @@ -773,7 +769,7 @@ normalize_atom (tree t, tree args, norm_info info)
>>>        return normalize_concept_check (t, args, info);
>>>        /* Build the parameter mapping for the atom.  */
>>> -  tree map = build_parameter_mapping (t, args, info.in_decl);
>>> +  tree map = build_parameter_mapping (t, args, info.ctx_parms ());
>>>        /* Build a new info object for the atom.  */
>>>      tree ci = build_tree_list (t, info.context);
>>> @@ -803,10 +799,8 @@ normalize_atom (tree t, tree args, norm_info info)
>>>    	      tree target = TREE_PURPOSE (node);
>>>    	      TREE_VEC_ELT (targets, i++) = target;
>>>    	    }
>>> -	  tree ctx_parms = (info.orig_decl
>>> -			    ? DECL_TEMPLATE_PARMS (info.orig_decl)
>>> -			    : current_template_parms);
>>> -	  tree target_parms = find_template_parameters (targets, ctx_parms);
>>> +	  tree target_parms = find_template_parameters (targets,
>>> +							info.initial_parms);
>>>    	  TREE_TYPE (map) = target_parms;
>>>    	}
>>>    @@ -983,17 +977,43 @@ normalize_nontemplate_requirements (tree decl, bool
>>> diag = false)
>>>    /* Normalize an EXPR as a constraint.  */
>>>      static tree
>>> -normalize_constraint_expression (tree expr, bool diag)
>>> +normalize_constraint_expression (tree expr, norm_info info)
>>>    {
>>>      if (!expr || expr == error_mark_node)
>>>        return expr;
>>> +
>>> +  if (!info.generate_diagnostics ())
>>> +    if (tree *p = hash_map_safe_get (normalized_map, expr))
>>> +      return *p;
>>
>> It seems like we only want this for NESTED_REQ.
> 
> I figured it'd also be beneficial to cache the normal form of a
> placeholder type constraint, which will also goes through this overload.

True.  And if we change REQUIRES_EXPR handling to not go through here, 
it should be just the two.  OK, let's leave this alone.

>>>      ++processing_template_decl;
>>> -  norm_info info (diag ? tf_norm : tf_none);
>>>      tree norm = get_normalized_constraints (expr, info);
>>>      --processing_template_decl;
>>> +
>>> +  if (!info.generate_diagnostics ())
>>> +    hash_map_safe_put<hm_ggc> (normalized_map, expr, norm);
>>> +
>>>      return norm;
>>>    }
>>>    +/* High-level wrapper for the above.  */
>>> +
>>> +static tree
>>> +normalize_constraint_expression (tree expr, bool diag)
>>> +{
>>> +  norm_info info (diag ? tf_norm : tf_none);
>>
>> I wonder if we want to add a norm_info constructor taking a sat_info so we
>> don't need to mediate passing from one into the other with bool "diag"
>> parameters in various functions.  That doesn't need to happen in this patch.
> 
> Sounds good.  I think such a constructor would let us eliminate the
> bool-taking overload of normalize_constraint_expression altogether, if
> we move the special handling of NESTED_REQ to the norm_info-taking
> overload or to satisfy_constraint_expression.

>>> +  if (TREE_CODE (expr) == NESTED_REQ)
>>> +    {
>>> +      /* The TREE_TYPE contains the set of template parameters that were
>>> +	 in scope for this nested requiremen; use them as the initial template
>>> +	 parameters for normalization.  */
>>> +      info.initial_parms = TREE_TYPE (expr);
>>> +      return normalize_constraint_expression (TREE_OPERAND (expr, 0),
>>> info);
>>> +    }
>>> +  else
>>> +    return normalize_constraint_expression (expr, info);
>>> +}
>>
>> It seems like you don't want any other code to call the first overload, so
>> let's give the first one a different name to make that clearer.  The
>> differences in functionality don't follow naturally from the parameter types.
> 
> In the next patch, a new function normalize_placeholder_type_constraints
> will also call this norm_info-taking overload.  But I think we could
> remove the bool-taking overload at least, as mentioned above.

Sounds good.

>> Maybe this year we can cull/refactor the confusing set of overlapping and
>> ambiguously named functions that the concepts code already has:
>>
>> satisfy_constraint
>> satisfy_associated_constraints
>> satisfy_constraint_expression x2
>> constraint_satisfaction_value x2
>> constraints_satisfied_p x2
>> satisfy_declaration_constraints x2
>> evaluate_concept_check
> 
> Sounds good.  Some ideas:
> 
> satisfy_constraint could be renamed to something like
> satisfy_normalized_constraint or satisfy_normal_form.

Let's go with the former.

> satisfy_associated_constraints is just a small wrapper over
> satisfy_constraint, so we could remove it and have its two callers could
> check for type-dependence of the supplied template arguments themselves.

Yes, it seems like the callers should check for dependent template args 
sooner, before normalization.

> satisfy_constraint_expression should probably be renamed to something
> like satisfy_nondeclaration_constraints since it'd now handle more than
> just constraint-expressions (e.g. NESTED_REQs and placeholder types).

nested-requirements, placeholders, requires-expressions, and concept 
checks.  A set of things that (now) carry all the information they need 
for their own normalization.  Unless it's ever called for a concept 
check with a non-null args argument.  Maybe let's move the null args 
test to an assert inside the concept-check case.

> The one-parameter version of satisfy_constraint_expression has just one
> caller, and this use could be replaced with tsubst_requires_expr I think.

Agreed; a requires-expression is already atomic, it isn't useful to 
normalize it, and better not to clutter up the hash table with them.

Incidentally, though, I don't mind overloads like this that just forward 
to the main implementation.  The ones that bother me are the ones that 
duplicate a lot of the code, like constraint_satisfaction_value.

> It seems we could straightforwardly merge the two
> constraint_satisfaction_value overloads, which would also let us merge
> the constraints_satisfied_p overloads.

Sounds good.

> We could also merge tsubst_requires_expr routines with the
> diagnose_requires_expr ones.

Sounds good.

>>>    /* 17.4.1.2p2. Two constraints are identical if they are formed
>>>       from the same expression and the targets of the parameter mapping
>>>       are equivalent.  */
>>> @@ -2086,16 +2106,14 @@ tsubst_compound_requirement (tree t, tree args,
>>> subst_info info)
>>>    static tree
>>>    tsubst_nested_requirement (tree t, tree args, subst_info info)
>>>    {
>>> -  /* Perform satisfaction quietly with the regular normal form.  */
>>> +  /* Perform satisfaction quietly first.  */
>>>      sat_info quiet (tf_none, info.in_decl);
>>> -  tree norm = TREE_VALUE (TREE_TYPE (t));
>>> -  tree diag_norm = TREE_PURPOSE (TREE_TYPE (t));
>>> -  tree result = satisfy_constraint (norm, args, quiet);
>>> +  tree result = satisfy_constraint_expression (t, args, quiet);
>>>      if (result == error_mark_node)
>>>        {
>>> -      /* Replay the error using the diagnostic normal form.  */
>>> +      /* Replay the error.  */
>>>          sat_info noisy (tf_warning_or_error, info.in_decl);
>>> -      satisfy_constraint (diag_norm, args, noisy);
>>> +      satisfy_constraint_expression (t, args, noisy);
>>>        }
>>>      if (result != boolean_true_node)
>>>        return error_mark_node;
>>> @@ -3301,15 +3319,9 @@ finish_compound_requirement (location_t loc, tree
>>> expr, tree type, bool noexcept
>>>    tree
>>>    finish_nested_requirement (location_t loc, tree expr)
>>>    {
>>> -  /* We need to normalize the constraints now, at parse time, while
>>> -     we have the necessary template context.  We normalize twice,
>>> -     once without diagnostic information and once with, which we'll
>>> -     later use for quiet and noisy satisfaction respectively.  */
>>> -  tree norm = normalize_constraint_expression (expr, /*diag=*/false);
>>> -  tree diag_norm = normalize_constraint_expression (expr, /*diag=*/true);
>>> -
>>> -  /* Build the constraint, saving its two normalizations as its type.  */
>>> -  tree r = build1 (NESTED_REQ, build_tree_list (diag_norm, norm), expr);
>>> +  /* Build the requirement, saving the set of in-scope template
>>> +     parameters as its type.  */
>>> +  tree r = build1 (NESTED_REQ, current_template_parms, expr);
>>>      SET_EXPR_LOCATION (r, loc);
>>>      return r;
>>>    }
>>> @@ -3710,12 +3722,9 @@ diagnose_type_requirement (tree req, tree args, tree
>>> in_decl)
>>>    static void
>>>    diagnose_nested_requirement (tree req, tree args)
>>>    {
>>> -  /* Quietly check for satisfaction first using the regular normal form.
>>> -     We can elaborate details later if needed.  */
>>> -  tree norm = TREE_VALUE (TREE_TYPE (req));
>>> -  tree diag_norm = TREE_PURPOSE (TREE_TYPE (req));
>>> -  sat_info info (tf_none, NULL_TREE);
>>> -  tree result = satisfy_constraint (norm, args, info);
>>> +  /* Quietly check for satisfaction first.  */
>>> +  sat_info quiet (tf_none, NULL_TREE);
>>> +  tree result = satisfy_constraint_expression (req, args, quiet);
>>>      if (result == boolean_true_node)
>>>        return;
>>>    @@ -3723,10 +3732,11 @@ diagnose_nested_requirement (tree req, tree args)
>>>      location_t loc = cp_expr_location (expr);
>>>      if (diagnosing_failed_constraint::replay_errors_p ())
>>>        {
>>> -      /* Replay the substitution error using the diagnostic normal form.
>>> */
>>> +      /* Replay the substitution error with re-normalized requirements.  */
>>>          inform (loc, "nested requirement %qE is not satisfied, because",
>>> expr);
>>> +
>>>          sat_info noisy (tf_warning_or_error, NULL_TREE,
>>> /*diag_unsat=*/true);
>>> -      satisfy_constraint (diag_norm, args, noisy);
>>> +      satisfy_constraint_expression (req, args, noisy);
>>>        }
>>>      else
>>>        inform (loc, "nested requirement %qE is not satisfied", expr);
>>> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
>>> index 970ed5e77bb..26fbf1eb663 100644
>>> --- a/gcc/cp/cp-tree.h
>>> +++ b/gcc/cp/cp-tree.h
>>> @@ -1587,7 +1587,9 @@ check_constraint_info (tree t)
>>>      TREE_LANG_FLAG_0 (TREE_CHECK (NODE, COMPOUND_REQ))
>>>      /* The constraints on an 'auto' placeholder type, used in an argument
>>> deduction
>>> -   constraint.  */
>>> +   constraint.  This should usually be set via
>>> set_placeholder_type_constraints
>>> +   since we also need to record the relevant set of in-scope template
>>> parameters
>>> +   for later normalization. */
>>>    #define PLACEHOLDER_TYPE_CONSTRAINTS(NODE) \
>>>      DECL_SIZE_UNIT (TYPE_NAME (NODE))
>>>    
>>
>>
> 


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 4/4] c++: dependent constraint on placeholder 'auto' [PR96443]
  2021-02-11 22:14     ` Patrick Palka
@ 2021-02-12 19:14       ` Jason Merrill
  2021-02-28 17:55         ` Patrick Palka
  0 siblings, 1 reply; 29+ messages in thread
From: Jason Merrill @ 2021-02-12 19:14 UTC (permalink / raw)
  To: Patrick Palka; +Cc: gcc-patches

On 2/11/21 5:14 PM, Patrick Palka wrote:
> On Thu, 11 Feb 2021, Jason Merrill wrote:
> 
>> On 2/8/21 2:03 PM, Patrick Palka wrote:
>>> This fixes the way we check satisfaction of constraints on placeholder
>>> types in various contexts, and in particular when the constraint is
>>> dependent.
>>>
>>> Firstly, when evaluating the return type requirement of a compound
>>> requirement, we currently substitute the outer template arguments into
>>> the constraint before checking satisfaction. But we should instead be
>>> passing in the complete set of template arguments to satisfaction and
>>> not do a prior separate substitution.  Our current approach leads to us
>>> incorrectly rejecting the testcase concepts-return-req2.C below.
>>>
>>> Secondly, when checking the constraints on a placeholder variable or
>>> return type, we don't substitute the template arguments of the enclosing
>>> context at all.  This leads to bogus errors during satisfaction when the
>>> constraint is dependent as in the testcase concepts-placeholder3.C
>>> below.
>>>
>>> In order to fix these two issues, we need to be able to properly
>>> normalize the constraints on a placeholder 'auto', which in turn
>>> requires us to know the template parameters that were in-scope where an
>>> 'auto' was introduced.  This information currently doesn't seem to be
>>> easily available when we need it, so this patch adds an auxiliary hash
>>> table that keeps track of the value of current_template_parms when each
>>> constrained 'auto' was formed.
>>>
>>> This patch also removes some seemingly wrong handling of placeholder
>>> type arguments from tsubst_parameter_mapping.  The code doesn't trigger
>>> with the example used in the comments, because type_uses_auto doesn't
>>> look inside non-deduced contexts such as the operand of decltype.  And
>>> the call to do_auto_deduction seems confused because if 'arg' is a type,
>>> then so is 'parm', and therefore 'init' too is a type, but
>>> do_auto_deduction expects it to be an expression.  Before this patch,
>>> this code was dead (as far as our testsuite can tell), but now it breaks
>>> other parts of this patch, so let's remove it.
>>>
>>> gcc/cp/ChangeLog:
>>>
>>> 	PR c++/96443
>>> 	* constraint.cc (type_deducible_p): Don't substitute into the
>>> 	constraints, and instead just pass 'args' to do_auto_deduction
>>> 	as the outer template arguments.
>>> 	(tsubst_parameter_mapping): Remove confused code for handling
>>> 	placeholder type arguments.
>>> 	(normalize_placeholder_type_constraint): Define.
>>> 	(satisfy_constraint_expression): Use it to handle placeholder
>>> 	'auto' types.
>>> 	* cp-tree.h (get_constrained_auto_context): Declare.
>>> 	* pt.c (constrained_auto_context_map): Define.
>>> 	(get_placeholder_type_constraint_context): Define.
>>> 	(set_placeholder_type_constraints): Define.
>>> 	(copy_placeholder_type_constraints): Define.
>>> 	(tsubst) <case TEMPLATE_TYPE_PARM>: Use
>>> 	copy_placeholder_type_constraints.
>>> 	(make_constrained_placeholder_type): Use
>>> 	set_placeholder_type_constraints.
>>> 	(do_auto_deduction): Clarify comments about the outer_targs
>>> 	parameter.  Rework satisfaction of a placeholder type constraint
>>> 	to pass in the complete set of template arguments directly to
>>> 	constraints_satisfied_p.
>>> 	(splice_late_return_type): Use copy_placeholder_type_constraints.
>>>
>>> gcc/testsuite/ChangeLog:
>>>
>>> 	PR c++/96443
>>> 	* g++.dg/cpp2a/concepts-placeholder3.C: New test.
>>> 	* g++.dg/cpp2a/concepts-return-req2.C: New test.
>>> 	* g++.dg/concepts-ts1.C: Add dg-bogus directive to the call to
>>> 	f15 that we expect to accept.
>>> ---
>>>    gcc/cp/constraint.cc                          | 106 ++++++++----------
>>>    gcc/cp/cp-tree.h                              |   1 +
>>>    gcc/cp/pt.c                                   | 101 +++++++++++------
>>>    .../g++.dg/cpp2a/concepts-placeholder3.C      |  19 ++++
>>>    .../g++.dg/cpp2a/concepts-return-req2.C       |  13 +++
>>>    gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C     |   2 +-
>>>    6 files changed, 146 insertions(+), 96 deletions(-)
>>>    create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-placeholder3.C
>>>    create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-return-req2.C
>>>
>>> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
>>> index 56134f8b2bf..53588047d44 100644
>>> --- a/gcc/cp/constraint.cc
>>> +++ b/gcc/cp/constraint.cc
>>> @@ -2007,39 +2007,19 @@ type_deducible_p (tree expr, tree type, tree
>>> placeholder, tree args,
>>>         references are preserved in the result.  */
>>>      expr = force_paren_expr_uneval (expr);
>>>    -  /* Replace the constraints with the instantiated constraints. This
>>> -     substitutes args into any template parameters in the trailing
>>> -     result type.  */
>>> -  tree saved_constr = PLACEHOLDER_TYPE_CONSTRAINTS (placeholder);
>>> -  tree subst_constr
>>> -    = tsubst_constraint (saved_constr,
>>> -			 args,
>>> -			 info.complain | tf_partial,
>>> -			 info.in_decl);
>>> -
>>> -  if (subst_constr == error_mark_node)
>>> -    return false;
>>> -
>>> -  PLACEHOLDER_TYPE_CONSTRAINTS (placeholder) = subst_constr;
>>> -
>>> -  /* Temporarily unlink the canonical type.  */
>>> -  tree saved_type = TYPE_CANONICAL (placeholder);
>>> -  TYPE_CANONICAL (placeholder) = NULL_TREE;
>>> -
>>> -  tree deduced_type
>>> -    = do_auto_deduction (type,
>>> -			 expr,
>>> -			 placeholder,
>>> -			 info.complain,
>>> -			 adc_requirement);
>>> -
>>> -  PLACEHOLDER_TYPE_CONSTRAINTS (placeholder) = saved_constr;
>>> -  TYPE_CANONICAL (placeholder) = saved_type;
>>> +  /* When args is empty, we're evaluating a non-templated requires
>>> expression,
>>> +     but even those are parsed under processing_template_decl == 1, and so
>>> the
>>> +     placeholder 'auto' inside this return-type-requirement has level 2.
>>> In
>>> +     order to have all parms and arguments match up for satisfaction, we
>>> need
>>> +     to pass a single level as OUTER_TARGS in this case.  */
>>> +  if (!args)
>>> +    args = make_tree_vec (0);
>>>    -  if (deduced_type == error_mark_node)
>>> -    return false;
>>> +  tree deduced_type = do_auto_deduction (type, expr, placeholder,
>>> +					 info.complain, adc_requirement,
>>> +					 /*outer_targs=*/args);
>>>    -  return true;
>>> +  return deduced_type != error_mark_node;
>>>    }
>>>      /* True if EXPR can not be converted to TYPE.  */
>>> @@ -2304,35 +2284,10 @@ tsubst_parameter_mapping (tree map, tree args,
>>> subst_info info)
>>>            return error_mark_node;
>>>          tree parm = TREE_VALUE (p);
>>>          tree arg = TREE_PURPOSE (p);
>>> -      tree new_arg = NULL_TREE;
>>> -      if (TYPE_P (arg))
>>> -        {
>>> -          /* If a template parameter is declared with a placeholder, we can
>>> -             get those in the argument list if decltype is applied to the
>>> -             placeholder. For example:
>>> -
>>> -		template<auto T>
>>> -		  requires C<decltype(T)>
>>> -		void f() { }
>>> -
>>> -	     The normalized argument for C will be an auto type, so we'll
>>> -             need to deduce the actual argument from the corresponding
>>> -             initializer (whatever argument is provided for T), and use
>>> -             that result in the instantiated parameter mapping.  */
>>> -          if (tree auto_node = type_uses_auto (arg))
>>> -            {
>>> -              int level;
>>> -              int index;
>>> -	      template_parm_level_and_index (parm, &level, &index);
>>> -	      tree init = TMPL_ARG (args, level, index);
>>> -              new_arg = do_auto_deduction (arg, init, auto_node,
>>> -					   complain, adc_variable_type,
>>> -					   make_tree_vec (0));
>>> -            }
>>> -        }
>>> -      else if (ARGUMENT_PACK_P (arg))
>>> +      tree new_arg;
>>> +      if (ARGUMENT_PACK_P (arg))
>>>    	new_arg = tsubst_argument_pack (arg, args, complain, in_decl);
>>> -      if (!new_arg)
>>> +      else
>>>    	{
>>>    	  new_arg = tsubst_template_arg (arg, args, complain, in_decl);
>>>    	  if (TYPE_P (new_arg))
>>> @@ -3038,6 +2993,31 @@ satisfy_associated_constraints (tree t, tree args,
>>> sat_info info)
>>>      return satisfy_constraint (t, args, info);
>>>    }
>>>    +/* Normalize the constraints on the placeholder 'auto' type T.  */
>>> +
>>> +tree
>>> +normalize_placeholder_type_constraints (tree t, bool diag)
>>> +{
>>> +  gcc_assert (is_auto (t));
>>> +  tree constr = PLACEHOLDER_TYPE_CONSTRAINTS (t);
>>> +  if (!constr)
>>> +    return NULL_TREE;
>>> +
>>> +  tree initial_parms = get_constrained_auto_context (t);
>>> +  /* The 'auto' itself is used as the first argument in its own
>>> constraints,
>>> +     and its level is one greater than its template context, so in order to
>>> +     capture all used template parameters we need to add an extra level of
>>> +     template parameters to the context; a dummy level suffices.  */
>>> +  initial_parms
>>> +    = tree_cons (size_int (initial_parms
>>> +			   ? TMPL_PARMS_DEPTH (initial_parms) + 1 : 1),
>>> +		 make_tree_vec (0), initial_parms);
>>> +
>>> +  norm_info info (diag ? tf_norm : tf_none);
>>> +  info.initial_parms = initial_parms;
>>> +  return normalize_constraint_expression (constr, info);
>>> +}
>>> +
>>>    /* Evaluate EXPR as a constraint expression using ARGS, returning a
>>>       satisfaction value. */
>>>    @@ -3047,8 +3027,6 @@ satisfy_constraint_expression (tree t, tree args,
>>> sat_info info)
>>>      if (t == error_mark_node)
>>>        return error_mark_node;
>>>    -  gcc_assert (EXPR_P (t));
>>> -
>>>      /* Get the normalized constraints.  */
>>>      tree norm;
>>>      if (args == NULL_TREE && concept_check_p (t))
>>> @@ -3058,8 +3036,12 @@ satisfy_constraint_expression (tree t, tree args,
>>> sat_info info)
>>>          tree tmpl = get_concept_check_template (id);
>>>          norm = normalize_concept_definition (tmpl, info.noisy ());
>>>        }
>>> -  else
>>> +  else if (EXPR_P (t))
>>>        norm = normalize_constraint_expression (t, info.noisy ());
>>> +  else if (is_auto (t))
>>> +    norm = normalize_placeholder_type_constraints (t, info.noisy ());
>>> +  else
>>> +    gcc_unreachable ();
>>>        /* Perform satisfaction.  */
>>>      return satisfy_constraint (norm, args, info);
>>> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
>>> index 26fbf1eb663..ca5550ccb6d 100644
>>> --- a/gcc/cp/cp-tree.h
>>> +++ b/gcc/cp/cp-tree.h
>>> @@ -7090,6 +7090,7 @@ extern tree make_auto
>>> (void);
>>>    extern tree make_decltype_auto			(void);
>>>    extern tree make_constrained_auto		(tree, tree);
>>>    extern tree make_constrained_decltype_auto	(tree, tree);
>>> +extern tree get_constrained_auto_context	(tree);
>>>    extern tree make_template_placeholder		(tree);
>>>    extern bool template_placeholder_p		(tree);
>>>    extern bool ctad_template_p			(tree);
>>> diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
>>> index bceb942e79a..bcf9c9a3d9a 100644
>>> --- a/gcc/cp/pt.c
>>> +++ b/gcc/cp/pt.c
>>> @@ -15364,6 +15364,46 @@ tsubst_tree_list (tree t, tree args, tsubst_flags_t
>>> complain, tree in_decl)
>>>      return chain;
>>>    }
>>>    +/* A hash table mapping a constrained 'auto' to the set of in-scope
>>> +   template parameters from where the 'auto' was introduced.  */
>>> +
>>> +static GTY((cache)) decl_tree_cache_map *constrained_auto_context_map;
>>
>> How about turning PLACEHOLDER_TYPE_CONSTRAINTS into a TREE_LIST instead of
>> introducing a new hash table?
> 
> Nice, this cleaner approach works well, and was quite straightforward to
> implement.  How does this look?  Tested on x86_64-pc-linux-gnu,
> range-v3  and cmcstl2.

Looks good.

> -- >8 --
> 
> Subject: [PATCH] c++: Fix satisfaction of placeholder type constraints
>   [PR96443]
> 
> This fixes the way we check satisfaction of constraints on placeholder
> types in various deduction contexts, and in particular when the
> constraint is dependent.
> 
> Firstly, when evaluating the return type requirement of a compound
> requirement, we currently substitute the outer template arguments into
> the constraint before checking satisfaction. But we should instead be
> passing in the complete set of template arguments to satisfaction and
> not do a prior separate substitution.  Our current approach leads to us
> incorrectly rejecting the testcase concepts-return-req2.C below.
> 
> Secondly, when checking the constraints on a placeholder variable or
> return type, we don't consider the template arguments of the enclosing
> context at all.  This leads to bogus errors during satisfaction when the
> constraint is dependent as in the testcase concepts-placeholder3.C
> below.
> 
> In order to fix these two issues, we need to be able to normalize the
> constraints on a placeholder 'auto', which in turn requires us to know
> the template parameters that were in scope where the 'auto' was
> introduced.  This information currently doesn't seem to be easily
> available when we need it, so this patch turns PLACEHOLDER_TYPE_CONSTRAINTS
> into a TREE_LIST whose TREE_PURPOSE holds the value of
> current_template_parms whence a constrained 'auto' was formed.
> 
> This patch also removes some seemingly wrong handling of placeholder
> type arguments from tsubst_parameter_mapping.  The code doesn't trigger
> with the example used in the comments, because type_uses_auto doesn't
> look inside non-deduced contexts such as the operand of decltype.  And
> the call to do_auto_deduction seems confused because if 'arg' is a type,
> then so is 'parm', and therefore 'init' too is a type, but
> do_auto_deduction expects it to be an expression.  Before this patch,
> this code was dead (as far as our testsuite can tell), but now it breaks
> other parts of this patch, so let's remove it.
> 
> gcc/cp/ChangeLog:
> 
> 	PR c++/96443
> 	* constraint.cc (type_deducible_p): Don't substitute into the
> 	constraints, and instead just pass 'args' to do_auto_deduction
> 	as the outer template arguments.
> 	(tsubst_parameter_mapping): Remove confused code for handling
> 	placeholder type arguments.
> 	(normalize_placeholder_type_constraint): Define.
> 	(satisfy_constraint_expression): Use it to handle placeholder
> 	'auto' types.
> 	* cp-tree.h (PLACEHOLDER_TYPE_CONSTRAINTS_INFO): Define.
> 	(PLACEHOLDER_TYPE_CONSTRAINTS): Redefine in terms of the above.
> 	* pt.c (tsubst) <case TEMPLATE_TYPE_PARM>: Use
> 	PLACEHOLDER_TYPE_CONSTRAINTS_INFO instead.
> 	(make_constrained_placeholder_type): Set
> 	PLACEHOLDER_TYPE_CONSTRAINTS_INFO instead.
> 	(do_auto_deduction): Clarify comments about the outer_targs
> 	parameter.  Rework satisfaction of a placeholder type constraint
> 	to pass in the complete set of template arguments directly to
> 	constraints_satisfied_p.
> 	(splice_late_return_type): Use PLACEHOLDER_TYPE_CONSTRAINTS_INFO
> 	instead.
> 
> gcc/testsuite/ChangeLog:
> 
> 	PR c++/96443
> 	* g++.dg/cpp2a/concepts-placeholder3.C: New test.
> 	* g++.dg/cpp2a/concepts-return-req2.C: New test.
> 	* g++.dg/concepts-ts1.C: Add dg-bogus directive to the call to
> 	f15 that we expect to accept.
> ---
>   gcc/cp/constraint.cc                          | 108 ++++++++----------
>   gcc/cp/cp-tree.h                              |  18 ++-
>   gcc/cp/pt.c                                   |  67 +++++------
>   .../g++.dg/cpp2a/concepts-placeholder3.C      |  19 +++
>   .../g++.dg/cpp2a/concepts-return-req2.C       |  13 +++
>   gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C     |   2 +-
>   6 files changed, 122 insertions(+), 105 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-placeholder3.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-return-req2.C
> 
> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> index a58b428c067..c26e3fae114 100644
> --- a/gcc/cp/constraint.cc
> +++ b/gcc/cp/constraint.cc
> @@ -2007,39 +2007,19 @@ type_deducible_p (tree expr, tree type, tree placeholder, tree args,
>        references are preserved in the result.  */
>     expr = force_paren_expr_uneval (expr);
>   
> -  /* Replace the constraints with the instantiated constraints. This
> -     substitutes args into any template parameters in the trailing
> -     result type.  */
> -  tree saved_constr = PLACEHOLDER_TYPE_CONSTRAINTS (placeholder);
> -  tree subst_constr
> -    = tsubst_constraint (saved_constr,
> -			 args,
> -			 info.complain | tf_partial,
> -			 info.in_decl);
> -
> -  if (subst_constr == error_mark_node)
> -    return false;
> -
> -  PLACEHOLDER_TYPE_CONSTRAINTS (placeholder) = subst_constr;
> -
> -  /* Temporarily unlink the canonical type.  */
> -  tree saved_type = TYPE_CANONICAL (placeholder);
> -  TYPE_CANONICAL (placeholder) = NULL_TREE;
> -
> -  tree deduced_type
> -    = do_auto_deduction (type,
> -			 expr,
> -			 placeholder,
> -			 info.complain,
> -			 adc_requirement);
> -
> -  PLACEHOLDER_TYPE_CONSTRAINTS (placeholder) = saved_constr;
> -  TYPE_CANONICAL (placeholder) = saved_type;
> +  /* When args is empty, we're evaluating a non-templated requires expression,
> +     but even those are parsed under processing_template_decl == 1, and so the
> +     placeholder 'auto' inside this return-type-requirement has level 2.  In
> +     order to have all parms and arguments match up for satisfaction, we need
> +     to pass a single level of OUTER_TARGS in this case.  */
> +  if (!args)
> +    args = make_tree_vec (0);
>   
> -  if (deduced_type == error_mark_node)
> -    return false;
> +  tree deduced_type = do_auto_deduction (type, expr, placeholder,
> +					 info.complain, adc_requirement,
> +					 /*outer_targs=*/args);
>   
> -  return true;
> +  return deduced_type != error_mark_node;
>   }
>   
>   /* True if EXPR can not be converted to TYPE.  */
> @@ -2304,35 +2284,10 @@ tsubst_parameter_mapping (tree map, tree args, subst_info info)
>           return error_mark_node;
>         tree parm = TREE_VALUE (p);
>         tree arg = TREE_PURPOSE (p);
> -      tree new_arg = NULL_TREE;
> -      if (TYPE_P (arg))
> -        {
> -          /* If a template parameter is declared with a placeholder, we can
> -             get those in the argument list if decltype is applied to the
> -             placeholder. For example:
> -
> -		template<auto T>
> -		  requires C<decltype(T)>
> -		void f() { }
> -
> -	     The normalized argument for C will be an auto type, so we'll
> -             need to deduce the actual argument from the corresponding
> -             initializer (whatever argument is provided for T), and use
> -             that result in the instantiated parameter mapping.  */
> -          if (tree auto_node = type_uses_auto (arg))
> -            {
> -              int level;
> -              int index;
> -	      template_parm_level_and_index (parm, &level, &index);
> -	      tree init = TMPL_ARG (args, level, index);
> -              new_arg = do_auto_deduction (arg, init, auto_node,
> -					   complain, adc_variable_type,
> -					   make_tree_vec (0));
> -            }
> -        }
> -      else if (ARGUMENT_PACK_P (arg))
> +      tree new_arg;
> +      if (ARGUMENT_PACK_P (arg))
>   	new_arg = tsubst_argument_pack (arg, args, complain, in_decl);
> -      if (!new_arg)
> +      else
>   	{
>   	  new_arg = tsubst_template_arg (arg, args, complain, in_decl);
>   	  if (TYPE_P (new_arg))
> @@ -3038,6 +2993,33 @@ satisfy_associated_constraints (tree t, tree args, sat_info info)
>     return satisfy_constraint (t, args, info);
>   }
>   
> +/* Return the normal form of the constraints on the placeholder 'auto'
> +   type T.  */
> +
> +tree
> +normalize_placeholder_type_constraints (tree t, bool diag)
> +{
> +  gcc_assert (is_auto (t));
> +  tree ci = PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t);
> +  if (!ci)
> +    return NULL_TREE;
> +
> +  tree constr = TREE_VALUE (ci);
> +  tree initial_parms = TREE_PURPOSE (ci);
> +  /* The 'auto' itself is used as the first argument in its own constraints,
> +     and its level is one greater than its template depth.  So in order to
> +     capture all used template parameters, we need to add an extra level of
> +     template parameters to the context; a dummy level suffices.  */
> +  initial_parms
> +    = tree_cons (size_int (initial_parms
> +			   ? TMPL_PARMS_DEPTH (initial_parms) + 1 : 1),
> +		 make_tree_vec (0), initial_parms);
> +
> +  norm_info info (diag ? tf_norm : tf_none);
> +  info.initial_parms = initial_parms;
> +  return normalize_constraint_expression (constr, info);
> +}
> +
>   /* Evaluate EXPR as a constraint expression using ARGS, returning a
>      satisfaction value. */
>   
> @@ -3047,8 +3029,6 @@ satisfy_constraint_expression (tree t, tree args, sat_info info)
>     if (t == error_mark_node)
>       return error_mark_node;
>   
> -  gcc_assert (EXPR_P (t));
> -
>     /* Get the normalized constraints.  */
>     tree norm;
>     if (args == NULL_TREE && concept_check_p (t))
> @@ -3058,8 +3038,12 @@ satisfy_constraint_expression (tree t, tree args, sat_info info)
>         tree tmpl = get_concept_check_template (id);
>         norm = normalize_concept_definition (tmpl, info.noisy ());
>       }
> -  else
> +  else if (EXPR_P (t))
>       norm = normalize_constraint_expression (t, info.noisy ());
> +  else if (is_auto (t))
> +    norm = normalize_placeholder_type_constraints (t, info.noisy ());
> +  else
> +    gcc_unreachable ();
>   
>     /* Perform satisfaction.  */
>     return satisfy_constraint (norm, args, info);
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index ce24247fc5b..af2a592470c 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -1588,13 +1588,19 @@ check_constraint_info (tree t)
>   #define COMPOUND_REQ_NOEXCEPT_P(NODE) \
>     TREE_LANG_FLAG_0 (TREE_CHECK (NODE, COMPOUND_REQ))
>   
> -/* The constraints on an 'auto' placeholder type, used in an argument deduction
> -   constraint.  This should usually be set via set_placeholder_type_constraints
> -   since we also need to record the relevant set of in-scope template parameters
> -   for later normalization. */
> -#define PLACEHOLDER_TYPE_CONSTRAINTS(NODE) \
> +/* A TREE_LIST whose TREE_VALUE is the constraints on the 'auto' placeholder
> +   type NODE, used in an argument deduction constraint.  The TREE_PURPOSE
> +   holds the set of template parameters that were in-scope when this 'auto'
> +   was formed.  */
> +#define PLACEHOLDER_TYPE_CONSTRAINTS_INFO(NODE) \
>     DECL_SIZE_UNIT (TYPE_NAME (NODE))
>   
> +/* The constraints on the 'auto' placeholder type NODE.  */
> +#define PLACEHOLDER_TYPE_CONSTRAINTS(NODE)		   \
> +  (PLACEHOLDER_TYPE_CONSTRAINTS_INFO (NODE)		   \
> +   ? TREE_VALUE (PLACEHOLDER_TYPE_CONSTRAINTS_INFO (NODE)) \
> +   : NULL_TREE)
> +
>   /* True if NODE is a constraint.  */
>   #define CONSTR_P(NODE)                  \
>     (TREE_CODE (NODE) == ATOMIC_CONSTR    \
> @@ -8433,7 +8439,7 @@ set_implicit_rvalue_p (tree ot)
>   inline bool
>   is_constrained_auto (const_tree t)
>   {
> -  return is_auto (t) && PLACEHOLDER_TYPE_CONSTRAINTS (t);
> +  return is_auto (t) && PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t);
>   }
>   
>   /* RAII class to push/pop class scope T; if T is not a class, do nothing.  */
> diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
> index c6c9c1dd4b1..15ef6d7565f 100644
> --- a/gcc/cp/pt.c
> +++ b/gcc/cp/pt.c
> @@ -15692,15 +15692,15 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
>   			       ? tf_ignore_bad_quals : 0));
>   	      }
>   	    else if (TREE_CODE (t) == TEMPLATE_TYPE_PARM
> -		     && PLACEHOLDER_TYPE_CONSTRAINTS (t)
> +		     && PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t)
>   		     && (r = (TEMPLATE_PARM_DESCENDANTS
>   			      (TEMPLATE_TYPE_PARM_INDEX (t))))
>   		     && (r = TREE_TYPE (r))
> -		     && !PLACEHOLDER_TYPE_CONSTRAINTS (r))
> +		     && !PLACEHOLDER_TYPE_CONSTRAINTS_INFO (r))
>   	      /* Break infinite recursion when substituting the constraints
>   		 of a constrained placeholder.  */;
>   	    else if (TREE_CODE (t) == TEMPLATE_TYPE_PARM
> -		     && !PLACEHOLDER_TYPE_CONSTRAINTS (t)
> +		     && !PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t)
>   		     && !CLASS_PLACEHOLDER_TEMPLATE (t)
>   		     && (arg = TEMPLATE_TYPE_PARM_INDEX (t),
>   			 r = TEMPLATE_PARM_DESCENDANTS (arg))
> @@ -15723,8 +15723,8 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
>   		  {
>   		    /* Propagate constraints on placeholders since they are
>   		       only instantiated during satisfaction.  */
> -		    if (tree constr = PLACEHOLDER_TYPE_CONSTRAINTS (t))
> -		      PLACEHOLDER_TYPE_CONSTRAINTS (r) = constr;
> +		    if (tree ci = PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t))
> +		      PLACEHOLDER_TYPE_CONSTRAINTS_INFO (r) = ci;
>   		    else if (tree pl = CLASS_PLACEHOLDER_TEMPLATE (t))
>   		      {
>   			pl = tsubst_copy (pl, args, complain, in_decl);
> @@ -28155,7 +28155,8 @@ make_constrained_placeholder_type (tree type, tree con, tree args)
>     expr = build_concept_check (expr, type, args, tf_warning_or_error);
>     --processing_template_decl;
>   
> -  PLACEHOLDER_TYPE_CONSTRAINTS (type) = expr;
> +  PLACEHOLDER_TYPE_CONSTRAINTS_INFO (type)
> +    = build_tree_list (current_template_parms, expr);
>   
>     /* Our canonical type depends on the constraint.  */
>     TYPE_CANONICAL (type) = canonical_type_parameter (type);
> @@ -29394,9 +29395,11 @@ do_class_deduction (tree ptype, tree tmpl, tree init,
>      from INIT.  AUTO_NODE is the TEMPLATE_TYPE_PARM used for 'auto' in TYPE.
>      The CONTEXT determines the context in which auto deduction is performed
>      and is used to control error diagnostics.  FLAGS are the LOOKUP_* flags.
> -   OUTER_TARGS are used during template argument deduction
> -   (context == adc_unify) to properly substitute the result, and is ignored
> -   in other contexts.
> +
> +   OUTER_TARGS is used during template argument deduction (context == adc_unify)
> +   to properly substitute the result.  It's also used in the adc_unify and
> +   adc_requirement contexts to communicate the the necessary template arguments
> +   to satisfaction.  OUTER_TARGS is ignored in other contexts.
>   
>      For partial-concept-ids, extra args may be appended to the list of deduced
>      template arguments prior to determining constraint satisfaction.  */
> @@ -29557,30 +29560,21 @@ do_auto_deduction (tree type, tree init, tree auto_node,
>       }
>   
>     /* Check any placeholder constraints against the deduced type. */
> -  if (flag_concepts && !processing_template_decl)
> -    if (tree check = NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
> +  if (flag_concepts)
> +    if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
>         {
> -        /* Use the deduced type to check the associated constraints. If we
> -           have a partial-concept-id, rebuild the argument list so that
> -           we check using the extra arguments. */
> -	check = unpack_concept_check (check);
> -	gcc_assert (TREE_CODE (check) == TEMPLATE_ID_EXPR);
> -	tree cdecl = TREE_OPERAND (check, 0);
> -	if (OVL_P (cdecl))
> -	  cdecl = OVL_FIRST (cdecl);
> -        tree cargs = TREE_OPERAND (check, 1);
> -        if (TREE_VEC_LENGTH (cargs) > 1)
> -          {
> -            cargs = copy_node (cargs);
> -            TREE_VEC_ELT (cargs, 0) = TREE_VEC_ELT (targs, 0);
> -          }
> -        else
> -          cargs = targs;
> +	if (processing_template_decl)
> +	  /* In general we can't check satisfaction until we know all
> +	     template arguments.  */
> +	  return type;
>   
> -	/* Rebuild the check using the deduced arguments.  */
> -	check = build_concept_check (cdecl, cargs, tf_none);
> +	if ((context == adc_return_type || context == adc_variable_type)
> +	    && current_function_decl
> +	    && DECL_TEMPLATE_INFO (current_function_decl))
> +	  outer_targs = DECL_TI_ARGS (current_function_decl);
>   
> -	if (!constraints_satisfied_p (check))
> +	tree complete_targs = add_to_template_args (outer_targs, targs);
> +	if (!constraints_satisfied_p (auto_node, complete_targs))
>             {
>               if (complain & tf_warning_or_error)
>                 {
> @@ -29605,15 +29599,16 @@ do_auto_deduction (tree type, tree init, tree auto_node,
>                              "placeholder constraints");
>                       break;
>                     }
> -		diagnose_constraints (input_location, check, targs);
> +		diagnose_constraints (input_location, auto_node, complete_targs);
>                 }
>               return error_mark_node;
>             }
>         }
>   
> -  if (processing_template_decl && context != adc_unify)
> -    outer_targs = current_template_args ();
> -  targs = add_to_template_args (outer_targs, targs);
> +  if (context == adc_unify)
> +    targs = add_to_template_args (outer_targs, targs);
> +  else if (processing_template_decl)
> +    targs = add_to_template_args (current_template_args (), targs);
>     return tsubst (type, targs, complain, NULL_TREE);
>   }
>   
> @@ -29638,8 +29633,8 @@ splice_late_return_type (tree type, tree late_return_type)
>   	     with a function template when we saw the auto return type, so update
>   	     it to have the correct level.  */
>   	  tree new_auto = make_auto_1 (TYPE_IDENTIFIER (*auto_node), false);
> -	  PLACEHOLDER_TYPE_CONSTRAINTS (new_auto)
> -	    = PLACEHOLDER_TYPE_CONSTRAINTS (*auto_node);
> +	  PLACEHOLDER_TYPE_CONSTRAINTS_INFO (new_auto)
> +	    = PLACEHOLDER_TYPE_CONSTRAINTS_INFO (*auto_node);
>   	  TYPE_CANONICAL (new_auto) = canonical_type_parameter (new_auto);
>   	  new_auto = cp_build_qualified_type (new_auto, TYPE_QUALS (*auto_node));
>   	  *auto_node = new_auto;
> diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder3.C b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder3.C
> new file mode 100644
> index 00000000000..87e3c093e28
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder3.C
> @@ -0,0 +1,19 @@
> +// PR c++/96443
> +// { dg-do compile { target c++20 } }
> +
> +template <class T, class U> concept same_as = __is_same(T, U);
> +
> +auto f(auto x) -> same_as<decltype(x)> auto { return 0; }; // { dg-error "constraints" }
> +void g(auto x) { same_as<decltype(x)> auto y = 0; } // { dg-error "constraints" }
> +auto h(auto x) -> same_as<decltype(x.missing)> auto { return 0; } // { dg-error "constraints|missing" }
> +template <class T, same_as<T> auto N> void i() {}
> +
> +int main() {
> +  f(0); // { dg-bogus "" }
> +  f(true); // { dg-message "required from here" }
> +  g(0); // { dg-bogus "" }
> +  g(true); // { dg-message "required from here" }
> +  h(0); // { dg-message "required from here" }
> +  i<int, 0>(); // { dg-bogus "" }
> +  i<int, true>(); // { dg-error "no match|constraints" }
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-return-req2.C b/gcc/testsuite/g++.dg/cpp2a/concepts-return-req2.C
> new file mode 100644
> index 00000000000..77208bb7069
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-return-req2.C
> @@ -0,0 +1,13 @@
> +// Verify we check return-type-requirements by passing the entire set of
> +// template arguments to normalization rather than first substituting into
> +// the constraint.  The latter approach would induce a substitution failure and
> +// cause the requires-expression to evaluate to false here.
> +// { dg-do compile { target c++20 } }
> +
> +template <class, class>
> +concept C1 = true;
> +
> +template <class T>
> +concept C2 = requires { { 0 } -> C1<typename T::type>; };
> +
> +static_assert(C2<int>);
> diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C b/gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C
> index 1cefe3b243f..a116cac4ea4 100644
> --- a/gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C
> +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C
> @@ -40,7 +40,7 @@ void driver()
>     f3('a'); // { dg-error "" }
>     f4(0, 0);
>     f4(0, 'a'); // { dg-error "" }
> -  f15(0);
> +  f15(0); // { dg-bogus "" }
>     f15('a'); // { dg-message "" }
>   }
>   
> 


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 3/4] c++: Delay normalizing nested requirements until satisfaction
  2021-02-12 19:11       ` Jason Merrill
@ 2021-02-28 17:40         ` Patrick Palka
  2021-03-01 22:23           ` Jason Merrill
  0 siblings, 1 reply; 29+ messages in thread
From: Patrick Palka @ 2021-02-28 17:40 UTC (permalink / raw)
  To: Jason Merrill; +Cc: Patrick Palka, gcc-patches

On Fri, 12 Feb 2021, Jason Merrill wrote:

> On 2/10/21 9:41 AM, Patrick Palka wrote:
> > On Tue, 9 Feb 2021, Jason Merrill wrote:
> > 
> > > On 2/8/21 2:03 PM, Patrick Palka wrote:
> > > > This sets up the functionality for controlling the initial set of
> > > > template parameters to pass to normalization when dealing with a
> > > > constraint-expression that is not associated with some constrained
> > > > declaration, for instance when normalizing a nested requirement of a
> > > > requires expression, or the constraints on a placeholder type.
> > > > 
> > > > The main new ingredient here is the data member norm_info::initial_parms
> > > > which can be set by callers of the normalization routines to communicate
> > > > the in-scope template parameters for the supplied constraint-expression,
> > > > rather than always falling back to using current_template_parms.
> > > > 
> > > > This patch then uses this functionality in our handling of nested
> > > > requirements so that we can delay normalizing them until needed for
> > > > satisfaction.  We currently immediately normalize nested requirements at
> > > > parse time, where we have the necessary template context, and cache the
> > > > normal form in their TREE_TYPE node.  With this patch, we now delay
> > > > normalization until needed (as with other constraint expressions), and
> > > > instead store the current value of current_template_parms in their
> > > > TREE_TYPE node (which we use to restore the template context at
> > > > normalization time).
> > > > 
> > > > In the subsequent patch, this functionality will also be used to
> > > > normalize placeholder type constraints during auto deduction.
> > > > 
> > > > gcc/cp/ChangeLog:
> > > > 
> > > > 	* constraint.cc (build_parameter_mapping): Rely on the caller to
> > > > 	determine the in-scope template parameters.
> > > > 	(norm_info::norm_info): Delegate the one-parameter constructor
> > > > 	to the two-parameter constructor.  In the two-parameter
> > > > 	constructor, fold in the definition of make_context, set
> > > > 	initial_parms appropriately, and don't set the now-removed
> > > > 	orig_decl member.
> > > > 	(norm_info::make_context): Remove, now that its only use is
> > > > 	inlined into the caller.
> > > > 	(norm_info::update_context): Adjust call to
> > > > 	build_parameter_mapping to pass in the relevant set of in-scope
> > > > 	template parameters.
> > > > 	(norm_info::ctx_parms): Define this member function.
> > > > 	(norm_info::context): Initialize to NULL_TREE.
> > > > 	(norm_info::orig_decl): Remove this data member.
> > > > 	(norm_info::initial_parms): Define this data member.
> > > > 	(normalize_atom): Adjust call to build_parameter_mapping to pass
> > > > 	in the relevant set of in-scope template parameters.  Use
> > > > 	info.initial_parms instead of info.orig_decl.
> > > > 	(normalize_constraint_expression): Define an overload that takes
> > > > 	a norm_info object.  Cache the result of normalization.  Define
> > > > 	the other overload in terms of this one, and handle a NESTED_REQ
> > > > 	argument by setting info.initial_parms appropriately.
> > > > 	(tsubst_nested_requirement): Go through
> > > > 	satisfy_constraint_expression so that we normalize on demand.
> > > > 	(finish_nested_requirement): Set the TREE_TYPE of the NESTED_REQ
> > > > 	to current_template_parms.
> > > > 	(diagnose_nested_requirements): Go through
> > > > 	satisfy_constraint_expression, as with tsubst_nested_requirement.
> > > > ---
> > > >    gcc/cp/constraint.cc | 140
> > > > +++++++++++++++++++++++--------------------
> > > >    gcc/cp/cp-tree.h     |   4 +-
> > > >    2 files changed, 78 insertions(+), 66 deletions(-)
> > > > 
> > > > diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> > > > index 39c97986082..56134f8b2bf 100644
> > > > --- a/gcc/cp/constraint.cc
> > > > +++ b/gcc/cp/constraint.cc
> > > > @@ -133,7 +133,7 @@ struct sat_info : subst_info
> > > >      bool diagnose_unsatisfaction;
> > > >    };
> > > >    -static tree satisfy_constraint (tree, tree, sat_info);
> > > > +static tree satisfy_constraint_expression (tree, tree, sat_info);
> > > >      /* True if T is known to be some type other than bool. Note that
> > > > this
> > > >       is false for dependent types and errors.  */
> > > > @@ -594,26 +594,12 @@ map_arguments (tree parms, tree args)
> > > >      return parms;
> > > >    }
> > > >    -/* Build the parameter mapping for EXPR using ARGS.  */
> > > > +/* Build the parameter mapping for EXPR using ARGS, where CTX_PARMS
> > > > +   are the template parameters in scope for EXPR.  */
> > > >      static tree
> > > > -build_parameter_mapping (tree expr, tree args, tree decl)
> > > > +build_parameter_mapping (tree expr, tree args, tree ctx_parms)
> > > >    {
> > > > -  tree ctx_parms = NULL_TREE;
> > > > -  if (decl)
> > > > -    {
> > > > -      gcc_assert (TREE_CODE (decl) == TEMPLATE_DECL);
> > > > -      ctx_parms = DECL_TEMPLATE_PARMS (decl);
> > > > -    }
> > > > -  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.  */
> > > > -      ctx_parms = current_template_parms;
> > > > -    }
> > > > -
> > > >      tree parms = find_template_parameters (expr, ctx_parms);
> > > >      tree map = map_arguments (parms, args);
> > > >      return map;
> > > > @@ -645,53 +631,63 @@ parameter_mapping_equivalent_p (tree t1, tree t2)
> > > >      struct norm_info : subst_info
> > > >    {
> > > > -  explicit norm_info (tsubst_flags_t complain)
> > > > -    : subst_info (tf_warning_or_error | complain, NULL_TREE),
> > > > -      context()
> > > > +  explicit norm_info (tsubst_flags_t cmp)
> > > > +    : norm_info (NULL_TREE, cmp)
> > > >      {}
> > > >        /* Construct a top-level context for DECL.  */
> > > >        norm_info (tree in_decl, tsubst_flags_t complain)
> > > > -    : subst_info (tf_warning_or_error | complain, in_decl),
> > > > -      context (make_context (in_decl)),
> > > > -      orig_decl (in_decl)
> > > > -  {}
> > > > -
> > > > -  bool generate_diagnostics() const
> > > > +    : subst_info (tf_warning_or_error | complain, in_decl)
> > > >      {
> > > > -    return complain & tf_norm;
> > > > +    if (in_decl)
> > > > +      {
> > > > +	initial_parms = DECL_TEMPLATE_PARMS (in_decl);
> > > > +	if (generate_diagnostics ())
> > > > +	  context = build_tree_list (NULL_TREE, in_decl);
> > > > +      }
> > > > +    else
> > > > +      initial_parms = current_template_parms;
> > > >      }
> > > >    -  tree make_context(tree in_decl)
> > > > +  bool generate_diagnostics() const
> > > >      {
> > > > -    if (generate_diagnostics ())
> > > > -      return build_tree_list (NULL_TREE, in_decl);
> > > > -    return NULL_TREE;
> > > > +    return complain & tf_norm;
> > > >      }
> > > >        void update_context(tree expr, tree args)
> > > >      {
> > > >        if (generate_diagnostics ())
> > > >          {
> > > > -	tree map = build_parameter_mapping (expr, args, in_decl);
> > > > +	tree map = build_parameter_mapping (expr, args, ctx_parms ());
> > > >    	context = tree_cons (map, expr, context);
> > > >          }
> > > >        in_decl = get_concept_check_template (expr);
> > > >      }
> > > >    +  /* Returns the template parameters that are in scope for the
> > > > current
> > > > +     normalization context.  */
> > > > +
> > > > +  tree ctx_parms()
> > > > +  {
> > > > +    if (in_decl)
> > > > +      return DECL_TEMPLATE_PARMS (in_decl);
> > > > +    else
> > > > +      return initial_parms;
> > > > +  }
> > > > +
> > > >      /* Provides information about the source of a constraint. This is a
> > > >         TREE_LIST whose VALUE is either a concept check or a constrained
> > > >         declaration. The PURPOSE, for concept checks is a parameter
> > > > mapping
> > > >         for that check.  */
> > > >    -  tree context;
> > > > +  tree context = NULL_TREE;
> > > >        /* The declaration whose constraints we're normalizing.  The
> > > > targets
> > > >         of the parameter mapping of each atom will be in terms of the
> > > >         template parameters of ORIG_DECL.  */
> > > >    -  tree orig_decl = NULL_TREE;
> > > > +  tree initial_parms = NULL_TREE;
> > > >    };
> > > >      static tree normalize_expression (tree, tree, norm_info);
> > > > @@ -773,7 +769,7 @@ normalize_atom (tree t, tree args, norm_info info)
> > > >        return normalize_concept_check (t, args, info);
> > > >        /* Build the parameter mapping for the atom.  */
> > > > -  tree map = build_parameter_mapping (t, args, info.in_decl);
> > > > +  tree map = build_parameter_mapping (t, args, info.ctx_parms ());
> > > >        /* Build a new info object for the atom.  */
> > > >      tree ci = build_tree_list (t, info.context);
> > > > @@ -803,10 +799,8 @@ normalize_atom (tree t, tree args, norm_info info)
> > > >    	      tree target = TREE_PURPOSE (node);
> > > >    	      TREE_VEC_ELT (targets, i++) = target;
> > > >    	    }
> > > > -	  tree ctx_parms = (info.orig_decl
> > > > -			    ? DECL_TEMPLATE_PARMS (info.orig_decl)
> > > > -			    : current_template_parms);
> > > > -	  tree target_parms = find_template_parameters (targets, ctx_parms);
> > > > +	  tree target_parms = find_template_parameters (targets,
> > > > +							info.initial_parms);
> > > >    	  TREE_TYPE (map) = target_parms;
> > > >    	}
> > > >    @@ -983,17 +977,43 @@ normalize_nontemplate_requirements (tree decl,
> > > > bool
> > > > diag = false)
> > > >    /* Normalize an EXPR as a constraint.  */
> > > >      static tree
> > > > -normalize_constraint_expression (tree expr, bool diag)
> > > > +normalize_constraint_expression (tree expr, norm_info info)
> > > >    {
> > > >      if (!expr || expr == error_mark_node)
> > > >        return expr;
> > > > +
> > > > +  if (!info.generate_diagnostics ())
> > > > +    if (tree *p = hash_map_safe_get (normalized_map, expr))
> > > > +      return *p;
> > > 
> > > It seems like we only want this for NESTED_REQ.
> > 
> > I figured it'd also be beneficial to cache the normal form of a
> > placeholder type constraint, which will also goes through this overload.
> 
> True.  And if we change REQUIRES_EXPR handling to not go through here, it
> should be just the two.  OK, let's leave this alone.
> 
> > > >      ++processing_template_decl;
> > > > -  norm_info info (diag ? tf_norm : tf_none);
> > > >      tree norm = get_normalized_constraints (expr, info);
> > > >      --processing_template_decl;
> > > > +
> > > > +  if (!info.generate_diagnostics ())
> > > > +    hash_map_safe_put<hm_ggc> (normalized_map, expr, norm);
> > > > +
> > > >      return norm;
> > > >    }
> > > >    +/* High-level wrapper for the above.  */
> > > > +
> > > > +static tree
> > > > +normalize_constraint_expression (tree expr, bool diag)
> > > > +{
> > > > +  norm_info info (diag ? tf_norm : tf_none);
> > > 
> > > I wonder if we want to add a norm_info constructor taking a sat_info so we
> > > don't need to mediate passing from one into the other with bool "diag"
> > > parameters in various functions.  That doesn't need to happen in this
> > > patch.
> > 
> > Sounds good.  I think such a constructor would let us eliminate the
> > bool-taking overload of normalize_constraint_expression altogether, if
> > we move the special handling of NESTED_REQ to the norm_info-taking
> > overload or to satisfy_constraint_expression.

Here's v2 of this patch, which refrains from adding a second overload of
normalize_constraint_expression.  Handling of NESTED_REQs now happens in
satisfy_constraint_expression.

The rest of the cleanups mentioned here will be performed in another
patch.

(For now, I didn't add a norm_info(sat_info) constructor since it wasn't
clear to me if this constructor should propagate in_decl or not.  For
sat_info, in_decl is used purely for diagnostics, whereas for norm_info,
in_decl keeps track of the current normalization context, so carrying
over the value of in_decl might not always make sense.

Perhaps it might be better to add a norm_info(bool diag) constructor
instead?  While we're at it, I think we could remove the tf_norm
flag.)

-- >8 --

Subject: [PATCH 3/6] c++: Delay normalizing nested requirements until
 satisfaction

This sets up the functionality for controlling the initial set of
template parameters to pass to normalization when dealing with a
constraint-expression that is not associated with some constrained
declaration, for instance when normalizing a nested requirement of a
requires expression, or the constraints on a placeholder type.

The main new ingredient here is the data member norm_info::initial_parms
which can be set by callers of the normalization routines to communicate
the in-scope template parameters for the supplied constraint-expression,
rather than always falling back to using current_template_parms.

This patch then uses this functionality in our handling of nested
requirements so that we can delay normalizing them until needed for
satisfaction.  We currently immediately normalize nested requirements at
parse time, where we have the necessary template context, and cache the
normal form in their TREE_TYPE node.  With this patch, we now delay
normalization until needed (as with other constraint expressions), and
instead store the current value of current_template_parms in their
TREE_TYPE node (which we use to restore the template context at
normalization time).

In the subsequent patch, this functionality will also be used to
normalize placeholder type constraints during auto deduction.

gcc/cp/ChangeLog:

	* constraint.cc (build_parameter_mapping): Rely on the caller to
	determine the in-scope template parameters.
	(norm_info::norm_info): Delegate the tsubst_flags_t constructor
	to the two-parameter constructor.  In the two-parameter
	constructor, fold in the definition of make_context, set
	initial_parms appropriately, and don't set the now-removed
	orig_decl member.
	(norm_info::make_context): Remove, now that its only use is
	inlined into the caller.
	(norm_info::update_context): Adjust call to
	build_parameter_mapping to pass in the relevant set of in-scope
	template parameters.
	(norm_info::ctx_parms): Define this member function.
	(norm_info::context): Initialize to NULL_TREE.
	(norm_info::orig_decl): Remove this data member.
	(norm_info::initial_parms): Define this data member.
	(normalize_atom): Adjust call to build_parameter_mapping to pass
	in the relevant set of in-scope template parameters.  Use
	info.initial_parms instead of info.orig_decl.
	(normalize_constraint_expression): Take a norm_info object
	instead of a bool.  Cache the result of normalization.
	(tsubst_nested_requirement): Call satisfy_constraint_expression
	instead of satisfy_constraint, so that we normalize on demand.
	(satisfy_constraint_expression): Handle a NESTED_REQ argument.
	Adjust call to normalize_constraint_expression.
	(finish_nested_requirement): Set the TREE_TYPE of the NESTED_REQ
	to current_template_parms.
	(diagnose_nested_requirements): Go through
	satisfy_constraint_expression, as with tsubst_nested_requirement.
---
 gcc/cp/constraint.cc | 138 ++++++++++++++++++++++---------------------
 1 file changed, 72 insertions(+), 66 deletions(-)

diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index 39c97986082..fcb249a642f 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -133,7 +133,7 @@ struct sat_info : subst_info
   bool diagnose_unsatisfaction;
 };
 
-static tree satisfy_constraint (tree, tree, sat_info);
+static tree satisfy_constraint_expression (tree, tree, sat_info);
 
 /* True if T is known to be some type other than bool. Note that this
    is false for dependent types and errors.  */
@@ -594,26 +594,12 @@ map_arguments (tree parms, tree args)
   return parms;
 }
 
-/* Build the parameter mapping for EXPR using ARGS.  */
+/* Build the parameter mapping for EXPR using ARGS, where CTX_PARMS
+   are the template parameters in scope for EXPR.  */
 
 static tree
-build_parameter_mapping (tree expr, tree args, tree decl)
+build_parameter_mapping (tree expr, tree args, tree ctx_parms)
 {
-  tree ctx_parms = NULL_TREE;
-  if (decl)
-    {
-      gcc_assert (TREE_CODE (decl) == TEMPLATE_DECL);
-      ctx_parms = DECL_TEMPLATE_PARMS (decl);
-    }
-  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.  */
-      ctx_parms = current_template_parms;
-    }
-
   tree parms = find_template_parameters (expr, ctx_parms);
   tree map = map_arguments (parms, args);
   return map;
@@ -645,53 +631,63 @@ parameter_mapping_equivalent_p (tree t1, tree t2)
 
 struct norm_info : subst_info
 {
-  explicit norm_info (tsubst_flags_t complain)
-    : subst_info (tf_warning_or_error | complain, NULL_TREE),
-      context()
+  explicit norm_info (tsubst_flags_t cmp)
+    : norm_info (NULL_TREE, cmp)
   {}
 
   /* Construct a top-level context for DECL.  */
 
   norm_info (tree in_decl, tsubst_flags_t complain)
-    : subst_info (tf_warning_or_error | complain, in_decl),
-      context (make_context (in_decl)),
-      orig_decl (in_decl)
-  {}
-
-  bool generate_diagnostics() const
+    : subst_info (tf_warning_or_error | complain, in_decl)
   {
-    return complain & tf_norm;
+    if (in_decl)
+      {
+	initial_parms = DECL_TEMPLATE_PARMS (in_decl);
+	if (generate_diagnostics ())
+	  context = build_tree_list (NULL_TREE, in_decl);
+      }
+    else
+      initial_parms = current_template_parms;
   }
 
-  tree make_context(tree in_decl)
+  bool generate_diagnostics() const
   {
-    if (generate_diagnostics ())
-      return build_tree_list (NULL_TREE, in_decl);
-    return NULL_TREE;
+    return complain & tf_norm;
   }
 
   void update_context(tree expr, tree args)
   {
     if (generate_diagnostics ())
       {
-	tree map = build_parameter_mapping (expr, args, in_decl);
+	tree map = build_parameter_mapping (expr, args, ctx_parms ());
 	context = tree_cons (map, expr, context);
       }
     in_decl = get_concept_check_template (expr);
   }
 
+  /* Returns the template parameters that are in scope for the current
+     normalization context.  */
+
+  tree ctx_parms()
+  {
+    if (in_decl)
+      return DECL_TEMPLATE_PARMS (in_decl);
+    else
+      return initial_parms;
+  }
+
   /* Provides information about the source of a constraint. This is a
      TREE_LIST whose VALUE is either a concept check or a constrained
      declaration. The PURPOSE, for concept checks is a parameter mapping
      for that check.  */
 
-  tree context;
+  tree context = NULL_TREE;
 
   /* The declaration whose constraints we're normalizing.  The targets
      of the parameter mapping of each atom will be in terms of the
      template parameters of ORIG_DECL.  */
 
-  tree orig_decl = NULL_TREE;
+  tree initial_parms = NULL_TREE;
 };
 
 static tree normalize_expression (tree, tree, norm_info);
@@ -773,7 +769,7 @@ normalize_atom (tree t, tree args, norm_info info)
     return normalize_concept_check (t, args, info);
 
   /* Build the parameter mapping for the atom.  */
-  tree map = build_parameter_mapping (t, args, info.in_decl);
+  tree map = build_parameter_mapping (t, args, info.ctx_parms ());
 
   /* Build a new info object for the atom.  */
   tree ci = build_tree_list (t, info.context);
@@ -803,10 +799,8 @@ normalize_atom (tree t, tree args, norm_info info)
 	      tree target = TREE_PURPOSE (node);
 	      TREE_VEC_ELT (targets, i++) = target;
 	    }
-	  tree ctx_parms = (info.orig_decl
-			    ? DECL_TEMPLATE_PARMS (info.orig_decl)
-			    : current_template_parms);
-	  tree target_parms = find_template_parameters (targets, ctx_parms);
+	  tree target_parms = find_template_parameters (targets,
+							info.initial_parms);
 	  TREE_TYPE (map) = target_parms;
 	}
 
@@ -983,14 +977,22 @@ normalize_nontemplate_requirements (tree decl, bool diag = false)
 /* Normalize an EXPR as a constraint.  */
 
 static tree
-normalize_constraint_expression (tree expr, bool diag)
+normalize_constraint_expression (tree expr, norm_info info)
 {
   if (!expr || expr == error_mark_node)
     return expr;
+
+  if (!info.generate_diagnostics ())
+    if (tree *p = hash_map_safe_get (normalized_map, expr))
+      return *p;
+
   ++processing_template_decl;
-  norm_info info (diag ? tf_norm : tf_none);
   tree norm = get_normalized_constraints (expr, info);
   --processing_template_decl;
+
+  if (!info.generate_diagnostics ())
+    hash_map_safe_put<hm_ggc> (normalized_map, expr, norm);
+
   return norm;
 }
 
@@ -2086,16 +2088,14 @@ tsubst_compound_requirement (tree t, tree args, subst_info info)
 static tree
 tsubst_nested_requirement (tree t, tree args, subst_info info)
 {
-  /* Perform satisfaction quietly with the regular normal form.  */
+  /* Perform satisfaction quietly first.  */
   sat_info quiet (tf_none, info.in_decl);
-  tree norm = TREE_VALUE (TREE_TYPE (t));
-  tree diag_norm = TREE_PURPOSE (TREE_TYPE (t));
-  tree result = satisfy_constraint (norm, args, quiet);
+  tree result = satisfy_constraint_expression (t, args, quiet);
   if (result == error_mark_node)
     {
-      /* Replay the error using the diagnostic normal form.  */
+      /* Replay the error.  */
       sat_info noisy (tf_warning_or_error, info.in_decl);
-      satisfy_constraint (diag_norm, args, noisy);
+      satisfy_constraint_expression (t, args, noisy);
     }
   if (result != boolean_true_node)
     return error_mark_node;
@@ -3040,8 +3040,22 @@ satisfy_constraint_expression (tree t, tree args, sat_info info)
       tree tmpl = get_concept_check_template (id);
       norm = normalize_concept_definition (tmpl, info.noisy ());
     }
+  else if (TREE_CODE (t) == NESTED_REQ)
+    {
+      norm_info ninfo (info.noisy () ? tf_norm : tf_none);
+      /* The TREE_TYPE contains the set of template parameters that were in
+	 scope for this nested requirement; use them as the initial template
+	 parameters for normalization.  */
+      ninfo.initial_parms = TREE_TYPE (t);
+      norm = normalize_constraint_expression (TREE_OPERAND (t, 0), ninfo);
+    }
+  else if (EXPR_P (t))
+    {
+      norm_info ninfo (info.noisy () ? tf_norm : tf_none);
+      norm = normalize_constraint_expression (t, ninfo);
+    }
   else
-    norm = normalize_constraint_expression (t, info.noisy ());
+    gcc_unreachable ();
 
   /* Perform satisfaction.  */
   return satisfy_constraint (norm, args, info);
@@ -3301,15 +3315,9 @@ finish_compound_requirement (location_t loc, tree expr, tree type, bool noexcept
 tree
 finish_nested_requirement (location_t loc, tree expr)
 {
-  /* We need to normalize the constraints now, at parse time, while
-     we have the necessary template context.  We normalize twice,
-     once without diagnostic information and once with, which we'll
-     later use for quiet and noisy satisfaction respectively.  */
-  tree norm = normalize_constraint_expression (expr, /*diag=*/false);
-  tree diag_norm = normalize_constraint_expression (expr, /*diag=*/true);
-
-  /* Build the constraint, saving its two normalizations as its type.  */
-  tree r = build1 (NESTED_REQ, build_tree_list (diag_norm, norm), expr);
+  /* Build the requirement, saving the set of in-scope template
+     parameters as its type.  */
+  tree r = build1 (NESTED_REQ, current_template_parms, expr);
   SET_EXPR_LOCATION (r, loc);
   return r;
 }
@@ -3710,12 +3718,9 @@ diagnose_type_requirement (tree req, tree args, tree in_decl)
 static void
 diagnose_nested_requirement (tree req, tree args)
 {
-  /* Quietly check for satisfaction first using the regular normal form.
-     We can elaborate details later if needed.  */
-  tree norm = TREE_VALUE (TREE_TYPE (req));
-  tree diag_norm = TREE_PURPOSE (TREE_TYPE (req));
-  sat_info info (tf_none, NULL_TREE);
-  tree result = satisfy_constraint (norm, args, info);
+  /* Quietly check for satisfaction first.  */
+  sat_info quiet (tf_none, NULL_TREE);
+  tree result = satisfy_constraint_expression (req, args, quiet);
   if (result == boolean_true_node)
     return;
 
@@ -3723,10 +3728,11 @@ diagnose_nested_requirement (tree req, tree args)
   location_t loc = cp_expr_location (expr);
   if (diagnosing_failed_constraint::replay_errors_p ())
     {
-      /* Replay the substitution error using the diagnostic normal form.  */
+      /* Replay the substitution error with re-normalized requirements.  */
       inform (loc, "nested requirement %qE is not satisfied, because", expr);
+
       sat_info noisy (tf_warning_or_error, NULL_TREE, /*diag_unsat=*/true);
-      satisfy_constraint (diag_norm, args, noisy);
+      satisfy_constraint_expression (req, args, noisy);
     }
   else
     inform (loc, "nested requirement %qE is not satisfied", expr);
-- 
2.31.0.rc0


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 4/4] c++: dependent constraint on placeholder 'auto' [PR96443]
  2021-02-12 19:14       ` Jason Merrill
@ 2021-02-28 17:55         ` Patrick Palka
  2021-03-01 22:31           ` Jason Merrill
  0 siblings, 1 reply; 29+ messages in thread
From: Patrick Palka @ 2021-02-28 17:55 UTC (permalink / raw)
  To: Jason Merrill; +Cc: Patrick Palka, gcc-patches

On Fri, 12 Feb 2021, Jason Merrill wrote:

> On 2/11/21 5:14 PM, Patrick Palka wrote:
> > On Thu, 11 Feb 2021, Jason Merrill wrote:
> > 
> > > On 2/8/21 2:03 PM, Patrick Palka wrote:
> > > > This fixes the way we check satisfaction of constraints on placeholder
> > > > types in various contexts, and in particular when the constraint is
> > > > dependent.
> > > > 
> > > > Firstly, when evaluating the return type requirement of a compound
> > > > requirement, we currently substitute the outer template arguments into
> > > > the constraint before checking satisfaction. But we should instead be
> > > > passing in the complete set of template arguments to satisfaction and
> > > > not do a prior separate substitution.  Our current approach leads to us
> > > > incorrectly rejecting the testcase concepts-return-req2.C below.
> > > > 
> > > > Secondly, when checking the constraints on a placeholder variable or
> > > > return type, we don't substitute the template arguments of the enclosing
> > > > context at all.  This leads to bogus errors during satisfaction when the
> > > > constraint is dependent as in the testcase concepts-placeholder3.C
> > > > below.
> > > > 
> > > > In order to fix these two issues, we need to be able to properly
> > > > normalize the constraints on a placeholder 'auto', which in turn
> > > > requires us to know the template parameters that were in-scope where an
> > > > 'auto' was introduced.  This information currently doesn't seem to be
> > > > easily available when we need it, so this patch adds an auxiliary hash
> > > > table that keeps track of the value of current_template_parms when each
> > > > constrained 'auto' was formed.
> > > > 
> > > > This patch also removes some seemingly wrong handling of placeholder
> > > > type arguments from tsubst_parameter_mapping.  The code doesn't trigger
> > > > with the example used in the comments, because type_uses_auto doesn't
> > > > look inside non-deduced contexts such as the operand of decltype.  And
> > > > the call to do_auto_deduction seems confused because if 'arg' is a type,
> > > > then so is 'parm', and therefore 'init' too is a type, but
> > > > do_auto_deduction expects it to be an expression.  Before this patch,
> > > > this code was dead (as far as our testsuite can tell), but now it breaks
> > > > other parts of this patch, so let's remove it.
> > > > 
> > > > gcc/cp/ChangeLog:
> > > > 
> > > > 	PR c++/96443
> > > > 	* constraint.cc (type_deducible_p): Don't substitute into the
> > > > 	constraints, and instead just pass 'args' to do_auto_deduction
> > > > 	as the outer template arguments.
> > > > 	(tsubst_parameter_mapping): Remove confused code for handling
> > > > 	placeholder type arguments.
> > > > 	(normalize_placeholder_type_constraint): Define.
> > > > 	(satisfy_constraint_expression): Use it to handle placeholder
> > > > 	'auto' types.
> > > > 	* cp-tree.h (get_constrained_auto_context): Declare.
> > > > 	* pt.c (constrained_auto_context_map): Define.
> > > > 	(get_placeholder_type_constraint_context): Define.
> > > > 	(set_placeholder_type_constraints): Define.
> > > > 	(copy_placeholder_type_constraints): Define.
> > > > 	(tsubst) <case TEMPLATE_TYPE_PARM>: Use
> > > > 	copy_placeholder_type_constraints.
> > > > 	(make_constrained_placeholder_type): Use
> > > > 	set_placeholder_type_constraints.
> > > > 	(do_auto_deduction): Clarify comments about the outer_targs
> > > > 	parameter.  Rework satisfaction of a placeholder type constraint
> > > > 	to pass in the complete set of template arguments directly to
> > > > 	constraints_satisfied_p.
> > > > 	(splice_late_return_type): Use copy_placeholder_type_constraints.
> > > > 
> > > > gcc/testsuite/ChangeLog:
> > > > 
> > > > 	PR c++/96443
> > > > 	* g++.dg/cpp2a/concepts-placeholder3.C: New test.
> > > > 	* g++.dg/cpp2a/concepts-return-req2.C: New test.
> > > > 	* g++.dg/concepts-ts1.C: Add dg-bogus directive to the call to
> > > > 	f15 that we expect to accept.
> > > > ---
> > > >    gcc/cp/constraint.cc                          | 106
> > > > ++++++++----------
> > > >    gcc/cp/cp-tree.h                              |   1 +
> > > >    gcc/cp/pt.c                                   | 101 +++++++++++------
> > > >    .../g++.dg/cpp2a/concepts-placeholder3.C      |  19 ++++
> > > >    .../g++.dg/cpp2a/concepts-return-req2.C       |  13 +++
> > > >    gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C     |   2 +-
> > > >    6 files changed, 146 insertions(+), 96 deletions(-)
> > > >    create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-placeholder3.C
> > > >    create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-return-req2.C
> > > > 
> > > > diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> > > > index 56134f8b2bf..53588047d44 100644
> > > > --- a/gcc/cp/constraint.cc
> > > > +++ b/gcc/cp/constraint.cc
> > > > @@ -2007,39 +2007,19 @@ type_deducible_p (tree expr, tree type, tree
> > > > placeholder, tree args,
> > > >         references are preserved in the result.  */
> > > >      expr = force_paren_expr_uneval (expr);
> > > >    -  /* Replace the constraints with the instantiated constraints. This
> > > > -     substitutes args into any template parameters in the trailing
> > > > -     result type.  */
> > > > -  tree saved_constr = PLACEHOLDER_TYPE_CONSTRAINTS (placeholder);
> > > > -  tree subst_constr
> > > > -    = tsubst_constraint (saved_constr,
> > > > -			 args,
> > > > -			 info.complain | tf_partial,
> > > > -			 info.in_decl);
> > > > -
> > > > -  if (subst_constr == error_mark_node)
> > > > -    return false;
> > > > -
> > > > -  PLACEHOLDER_TYPE_CONSTRAINTS (placeholder) = subst_constr;
> > > > -
> > > > -  /* Temporarily unlink the canonical type.  */
> > > > -  tree saved_type = TYPE_CANONICAL (placeholder);
> > > > -  TYPE_CANONICAL (placeholder) = NULL_TREE;
> > > > -
> > > > -  tree deduced_type
> > > > -    = do_auto_deduction (type,
> > > > -			 expr,
> > > > -			 placeholder,
> > > > -			 info.complain,
> > > > -			 adc_requirement);
> > > > -
> > > > -  PLACEHOLDER_TYPE_CONSTRAINTS (placeholder) = saved_constr;
> > > > -  TYPE_CANONICAL (placeholder) = saved_type;
> > > > +  /* When args is empty, we're evaluating a non-templated requires
> > > > expression,
> > > > +     but even those are parsed under processing_template_decl == 1, and
> > > > so
> > > > the
> > > > +     placeholder 'auto' inside this return-type-requirement has level
> > > > 2.
> > > > In
> > > > +     order to have all parms and arguments match up for satisfaction,
> > > > we
> > > > need
> > > > +     to pass a single level as OUTER_TARGS in this case.  */
> > > > +  if (!args)
> > > > +    args = make_tree_vec (0);
> > > >    -  if (deduced_type == error_mark_node)
> > > > -    return false;
> > > > +  tree deduced_type = do_auto_deduction (type, expr, placeholder,
> > > > +					 info.complain, adc_requirement,
> > > > +					 /*outer_targs=*/args);
> > > >    -  return true;
> > > > +  return deduced_type != error_mark_node;
> > > >    }
> > > >      /* True if EXPR can not be converted to TYPE.  */
> > > > @@ -2304,35 +2284,10 @@ tsubst_parameter_mapping (tree map, tree args,
> > > > subst_info info)
> > > >            return error_mark_node;
> > > >          tree parm = TREE_VALUE (p);
> > > >          tree arg = TREE_PURPOSE (p);
> > > > -      tree new_arg = NULL_TREE;
> > > > -      if (TYPE_P (arg))
> > > > -        {
> > > > -          /* If a template parameter is declared with a placeholder, we
> > > > can
> > > > -             get those in the argument list if decltype is applied to
> > > > the
> > > > -             placeholder. For example:
> > > > -
> > > > -		template<auto T>
> > > > -		  requires C<decltype(T)>
> > > > -		void f() { }
> > > > -
> > > > -	     The normalized argument for C will be an auto type, so we'll
> > > > -             need to deduce the actual argument from the corresponding
> > > > -             initializer (whatever argument is provided for T), and use
> > > > -             that result in the instantiated parameter mapping.  */
> > > > -          if (tree auto_node = type_uses_auto (arg))
> > > > -            {
> > > > -              int level;
> > > > -              int index;
> > > > -	      template_parm_level_and_index (parm, &level, &index);
> > > > -	      tree init = TMPL_ARG (args, level, index);
> > > > -              new_arg = do_auto_deduction (arg, init, auto_node,
> > > > -					   complain, adc_variable_type,
> > > > -					   make_tree_vec (0));
> > > > -            }
> > > > -        }
> > > > -      else if (ARGUMENT_PACK_P (arg))
> > > > +      tree new_arg;
> > > > +      if (ARGUMENT_PACK_P (arg))
> > > >    	new_arg = tsubst_argument_pack (arg, args, complain, in_decl);
> > > > -      if (!new_arg)
> > > > +      else
> > > >    	{
> > > >    	  new_arg = tsubst_template_arg (arg, args, complain,
> > > > in_decl);
> > > >    	  if (TYPE_P (new_arg))
> > > > @@ -3038,6 +2993,31 @@ satisfy_associated_constraints (tree t, tree
> > > > args,
> > > > sat_info info)
> > > >      return satisfy_constraint (t, args, info);
> > > >    }
> > > >    +/* Normalize the constraints on the placeholder 'auto' type T.  */
> > > > +
> > > > +tree
> > > > +normalize_placeholder_type_constraints (tree t, bool diag)
> > > > +{
> > > > +  gcc_assert (is_auto (t));
> > > > +  tree constr = PLACEHOLDER_TYPE_CONSTRAINTS (t);
> > > > +  if (!constr)
> > > > +    return NULL_TREE;
> > > > +
> > > > +  tree initial_parms = get_constrained_auto_context (t);
> > > > +  /* The 'auto' itself is used as the first argument in its own
> > > > constraints,
> > > > +     and its level is one greater than its template context, so in
> > > > order to
> > > > +     capture all used template parameters we need to add an extra level
> > > > of
> > > > +     template parameters to the context; a dummy level suffices.  */
> > > > +  initial_parms
> > > > +    = tree_cons (size_int (initial_parms
> > > > +			   ? TMPL_PARMS_DEPTH (initial_parms) + 1 : 1),
> > > > +		 make_tree_vec (0), initial_parms);
> > > > +
> > > > +  norm_info info (diag ? tf_norm : tf_none);
> > > > +  info.initial_parms = initial_parms;
> > > > +  return normalize_constraint_expression (constr, info);
> > > > +}
> > > > +
> > > >    /* Evaluate EXPR as a constraint expression using ARGS, returning a
> > > >       satisfaction value. */
> > > >    @@ -3047,8 +3027,6 @@ satisfy_constraint_expression (tree t, tree
> > > > args,
> > > > sat_info info)
> > > >      if (t == error_mark_node)
> > > >        return error_mark_node;
> > > >    -  gcc_assert (EXPR_P (t));
> > > > -
> > > >      /* Get the normalized constraints.  */
> > > >      tree norm;
> > > >      if (args == NULL_TREE && concept_check_p (t))
> > > > @@ -3058,8 +3036,12 @@ satisfy_constraint_expression (tree t, tree args,
> > > > sat_info info)
> > > >          tree tmpl = get_concept_check_template (id);
> > > >          norm = normalize_concept_definition (tmpl, info.noisy ());
> > > >        }
> > > > -  else
> > > > +  else if (EXPR_P (t))
> > > >        norm = normalize_constraint_expression (t, info.noisy ());
> > > > +  else if (is_auto (t))
> > > > +    norm = normalize_placeholder_type_constraints (t, info.noisy ());
> > > > +  else
> > > > +    gcc_unreachable ();
> > > >        /* Perform satisfaction.  */
> > > >      return satisfy_constraint (norm, args, info);
> > > > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> > > > index 26fbf1eb663..ca5550ccb6d 100644
> > > > --- a/gcc/cp/cp-tree.h
> > > > +++ b/gcc/cp/cp-tree.h
> > > > @@ -7090,6 +7090,7 @@ extern tree make_auto
> > > > (void);
> > > >    extern tree make_decltype_auto			(void);
> > > >    extern tree make_constrained_auto		(tree, tree);
> > > >    extern tree make_constrained_decltype_auto	(tree, tree);
> > > > +extern tree get_constrained_auto_context	(tree);
> > > >    extern tree make_template_placeholder		(tree);
> > > >    extern bool template_placeholder_p		(tree);
> > > >    extern bool ctad_template_p			(tree);
> > > > diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
> > > > index bceb942e79a..bcf9c9a3d9a 100644
> > > > --- a/gcc/cp/pt.c
> > > > +++ b/gcc/cp/pt.c
> > > > @@ -15364,6 +15364,46 @@ tsubst_tree_list (tree t, tree args,
> > > > tsubst_flags_t
> > > > complain, tree in_decl)
> > > >      return chain;
> > > >    }
> > > >    +/* A hash table mapping a constrained 'auto' to the set of in-scope
> > > > +   template parameters from where the 'auto' was introduced.  */
> > > > +
> > > > +static GTY((cache)) decl_tree_cache_map *constrained_auto_context_map;
> > > 
> > > How about turning PLACEHOLDER_TYPE_CONSTRAINTS into a TREE_LIST instead of
> > > introducing a new hash table?
> > 
> > Nice, this cleaner approach works well, and was quite straightforward to
> > implement.  How does this look?  Tested on x86_64-pc-linux-gnu,
> > range-v3  and cmcstl2.
> 
> Looks good.

Here's v3 of this patch, which correctly addresses the FIXME in
splice_late_return_type added by r11-7409, and also adds a testcase
abbrev9.C which verifies this.

From the r11-7409 thread:
> >> +	/* FIXME: We should also rebuild the constraint to refer to the new
> >> +	   auto.  */
> >> +	PLACEHOLDER_TYPE_CONSTRAINTS (new_auto)
> >> +	  = PLACEHOLDER_TYPE_CONSTRAINTS (auto_node);
> >> +	TYPE_CANONICAL (new_auto) = canonical_type_parameter (new_auto);

> For the FIXME it would be nice to adjust 
> make_constrained_auto/_placeholder_type to handle this for you.

Hmm, I tried, but I couldn't see a good way to adjust these functions to
handle rebuilding the constraint for us.

-- >8 --

Subject: [PATCH 4/6] c++: Fix satisfaction of placeholder type constraints
 [PR96443]

This fixes the way we check satisfaction of constraints on placeholder
types in various deduction contexts, and in particular when the
constraint is dependent.

Firstly, when evaluating the return type requirement of a compound
requirement, we currently substitute the outer template arguments into
the constraint before checking satisfaction. But we should instead be
passing in the complete set of template arguments to satisfaction and
not do a prior separate substitution.  Our current approach leads to us
incorrectly rejecting the testcase concepts-return-req2.C below.

Secondly, when checking the constraints on a placeholder variable or
return type, we don't consider the template arguments of the enclosing
context at all.  This leads to bogus errors during satisfaction when the
constraint is dependent as in the testcase concepts-placeholder3.C
below.

In order to fix these two issues, we need to be able to normalize the
constraints on a placeholder 'auto', which in turn requires us to know
the template parameters that were in scope where the 'auto' was
introduced.  This information currently doesn't seem to be easily
available when we need it, so this patch turns PLACEHOLDER_TYPE_CONSTRAINTS
into a TREE_LIST whose TREE_PURPOSE additionally holds the value of
current_template_parms whence a constrained 'auto' was formed.

This patch also removes some seemingly wrong handling of placeholder
type arguments from tsubst_parameter_mapping.  The code doesn't trigger
with the example used in the comments, because type_uses_auto doesn't
look inside non-deduced contexts such as the operand of decltype.  And
the call to do_auto_deduction seems confused because if 'arg' is a type,
then so is 'parm', and therefore 'init' too is a type, but
do_auto_deduction expects it to be an expression.  Before this patch,
this code was dead (as far as our testsuite can tell), but now it breaks
other parts of this patch, so let's remove it.

gcc/cp/ChangeLog:

	PR c++/96443
	PR c++/96960
	* constraint.cc (type_deducible_p): Don't substitute into the
	constraints, and instead just pass 'args' to do_auto_deduction
	as the outer template arguments.
	(tsubst_parameter_mapping): Remove confused code for handling
	placeholder type arguments.
	(normalize_placeholder_type_constraint): Define.
	(satisfy_constraint_expression): Use it to handle placeholder
	'auto' types.
	* cp-tree.h (PLACEHOLDER_TYPE_CONSTRAINTS_INFO): Define.
	(PLACEHOLDER_TYPE_CONSTRAINTS): Redefine in terms of the above.
	* pt.c (tsubst) <case TEMPLATE_TYPE_PARM>: Use
	PLACEHOLDER_TYPE_CONSTRAINTS_INFO instead.
	(make_constrained_placeholder_type): Set
	PLACEHOLDER_TYPE_CONSTRAINTS_INFO instead.
	(do_auto_deduction): Clarify comments about the outer_targs
	parameter.  Rework satisfaction of a placeholder type constraint
	to pass in the complete set of template arguments directly to
	constraints_satisfied_p.
	(splice_late_return_type): Use PLACEHOLDER_TYPE_CONSTRAINTS_INFO
	instead.  Also rebuild the the constraint info on the new auto.

gcc/testsuite/ChangeLog:

	PR c++/96443
	PR c++/96960
	* g++.dg/concepts/abbrev9.C: New test.
	* g++.dg/cpp2a/concepts-lambda15.C: New test.
	* g++.dg/cpp2a/concepts-placeholder3.C: New test.
	* g++.dg/cpp2a/concepts-return-req2.C: New test.
	* g++.dg/concepts-ts1.C: Add dg-bogus directive to the call to
	f15 that we expect to accept.
---
 gcc/cp/constraint.cc                          | 111 ++++++++----------
 gcc/cp/cp-tree.h                              |  16 ++-
 gcc/cp/pt.c                                   |  73 ++++++------
 gcc/testsuite/g++.dg/concepts/abbrev9.C       |  26 ++++
 .../g++.dg/cpp2a/concepts-lambda15.C          |  16 +++
 .../g++.dg/cpp2a/concepts-placeholder3.C      |  19 +++
 .../g++.dg/cpp2a/concepts-return-req2.C       |  13 ++
 gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C     |   2 +-
 8 files changed, 172 insertions(+), 104 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/concepts/abbrev9.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-lambda15.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-placeholder3.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-return-req2.C

diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index fcb249a642f..2b61ad8d9ea 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -1989,39 +1989,19 @@ type_deducible_p (tree expr, tree type, tree placeholder, tree args,
      references are preserved in the result.  */
   expr = force_paren_expr_uneval (expr);
 
-  /* Replace the constraints with the instantiated constraints. This
-     substitutes args into any template parameters in the trailing
-     result type.  */
-  tree saved_constr = PLACEHOLDER_TYPE_CONSTRAINTS (placeholder);
-  tree subst_constr
-    = tsubst_constraint (saved_constr,
-			 args,
-			 info.complain | tf_partial,
-			 info.in_decl);
-
-  if (subst_constr == error_mark_node)
-    return false;
-
-  PLACEHOLDER_TYPE_CONSTRAINTS (placeholder) = subst_constr;
-
-  /* Temporarily unlink the canonical type.  */
-  tree saved_type = TYPE_CANONICAL (placeholder);
-  TYPE_CANONICAL (placeholder) = NULL_TREE;
-
-  tree deduced_type
-    = do_auto_deduction (type,
-			 expr,
-			 placeholder,
-			 info.complain,
-			 adc_requirement);
-
-  PLACEHOLDER_TYPE_CONSTRAINTS (placeholder) = saved_constr;
-  TYPE_CANONICAL (placeholder) = saved_type;
+  /* When args is empty, we're evaluating a non-templated requires expression,
+     but even those are parsed under processing_template_decl == 1, and so the
+     placeholder 'auto' inside this return-type-requirement has level 2.  In
+     order to have all parms and arguments match up for satisfaction, we need
+     to pass a single level of OUTER_TARGS in this case.  */
+  if (!args)
+    args = make_tree_vec (0);
 
-  if (deduced_type == error_mark_node)
-    return false;
+  tree deduced_type = do_auto_deduction (type, expr, placeholder,
+					 info.complain, adc_requirement,
+					 /*outer_targs=*/args);
 
-  return true;
+  return deduced_type != error_mark_node;
 }
 
 /* True if EXPR can not be converted to TYPE.  */
@@ -2286,35 +2266,10 @@ tsubst_parameter_mapping (tree map, tree args, subst_info info)
         return error_mark_node;
       tree parm = TREE_VALUE (p);
       tree arg = TREE_PURPOSE (p);
-      tree new_arg = NULL_TREE;
-      if (TYPE_P (arg))
-        {
-          /* If a template parameter is declared with a placeholder, we can
-             get those in the argument list if decltype is applied to the
-             placeholder. For example:
-
-		template<auto T>
-		  requires C<decltype(T)>
-		void f() { }
-
-	     The normalized argument for C will be an auto type, so we'll
-             need to deduce the actual argument from the corresponding
-             initializer (whatever argument is provided for T), and use
-             that result in the instantiated parameter mapping.  */
-          if (tree auto_node = type_uses_auto (arg))
-            {
-              int level;
-              int index;
-	      template_parm_level_and_index (parm, &level, &index);
-	      tree init = TMPL_ARG (args, level, index);
-              new_arg = do_auto_deduction (arg, init, auto_node,
-					   complain, adc_variable_type,
-					   make_tree_vec (0));
-            }
-        }
-      else if (ARGUMENT_PACK_P (arg))
+      tree new_arg;
+      if (ARGUMENT_PACK_P (arg))
 	new_arg = tsubst_argument_pack (arg, args, complain, in_decl);
-      if (!new_arg)
+      else
 	{
 	  new_arg = tsubst_template_arg (arg, args, complain, in_decl);
 	  if (TYPE_P (new_arg))
@@ -3020,6 +2975,36 @@ satisfy_associated_constraints (tree t, tree args, sat_info info)
   return satisfy_constraint (t, args, info);
 }
 
+/* Return the normal form of the constraints on the placeholder 'auto'
+   type T.  */
+
+static tree
+normalize_placeholder_type_constraints (tree t, bool diag)
+{
+  gcc_assert (is_auto (t));
+  tree ci = PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t);
+  if (!ci)
+    return NULL_TREE;
+
+  tree constr = TREE_VALUE (ci);
+  /* The TREE_PURPOSE contains the set of template parameters that were in
+     scope for this placeholder type; use them as the initial template
+     parameters for normalization.  */
+  tree initial_parms = TREE_PURPOSE (ci);
+  /* The 'auto' itself is used as the first argument in its own constraints,
+     and its level is one greater than its template depth.  So in order to
+     capture all used template parameters, we need to add an extra level of
+     template parameters to the context; a dummy level suffices.  */
+  initial_parms
+    = tree_cons (size_int (initial_parms
+			   ? TMPL_PARMS_DEPTH (initial_parms) + 1 : 1),
+		 make_tree_vec (0), initial_parms);
+
+  norm_info info (diag ? tf_norm : tf_none);
+  info.initial_parms = initial_parms;
+  return normalize_constraint_expression (constr, info);
+}
+
 /* Evaluate EXPR as a constraint expression using ARGS, returning a
    satisfaction value. */
 
@@ -3029,8 +3014,6 @@ satisfy_constraint_expression (tree t, tree args, sat_info info)
   if (t == error_mark_node)
     return error_mark_node;
 
-  gcc_assert (EXPR_P (t));
-
   /* Get the normalized constraints.  */
   tree norm;
   if (args == NULL_TREE && concept_check_p (t))
@@ -3054,6 +3037,12 @@ satisfy_constraint_expression (tree t, tree args, sat_info info)
       norm_info ninfo (info.noisy () ? tf_norm : tf_none);
       norm = normalize_constraint_expression (t, ninfo);
     }
+  else if (is_auto (t))
+    {
+      norm = normalize_placeholder_type_constraints (t, info.noisy ());
+      if (!norm)
+	return boolean_true_node;
+    }
   else
     gcc_unreachable ();
 
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 38b31e3908f..544e99538a4 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -1578,11 +1578,19 @@ check_constraint_info (tree t)
 #define COMPOUND_REQ_NOEXCEPT_P(NODE) \
   TREE_LANG_FLAG_0 (TREE_CHECK (NODE, COMPOUND_REQ))
 
-/* The constraints on an 'auto' placeholder type, used in an argument deduction
-   constraint.  */
-#define PLACEHOLDER_TYPE_CONSTRAINTS(NODE) \
+/* A TREE_LIST whose TREE_VALUE is the constraints on the 'auto' placeholder
+   type NODE, used in an argument deduction constraint.  The TREE_PURPOSE
+   holds the set of template parameters that were in-scope when this 'auto'
+   was formed.  */
+#define PLACEHOLDER_TYPE_CONSTRAINTS_INFO(NODE) \
   DECL_SIZE_UNIT (TYPE_NAME (NODE))
 
+/* The constraints on the 'auto' placeholder type NODE.  */
+#define PLACEHOLDER_TYPE_CONSTRAINTS(NODE)		   \
+  (PLACEHOLDER_TYPE_CONSTRAINTS_INFO (NODE)		   \
+   ? TREE_VALUE (PLACEHOLDER_TYPE_CONSTRAINTS_INFO (NODE)) \
+   : NULL_TREE)
+
 /* True if NODE is a constraint.  */
 #define CONSTR_P(NODE)                  \
   (TREE_CODE (NODE) == ATOMIC_CONSTR    \
@@ -8420,7 +8428,7 @@ set_implicit_rvalue_p (tree ot)
 inline bool
 is_constrained_auto (const_tree t)
 {
-  return is_auto (t) && PLACEHOLDER_TYPE_CONSTRAINTS (t);
+  return is_auto (t) && PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t);
 }
 
 /* RAII class to push/pop class scope T; if T is not a class, do nothing.  */
diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
index f324f6a1e1b..0c7fd496935 100644
--- a/gcc/cp/pt.c
+++ b/gcc/cp/pt.c
@@ -15710,15 +15710,15 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
 			       ? tf_ignore_bad_quals : 0));
 	      }
 	    else if (TREE_CODE (t) == TEMPLATE_TYPE_PARM
-		     && PLACEHOLDER_TYPE_CONSTRAINTS (t)
+		     && PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t)
 		     && (r = (TEMPLATE_PARM_DESCENDANTS
 			      (TEMPLATE_TYPE_PARM_INDEX (t))))
 		     && (r = TREE_TYPE (r))
-		     && !PLACEHOLDER_TYPE_CONSTRAINTS (r))
+		     && !PLACEHOLDER_TYPE_CONSTRAINTS_INFO (r))
 	      /* Break infinite recursion when substituting the constraints
 		 of a constrained placeholder.  */;
 	    else if (TREE_CODE (t) == TEMPLATE_TYPE_PARM
-		     && !PLACEHOLDER_TYPE_CONSTRAINTS (t)
+		     && !PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t)
 		     && !CLASS_PLACEHOLDER_TEMPLATE (t)
 		     && (arg = TEMPLATE_TYPE_PARM_INDEX (t),
 			 r = TEMPLATE_PARM_DESCENDANTS (arg))
@@ -15741,8 +15741,8 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
 		  {
 		    /* Propagate constraints on placeholders since they are
 		       only instantiated during satisfaction.  */
-		    if (tree constr = PLACEHOLDER_TYPE_CONSTRAINTS (t))
-		      PLACEHOLDER_TYPE_CONSTRAINTS (r) = constr;
+		    if (tree ci = PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t))
+		      PLACEHOLDER_TYPE_CONSTRAINTS_INFO (r) = ci;
 		    else if (tree pl = CLASS_PLACEHOLDER_TEMPLATE (t))
 		      {
 			pl = tsubst_copy (pl, args, complain, in_decl);
@@ -28183,7 +28183,8 @@ make_constrained_placeholder_type (tree type, tree con, tree args)
   expr = build_concept_check (expr, type, args, tf_warning_or_error);
   --processing_template_decl;
 
-  PLACEHOLDER_TYPE_CONSTRAINTS (type) = expr;
+  PLACEHOLDER_TYPE_CONSTRAINTS_INFO (type)
+    = build_tree_list (current_template_parms, expr);
 
   /* Our canonical type depends on the constraint.  */
   TYPE_CANONICAL (type) = canonical_type_parameter (type);
@@ -29423,9 +29424,11 @@ do_class_deduction (tree ptype, tree tmpl, tree init,
    from INIT.  AUTO_NODE is the TEMPLATE_TYPE_PARM used for 'auto' in TYPE.
    The CONTEXT determines the context in which auto deduction is performed
    and is used to control error diagnostics.  FLAGS are the LOOKUP_* flags.
-   OUTER_TARGS are used during template argument deduction
-   (context == adc_unify) to properly substitute the result, and is ignored
-   in other contexts.
+
+   OUTER_TARGS is used during template argument deduction (context == adc_unify)
+   to properly substitute the result.  It's also used in the adc_unify and
+   adc_requirement contexts to communicate the the necessary template arguments
+   to satisfaction.  OUTER_TARGS is ignored in other contexts.
 
    For partial-concept-ids, extra args may be appended to the list of deduced
    template arguments prior to determining constraint satisfaction.  */
@@ -29586,30 +29589,21 @@ do_auto_deduction (tree type, tree init, tree auto_node,
     }
 
   /* Check any placeholder constraints against the deduced type. */
-  if (flag_concepts && !processing_template_decl)
-    if (tree check = NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
+  if (flag_concepts)
+    if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
       {
-        /* Use the deduced type to check the associated constraints. If we
-           have a partial-concept-id, rebuild the argument list so that
-           we check using the extra arguments. */
-	check = unpack_concept_check (check);
-	gcc_assert (TREE_CODE (check) == TEMPLATE_ID_EXPR);
-	tree cdecl = TREE_OPERAND (check, 0);
-	if (OVL_P (cdecl))
-	  cdecl = OVL_FIRST (cdecl);
-        tree cargs = TREE_OPERAND (check, 1);
-        if (TREE_VEC_LENGTH (cargs) > 1)
-          {
-            cargs = copy_node (cargs);
-            TREE_VEC_ELT (cargs, 0) = TREE_VEC_ELT (targs, 0);
-          }
-        else
-          cargs = targs;
+	if (processing_template_decl)
+	  /* In general we can't check satisfaction until we know all
+	     template arguments.  */
+	  return type;
 
-	/* Rebuild the check using the deduced arguments.  */
-	check = build_concept_check (cdecl, cargs, tf_none);
+	if ((context == adc_return_type || context == adc_variable_type)
+	    && current_function_decl
+	    && DECL_TEMPLATE_INFO (current_function_decl))
+	  outer_targs = DECL_TI_ARGS (current_function_decl);
 
-	if (!constraints_satisfied_p (check))
+	tree complete_targs = add_to_template_args (outer_targs, targs);
+	if (!constraints_satisfied_p (auto_node, complete_targs))
           {
             if (complain & tf_warning_or_error)
               {
@@ -29634,15 +29628,16 @@ do_auto_deduction (tree type, tree init, tree auto_node,
                            "placeholder constraints");
                     break;
                   }
-		diagnose_constraints (input_location, check, targs);
+		diagnose_constraints (input_location, auto_node, complete_targs);
               }
             return error_mark_node;
           }
       }
 
-  if (processing_template_decl && context != adc_unify)
-    outer_targs = current_template_args ();
-  targs = add_to_template_args (outer_targs, targs);
+  if (context == adc_unify)
+    targs = add_to_template_args (outer_targs, targs);
+  else if (processing_template_decl)
+    targs = add_to_template_args (current_template_args (), targs);
   return tsubst (type, targs, complain, NULL_TREE);
 }
 
@@ -29669,10 +29664,12 @@ splice_late_return_type (tree type, tree late_return_type)
 	TREE_VEC_ELT (auto_vec, 0) = new_auto;
 	tree targs = add_outermost_template_args (current_template_args (),
 						  auto_vec);
-	/* FIXME: We should also rebuild the constraint to refer to the new
-	   auto.  */
-	PLACEHOLDER_TYPE_CONSTRAINTS (new_auto)
-	  = PLACEHOLDER_TYPE_CONSTRAINTS (auto_node);
+	/* Also rebuild the constraint info in terms of the new auto.  */
+	if (tree ci = PLACEHOLDER_TYPE_CONSTRAINTS_INFO (auto_node))
+	  PLACEHOLDER_TYPE_CONSTRAINTS_INFO (new_auto)
+	    = build_tree_list (current_template_parms,
+			       tsubst_constraint (TREE_VALUE (ci), targs,
+						  tf_none, NULL_TREE));
 	TYPE_CANONICAL (new_auto) = canonical_type_parameter (new_auto);
 	return tsubst (type, targs, tf_none, NULL_TREE);
       }
diff --git a/gcc/testsuite/g++.dg/concepts/abbrev9.C b/gcc/testsuite/g++.dg/concepts/abbrev9.C
new file mode 100644
index 00000000000..865b44c6a63
--- /dev/null
+++ b/gcc/testsuite/g++.dg/concepts/abbrev9.C
@@ -0,0 +1,26 @@
+// { dg-do compile { target concepts } }
+
+template <class T, class U> concept same_as = __is_same(T, U);
+
+same_as<int> auto f(auto, auto y) {
+  return y; // { dg-error "deduced return type" }
+}
+
+template <class>
+struct A {
+  static auto g(auto x, auto y) -> same_as<decltype(x)> auto {
+    return y; // { dg-error "deduced return type" }
+  }
+};
+
+int main() {
+  f(0, 0);   // { dg-bogus "" }
+  f("", 0);  // { dg-bogus "" }
+  f(0, "");  // { dg-message "required from here" }
+  f("", ""); // { dg-message "required from here" }
+
+  A<void>::g(0, 0);   // { dg-bogus "" }
+  A<void>::g("", 0);  // { dg-message "required from here" }
+  A<void>::g(0, "");  // { dg-message "required from here" }
+  A<void>::g("", ""); // { dg-bogus "" }
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-lambda15.C b/gcc/testsuite/g++.dg/cpp2a/concepts-lambda15.C
new file mode 100644
index 00000000000..29df5d0b1ac
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-lambda15.C
@@ -0,0 +1,16 @@
+// PR c++/96960
+// { dg-do compile { target c++20 } }
+
+template <class, class> concept C0 = true;
+
+template <class T>
+concept C = requires(T t) {
+  { 42 } -> C0<char [([] { return 42; }())]>;
+};
+
+static_assert(C<int>);
+
+C0<char [([] { return 42; }())]> auto x = 42;
+
+int f(C0<char [([] { return 42; }())]> auto x);
+int y = f(42);
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder3.C b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder3.C
new file mode 100644
index 00000000000..87e3c093e28
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder3.C
@@ -0,0 +1,19 @@
+// PR c++/96443
+// { dg-do compile { target c++20 } }
+
+template <class T, class U> concept same_as = __is_same(T, U);
+
+auto f(auto x) -> same_as<decltype(x)> auto { return 0; }; // { dg-error "constraints" }
+void g(auto x) { same_as<decltype(x)> auto y = 0; } // { dg-error "constraints" }
+auto h(auto x) -> same_as<decltype(x.missing)> auto { return 0; } // { dg-error "constraints|missing" }
+template <class T, same_as<T> auto N> void i() {}
+
+int main() {
+  f(0); // { dg-bogus "" }
+  f(true); // { dg-message "required from here" }
+  g(0); // { dg-bogus "" }
+  g(true); // { dg-message "required from here" }
+  h(0); // { dg-message "required from here" }
+  i<int, 0>(); // { dg-bogus "" }
+  i<int, true>(); // { dg-error "no match|constraints" }
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-return-req2.C b/gcc/testsuite/g++.dg/cpp2a/concepts-return-req2.C
new file mode 100644
index 00000000000..77208bb7069
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-return-req2.C
@@ -0,0 +1,13 @@
+// Verify we check return-type-requirements by passing the entire set of
+// template arguments to normalization rather than first substituting into
+// the constraint.  The latter approach would induce a substitution failure and
+// cause the requires-expression to evaluate to false here.
+// { dg-do compile { target c++20 } }
+
+template <class, class>
+concept C1 = true;
+
+template <class T>
+concept C2 = requires { { 0 } -> C1<typename T::type>; };
+
+static_assert(C2<int>);
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C b/gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C
index 1cefe3b243f..a116cac4ea4 100644
--- a/gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C
@@ -40,7 +40,7 @@ void driver()
   f3('a'); // { dg-error "" }
   f4(0, 0);
   f4(0, 'a'); // { dg-error "" }
-  f15(0);
+  f15(0); // { dg-bogus "" }
   f15('a'); // { dg-message "" }
 }
 
-- 
2.31.0.rc0


^ permalink raw reply	[flat|nested] 29+ messages in thread

* [PATCH 5/6] c++: Clean up normalization / satisfaction routines
  2021-02-08 19:03 [PATCH 1/4] c++: Avoid building garbage trees from tsubst_requires_expr Patrick Palka
                   ` (3 preceding siblings ...)
  2021-02-09  5:09 ` [PATCH 1/4] c++: Avoid building garbage trees from tsubst_requires_expr Jason Merrill
@ 2021-02-28 17:58 ` Patrick Palka
  2021-03-01 22:50   ` Jason Merrill
  2021-02-28 17:59 ` [PATCH 6/6] c++: Consolidate REQUIRES_EXPR evaluation/diagnostic routines Patrick Palka
  5 siblings, 1 reply; 29+ messages in thread
From: Patrick Palka @ 2021-02-28 17:58 UTC (permalink / raw)
  To: gcc-patches

This patch mostly performs some straightforward refactoring:

  - Renamed satisfy_constraint to satisfy_normalized_constraints
  - Renamed the three-parameter version of satisfy_constraint_expression
    to satisfy_nondeclaration_constraints
  - Removed normalize_(non)?template_requirements
  - Removed satisfy_associated_constraints (and made its callers
    check for dependent template args sooner, before normalization)
  - Removed the tsubst_flags_t parameter of evaluate_concept_check
  - Combined the two versions of constraint_satisfaction_value
  - Combined the two versions of constraint_satisfied_p

Additionally, this patch removes the handling of bare
constraint-expressions from satisfy_nondeclaration_constraints, and
hence constraints_satisfied_p and constraint_satisfaction_value now only
take things that carry their own template information needed for
normalization.  In practice, this only means it's no longer possible to
evaluate bare REQUIRES_EXPRs via the satisfaction routines, and so this
patch adjusts the affected callers to instead use tsubst_requires_expr.
For convenience, the function diagnose_constraints continues to accept
REQUIRES_EXPRs, but it now handles them by calling diagnose_require_expr
directly.

(That we used to evaluate REQUIRES_EXPR via satisfaction might even be a
correctness issue: since we cache satisfaction in special ways that don't
apply to regular evaluation, going through satisfaction could in theory
cause us to reuse a cached value for a REQUIRES_EXPR when we shouldn't
have.)

gcc/cp/ChangeLog:

	* constexpr.c (cxx_eval_call_expression): Adjust call to
	evaluate_concept_check.
	(cxx_eval_constant_expression) <case REQUIRES_EXPR>: Use
	tsubst_requires_expr instead of satisfy_constraint_expression.
	<case TEMPLATE_ID_EXPR>: Adjust call to evaluate_concept_check.
	* constraint.cc (struct sat_info): Adjust comment about which
	satisfaction entrypoints use noisy-unsat.
	(normalize_template_requirements): Remove (and adjust callers
	appropriately).
	(normalize_nontemplate_requirements): Likewise.
	(tsubst_nested_requirement): Use constraint_satisfaction_value
	instead of satisfy_constraint_expression, which'll do the
	noisy replaying of ill-formed quiet satisfaction for us.
	(decl_satisfied_cache): Adjust comment.
	(satisfy_constraint): Rename to ...
	(satisfy_normalized_constraints): ... this.
	(satisfy_associated_constraints): Remove (and make its
	callers check for dependent arguments).
	(satisfy_constraint_expression): Rename to ...
	(satisfy_nondeclaration_constraints): ... this.  Assert that
	'args' is empty when 't' is a concept-id.  Removing handling
	bare constraint-expressions.  Adjust comment accordingly.
	(satisfy_declaration_constraints): Assert in the two-parameter
	version that 't' is not a TEMPLATE_DECL.  Adjust following
	removal of normalize_(non)?template_requirements and
	satisfy_asociated_constraints.
	(constraint_satisfaction_value): Combine the two- and
	three-parameter versions in the natural way.
	(constraints_satisfied_p): Combine the one- and two-parameter
	versions in the natural way.  Improve documentation.
	(evaluate_concept_check): Remove 'complain' parameter.  Use
	constraint_satisfaction_value instead of
	satisfy_constraint_expression.
	(diagnose_nested_requirement): Adjust following renaming of
	satisfy_constraint_expression.
	(diagnose_constraints): Handle REQUIRES_EXPR by going through
	diagnose_requires_expr directly instead of treating it as a
	constraint-expression.  Improve documentation.
	* cp-gimplify.c (cp_genericize_r) <case CALL_EXPR>: Adjust call
	to evaluate_concept_check.
	<case REQUIRES_EXPR>: Use tsubst_requires_expr instead of
	constraints_satisfied_p.
	<case TEMPLATE_ID_EXPR>: Adjust call to evaluate_concept_check.
	* cp-tree.h (evaluate_concept_check): Remove tsubst_flag_t
	parameter.
	(satisfy_constraint_expression): Remove declaration.
	(constraints_satisfied_p): Remove one-parameter declaration.
	Add a default argument to the two-parameter declaration.
	* cvt.c (convert_to_void): Adjust call to
	evaluate_concept_check.
---
 gcc/cp/constexpr.c   |   6 +-
 gcc/cp/constraint.cc | 210 ++++++++++++++++---------------------------
 gcc/cp/cp-gimplify.c |   7 +-
 gcc/cp/cp-tree.h     |   6 +-
 gcc/cp/cvt.c         |   2 +-
 5 files changed, 85 insertions(+), 146 deletions(-)

diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c
index cd0a68e9fd6..f940e3e5985 100644
--- a/gcc/cp/constexpr.c
+++ b/gcc/cp/constexpr.c
@@ -2257,7 +2257,7 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
 {
   /* Handle concept checks separately.  */
   if (concept_check_p (t))
-    return evaluate_concept_check (t, tf_warning_or_error);
+    return evaluate_concept_check (t);
 
   location_t loc = cp_expr_loc_or_input_loc (t);
   tree fun = get_function_named_in_call (t);
@@ -6905,7 +6905,7 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
          '!requires (T t) { ... }' which is not transformed into
          a constraint.  */
       if (!processing_template_decl)
-        return satisfy_constraint_expression (t);
+	return tsubst_requires_expr (t, NULL_TREE, tf_none, NULL_TREE);
       else
         *non_constant_p = true;
       return t;
@@ -6941,7 +6941,7 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
 
 	if (!processing_template_decl
 	    && !uid_sensitive_constexpr_evaluation_p ())
-	  r = evaluate_concept_check (t, tf_warning_or_error);
+	  r = evaluate_concept_check (t);
 	else
 	  *non_constant_p = true;
 
diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index 2b61ad8d9ea..cf319b34da0 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -107,10 +107,9 @@ struct subst_info
    a constraint is not satisfied.
 
    The entrypoints to satisfaction for which we set noisy+unsat are
-   diagnose_constraints and diagnose_nested_requirement.  The entrypoints for
-   which we set noisy-unsat are the replays inside constraint_satisfaction_value,
-   evaluate_concept_check and tsubst_nested_requirement.  In other entrypoints,
-   e.g. constraints_satisfied_p, we enter satisfaction quietly (both flags
+   diagnose_constraints and diagnose_nested_requirement.  The entrypoint for
+   which we set noisy-unsat is the replay inside constraint_satisfaction_value.
+   From constraints_satisfied_p, we enter satisfaction quietly (both flags
    cleared).  */
 
 struct sat_info : subst_info
@@ -133,7 +132,7 @@ struct sat_info : subst_info
   bool diagnose_unsatisfaction;
 };
 
-static tree satisfy_constraint_expression (tree, tree, sat_info);
+static tree constraint_satisfaction_value (tree, tree, sat_info);
 
 /* True if T is known to be some type other than bool. Note that this
    is false for dependent types and errors.  */
@@ -958,22 +957,6 @@ normalize_concept_definition (tree tmpl, bool diag = false)
   return norm;
 }
 
-/* Returns the normal form of TMPL's requirements.  */
-
-static tree
-normalize_template_requirements (tree tmpl, bool diag = false)
-{
-  return get_normalized_constraints_from_decl (tmpl, diag);
-}
-
-/* Returns the normal form of TMPL's requirements.  */
-
-static tree
-normalize_nontemplate_requirements (tree decl, bool diag = false)
-{
-  return get_normalized_constraints_from_decl (decl, diag);
-}
-
 /* Normalize an EXPR as a constraint.  */
 
 static tree
@@ -2068,15 +2051,8 @@ tsubst_compound_requirement (tree t, tree args, subst_info info)
 static tree
 tsubst_nested_requirement (tree t, tree args, subst_info info)
 {
-  /* Perform satisfaction quietly first.  */
   sat_info quiet (tf_none, info.in_decl);
-  tree result = satisfy_constraint_expression (t, args, quiet);
-  if (result == error_mark_node)
-    {
-      /* Replay the error.  */
-      sat_info noisy (tf_warning_or_error, info.in_decl);
-      satisfy_constraint_expression (t, args, noisy);
-    }
+  tree result = constraint_satisfaction_value (t, args, quiet);
   if (result != boolean_true_node)
     return error_mark_node;
   return boolean_true_node;
@@ -2459,7 +2435,7 @@ struct sat_hasher : ggc_ptr_hash<sat_entry>
 /* Cache the result of satisfy_atom.  */
 static GTY((deletable)) hash_table<sat_hasher> *sat_cache;
 
-/* Cache the result of constraint_satisfaction_value.  */
+/* Cache the result of satisfy_declaration_constraints.  */
 static GTY((deletable)) hash_map<tree, tree> *decl_satisfied_cache;
 
 /* A tool used by satisfy_atom to help manage satisfaction caching and to
@@ -2941,7 +2917,7 @@ satisfy_constraint_r (tree t, tree args, sat_info info)
 /* Check that the normalized constraint T is satisfied for ARGS.  */
 
 static tree
-satisfy_constraint (tree t, tree args, sat_info info)
+satisfy_normalized_constraints (tree t, tree args, sat_info info)
 {
   auto_timevar time (TV_CONSTRAINT_SAT);
 
@@ -2957,24 +2933,6 @@ satisfy_constraint (tree t, tree args, sat_info info)
   return satisfy_constraint_r (t, args, info);
 }
 
-/* Check the normalized constraints T against ARGS, returning a satisfaction
-   value (either true, false, or error).  */
-
-static tree
-satisfy_associated_constraints (tree t, tree args, sat_info info)
-{
-  /* If there are no constraints then this is trivially satisfied.  */
-  if (!t)
-    return boolean_true_node;
-
-  /* If any arguments depend on template parameters, we can't
-     check constraints. Pretend they're satisfied for now.  */
-  if (args && uses_template_parms (args))
-    return boolean_true_node;
-
-  return satisfy_constraint (t, args, info);
-}
-
 /* Return the normal form of the constraints on the placeholder 'auto'
    type T.  */
 
@@ -3005,19 +2963,20 @@ normalize_placeholder_type_constraints (tree t, bool diag)
   return normalize_constraint_expression (constr, info);
 }
 
-/* Evaluate EXPR as a constraint expression using ARGS, returning a
-   satisfaction value. */
+/* Evaluate the constraints of T using ARGS, returning a satisfaction value.
+   Here, T can be a concept-id, nested-requirement or placeholder 'auto'.  */
 
 static tree
-satisfy_constraint_expression (tree t, tree args, sat_info info)
+satisfy_nondeclaration_constraints (tree t, tree args, sat_info info)
 {
   if (t == error_mark_node)
     return error_mark_node;
 
   /* Get the normalized constraints.  */
   tree norm;
-  if (args == NULL_TREE && concept_check_p (t))
+  if (concept_check_p (t))
     {
+      gcc_assert (!args);
       tree id = unpack_concept_check (t);
       args = TREE_OPERAND (id, 1);
       tree tmpl = get_concept_check_template (id);
@@ -3032,11 +2991,6 @@ satisfy_constraint_expression (tree t, tree args, sat_info info)
       ninfo.initial_parms = TREE_TYPE (t);
       norm = normalize_constraint_expression (TREE_OPERAND (t, 0), ninfo);
     }
-  else if (EXPR_P (t))
-    {
-      norm_info ninfo (info.noisy () ? tf_norm : tf_none);
-      norm = normalize_constraint_expression (t, ninfo);
-    }
   else if (is_auto (t))
     {
       norm = normalize_placeholder_type_constraints (t, info.noisy ());
@@ -3047,23 +3001,16 @@ satisfy_constraint_expression (tree t, tree args, sat_info info)
     gcc_unreachable ();
 
   /* Perform satisfaction.  */
-  return satisfy_constraint (norm, args, info);
+  return satisfy_normalized_constraints (norm, args, info);
 }
 
-/* Used only to evaluate requires-expressions during constant expression
-   evaluation.  */
-
-tree
-satisfy_constraint_expression (tree expr)
-{
-  sat_info info (tf_none, NULL_TREE);
-  return satisfy_constraint_expression (expr, NULL_TREE, info);
-}
+/* Evaluate the associated constraints of the template specialization T
+   according to INFO, returning a satisfaction value.  */
 
 static tree
 satisfy_declaration_constraints (tree t, sat_info info)
 {
-  gcc_assert (DECL_P (t));
+  gcc_assert (DECL_P (t) && TREE_CODE (t) != TEMPLATE_DECL);
   const tree saved_t = t;
 
   /* For inherited constructors, consider the original declaration;
@@ -3083,26 +3030,24 @@ satisfy_declaration_constraints (tree t, sat_info info)
     if (tree *result = hash_map_safe_get (decl_satisfied_cache, saved_t))
       return *result;
 
-  /* Get the normalized constraints.  */
-  tree norm = NULL_TREE;
   tree args = NULL_TREE;
   if (tree ti = DECL_TEMPLATE_INFO (t))
     {
-      tree tmpl = TI_TEMPLATE (ti);
-      norm = normalize_template_requirements (tmpl, info.noisy ());
-
       /* The initial parameter mapping is the complete set of
 	 template arguments substituted into the declaration.  */
       args = TI_ARGS (ti);
       if (inh_ctor_targs)
 	args = add_outermost_template_args (args, inh_ctor_targs);
-    }
-  else
-    {
-      /* These should be empty until we allow constraints on non-templates.  */
-      norm = normalize_nontemplate_requirements (t, info.noisy ());
+
+      /* If any arguments depend on template parameters, we can't
+	 check constraints. Pretend they're satisfied for now.  */
+      if (uses_template_parms (args))
+	return boolean_true_node;
     }
 
+  /* Get the normalized constraints.  */
+  tree norm = get_normalized_constraints_from_decl (t, info.noisy ());
+
   unsigned ftc_count = vec_safe_length (failed_type_completions);
 
   tree result = boolean_true_node;
@@ -3111,7 +3056,7 @@ satisfy_declaration_constraints (tree t, sat_info info)
       if (!push_tinst_level (t))
 	return result;
       push_access_scope (t);
-      result = satisfy_associated_constraints (norm, args, info);
+      result = satisfy_normalized_constraints (norm, args, info);
       pop_access_scope (t);
       pop_tinst_level ();
     }
@@ -3134,6 +3079,10 @@ satisfy_declaration_constraints (tree t, sat_info info)
   return result;
 }
 
+/* Evaluate the associated constraints of the template T using ARGS as the
+   innermost set of template arguments and according to INFO, returning a
+   satisfaction value.  */
+
 static tree
 satisfy_declaration_constraints (tree t, tree args, sat_info info)
 {
@@ -3144,14 +3093,19 @@ satisfy_declaration_constraints (tree t, tree args, sat_info info)
 
   args = add_outermost_template_args (t, args);
 
+  /* If any arguments depend on template parameters, we can't
+     check constraints. Pretend they're satisfied for now.  */
+  if (uses_template_parms (args))
+    return boolean_true_node;
+
   tree result = boolean_true_node;
-  if (tree norm = normalize_template_requirements (t, info.noisy ()))
+  if (tree norm = get_normalized_constraints_from_decl (t, info.noisy ()))
     {
       if (!push_tinst_level (t, args))
 	return result;
       tree pattern = DECL_TEMPLATE_RESULT (t);
       push_access_scope (pattern);
-      result = satisfy_associated_constraints (norm, args, info);
+      result = satisfy_normalized_constraints (norm, args, info);
       pop_access_scope (pattern);
       pop_tinst_level ();
     }
@@ -3159,62 +3113,50 @@ satisfy_declaration_constraints (tree t, tree args, sat_info info)
   return result;
 }
 
+/* A wrapper around satisfy_declaration_constraints and
+   satisfy_nondeclaration_constraints which additionally replays
+   quiet ill-formed satisfaction noisily, so that ill-formed
+   satisfaction always gets diagnosed.  */
+
 static tree
-constraint_satisfaction_value (tree t, sat_info info)
+constraint_satisfaction_value (tree t, tree args, sat_info info)
 {
   tree r;
   if (DECL_P (t))
-    r = satisfy_declaration_constraints (t, info);
+    {
+      if (args)
+	r = satisfy_declaration_constraints (t, args, info);
+      else
+	r = satisfy_declaration_constraints (t, info);
+    }
   else
-    r = satisfy_constraint_expression (t, NULL_TREE, info);
+    r = satisfy_nondeclaration_constraints (t, args, info);
   if (r == error_mark_node && info.quiet ()
       && !(DECL_P (t) && TREE_NO_WARNING (t)))
     {
-      /* Replay the error with re-normalized requirements.  */
+      /* Replay the error noisily.  */
       sat_info noisy (tf_warning_or_error, info.in_decl);
-      constraint_satisfaction_value (t, noisy);
-      if (DECL_P (t))
+      constraint_satisfaction_value (t, args, noisy);
+      if (DECL_P (t) && !args)
 	/* Avoid giving these errors again.  */
 	TREE_NO_WARNING (t) = true;
     }
   return r;
 }
 
-static tree
-constraint_satisfaction_value (tree t, tree args, sat_info info)
-{
-  tree r;
-  if (DECL_P (t))
-    r = satisfy_declaration_constraints (t, args, info);
-  else
-    r = satisfy_constraint_expression (t, args, info);
-  if (r == error_mark_node && info.quiet ())
-    {
-      /* Replay the error with re-normalized requirements.  */
-      sat_info noisy (tf_warning_or_error, info.in_decl);
-      constraint_satisfaction_value (t, args, noisy);
-    }
-  return r;
-}
-
-/* True iff the result of satisfying T is BOOLEAN_TRUE_NODE and false
-   otherwise, even in the case of errors.  */
+/* True iff the result of satisfying T using ARGS is BOOLEAN_TRUE_NODE
+   and false otherwise, even in the case of errors.
 
-bool
-constraints_satisfied_p (tree t)
-{
-  if (!flag_concepts)
-    return true;
-
-  sat_info quiet (tf_none, NULL_TREE);
-  return constraint_satisfaction_value (t, quiet) == boolean_true_node;
-}
-
-/* True iff the result of satisfying T with ARGS is BOOLEAN_TRUE_NODE
-    and false otherwise, even in the case of errors.  */
+   Here, T can be:
+     - a template declaration (in which case ARGS is an innermost template
+       argument set for T)
+     - a template specialization (in which case ARGS must be empty)
+     - a concept-id (in which case ARGS must be empty)
+     - a nested-requirement (in which case ARGS is a complete argument set)
+     - a placeholder 'auto' (in which case ARGS is a complete argument set).  */
 
 bool
-constraints_satisfied_p (tree t, tree args)
+constraints_satisfied_p (tree t, tree args/*= NULL_TREE */)
 {
   if (!flag_concepts)
     return true;
@@ -3227,7 +3169,7 @@ constraints_satisfied_p (tree t, tree args)
    evaluation of template-ids as id-expressions.  */
 
 tree
-evaluate_concept_check (tree check, tsubst_flags_t complain)
+evaluate_concept_check (tree check)
 {
   if (check == error_mark_node)
     return error_mark_node;
@@ -3236,14 +3178,7 @@ evaluate_concept_check (tree check, tsubst_flags_t complain)
 
   /* Check for satisfaction without diagnostics.  */
   sat_info quiet (tf_none, NULL_TREE);
-  tree result = satisfy_constraint_expression (check, NULL_TREE, quiet);
-  if (result == error_mark_node && (complain & tf_error))
-    {
-      /* Replay the error with re-normalized requirements.  */
-      sat_info noisy (tf_warning_or_error, NULL_TREE);
-      satisfy_constraint_expression (check, NULL_TREE, noisy);
-    }
-  return result;
+  return constraint_satisfaction_value (check, /*args=*/NULL_TREE, quiet);
 }
 
 /*---------------------------------------------------------------------------
@@ -3709,7 +3644,7 @@ diagnose_nested_requirement (tree req, tree args)
 {
   /* Quietly check for satisfaction first.  */
   sat_info quiet (tf_none, NULL_TREE);
-  tree result = satisfy_constraint_expression (req, args, quiet);
+  tree result = satisfy_nondeclaration_constraints (req, args, quiet);
   if (result == boolean_true_node)
     return;
 
@@ -3721,7 +3656,7 @@ diagnose_nested_requirement (tree req, tree args)
       inform (loc, "nested requirement %qE is not satisfied, because", expr);
 
       sat_info noisy (tf_warning_or_error, NULL_TREE, /*diag_unsat=*/true);
-      satisfy_constraint_expression (req, args, noisy);
+      satisfy_nondeclaration_constraints (req, args, noisy);
     }
   else
     inform (loc, "nested requirement %qE is not satisfied", expr);
@@ -3854,7 +3789,9 @@ diagnosing_failed_constraint::replay_errors_p ()
 }
 
 /* Emit diagnostics detailing the failure ARGS to satisfy the constraints
-   of T. Here, T can be either a constraint or a declaration.  */
+   of T.  Here, T and ARGS are as in constraints_satisfied_p, except that T
+   can also be a REQUIRES_EXPR (in which case ARGS is must be empty).  The
+   latter is used by finish_static_assert to diagnose a false REQUIRES_EXPR.  */
 
 void
 diagnose_constraints (location_t loc, tree t, tree args)
@@ -3866,8 +3803,13 @@ diagnose_constraints (location_t loc, tree t, tree args)
 
   /* Replay satisfaction, but diagnose unsatisfaction.  */
   sat_info noisy (tf_warning_or_error, NULL_TREE, /*diag_unsat=*/true);
-  if (!args)
-    constraint_satisfaction_value (t, noisy);
+  if (TREE_CODE (t) == REQUIRES_EXPR)
+    {
+      gcc_assert (!args);
+      ++current_constraint_diagnosis_depth;
+      diagnose_requires_expr (t, /*map=*/NULL_TREE, /*in_decl=*/NULL_TREE);
+      --current_constraint_diagnosis_depth;
+    }
   else
     constraint_satisfaction_value (t, args, noisy);
 
diff --git a/gcc/cp/cp-gimplify.c b/gcc/cp/cp-gimplify.c
index abb8a6ef078..64b1dc1b433 100644
--- a/gcc/cp/cp-gimplify.c
+++ b/gcc/cp/cp-gimplify.c
@@ -1381,7 +1381,7 @@ cp_genericize_r (tree *stmt_p, int *walk_subtrees, void *data)
 	 normal functions.  */
       if (concept_check_p (stmt))
 	{
-	  *stmt_p = evaluate_concept_check (stmt, tf_warning_or_error);
+	  *stmt_p = evaluate_concept_check (stmt);
 	  * walk_subtrees = 0;
 	  break;
 	}
@@ -1453,15 +1453,14 @@ cp_genericize_r (tree *stmt_p, int *walk_subtrees, void *data)
 
     case REQUIRES_EXPR:
       /* Emit the value of the requires-expression.  */
-      *stmt_p = constant_boolean_node (constraints_satisfied_p (stmt),
-				       boolean_type_node);
+      *stmt_p = tsubst_requires_expr (stmt, NULL_TREE, tf_none, NULL_TREE);
       *walk_subtrees = 0;
       break;
 
     case TEMPLATE_ID_EXPR:
       gcc_assert (concept_check_p (stmt));
       /* Emit the value of the concept check.  */
-      *stmt_p = evaluate_concept_check (stmt, tf_warning_or_error);
+      *stmt_p = evaluate_concept_check (stmt);
       walk_subtrees = 0;
       break;
 
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 544e99538a4..995e13f4a6e 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -8123,10 +8123,8 @@ struct processing_constraint_expression_sentinel
 extern bool processing_constraint_expression_p	();
 
 extern tree unpack_concept_check		(tree);
-extern tree evaluate_concept_check              (tree, tsubst_flags_t);
-extern tree satisfy_constraint_expression	(tree);
-extern bool constraints_satisfied_p		(tree);
-extern bool constraints_satisfied_p		(tree, tree);
+extern tree evaluate_concept_check              (tree);
+extern bool constraints_satisfied_p		(tree, tree = NULL_TREE);
 extern bool* lookup_subsumption_result          (tree, tree);
 extern bool save_subsumption_result             (tree, tree, bool);
 extern tree find_template_parameters		(tree, tree);
diff --git a/gcc/cp/cvt.c b/gcc/cp/cvt.c
index e809f0e4068..3f5467c8283 100644
--- a/gcc/cp/cvt.c
+++ b/gcc/cp/cvt.c
@@ -1170,7 +1170,7 @@ convert_to_void (tree expr, impl_conv_void implicit, tsubst_flags_t complain)
   /* Explicitly evaluate void-converted concept checks since their
      satisfaction may produce ill-formed programs.  */
    if (concept_check_p (expr))
-     expr = evaluate_concept_check (expr, tf_warning_or_error);
+     expr = evaluate_concept_check (expr);
 
   if (VOID_TYPE_P (TREE_TYPE (expr)))
     return expr;
-- 
2.31.0.rc0


^ permalink raw reply	[flat|nested] 29+ messages in thread

* [PATCH 6/6] c++: Consolidate REQUIRES_EXPR evaluation/diagnostic routines
  2021-02-08 19:03 [PATCH 1/4] c++: Avoid building garbage trees from tsubst_requires_expr Patrick Palka
                   ` (4 preceding siblings ...)
  2021-02-28 17:58 ` [PATCH 5/6] c++: Clean up normalization / satisfaction routines Patrick Palka
@ 2021-02-28 17:59 ` Patrick Palka
  2021-03-02  2:18   ` Jason Merrill
  5 siblings, 1 reply; 29+ messages in thread
From: Patrick Palka @ 2021-02-28 17:59 UTC (permalink / raw)
  To: gcc-patches

This folds the diagnose_requires_expr routines into the corresponding
tsubst_requires_expr ones.  This is achieved by making the latter
routines take a sat_info instead of a subst_info, and assigning the
appropriate meanings to the flags sat_info::noisy and
sat_info::diagnose_unsatisfaction_p during tsubst_requires_expr:
info.noisy() controls whether to diagnose invalid types and expressions
inside the requires-expression, and info.diagnose_unsatisfaction_p()
controls whether to diagnose why the requires-expression evaluates to
false.

gcc/cp/ChangeLog:

	* constraint.cc (struct sat_info): Document the different
	meanings of noisy() and diagnose_unsatisfaction_p() during
	satisfaction and requires-expression evaluation.
	(tsubst_valid_expression_requirement): Take a sat_info instead
	of a subst_info.  Perform the substitution quietly first.  Fold
	in error-replaying code from diagnose_valid_expression.
	(tsubst_simple_requirement): Take a sat_info instead of a
	subst_info.
	(tsubst_type_requirement_1): New.  Fold in error-replaying code
	from diagnose_valid_type.
	(tsubst_type_requirement): Use the above.  Take a sat_info
	instead of a subst_info.
	(tsubst_compound_requirement): Likewise.  Fold in
	error-replaying code from diagnose_compound_requirement.
	(tsubst_nested_requirement): Take a sat_info instead of a
	subst_info.  Fold in error-replaying code from
	diagnose_nested_requirement.
	(tsubst_requirement): Take a sat_info instead of a subst_info.
	(tsubst_requires_expr): Split into two versions, one that takes
	a sat_info argument and another that takes a complain and
	in_decl argument.  Remove outdated documentation.  Document the
	effects of the sat_info argument.
	(diagnose_trait_expr): Make static.  Take a template argument
	vector instead of a parameter mapping.
	(diagnose_valid_expression): Remove.
	(diagnose_valid_type): Remove.
	(diagnose_simple_requirement): Remove.
	(diagnose_compound_requirement): Remove.
	(diagnose_type_requirement): Remove.
	(diagnose_nested_requirement): Remove.
	(diagnose_requirement): Remove.
	(diagnose_requires_expr): Remove.
	(diagnose_atomic_constraint): Take a sat_info instead of a
	subst_info.  Adjust call to diagnose_trait_expr.  Call
	tsubst_requires_expr instead of diagnose_requires_expr.
	(diagnose_constraints): Call tsubst_requires_expr instead of
	diagnose_requires_expr.

gcc/testsuite/ChangeLog:

	* g++.dg/concepts/diagnostic1.C: Adjust expected diagnostics
	now that we diagnose only the first failed requirement of a
	requires-expression.
---
 gcc/cp/constraint.cc                        | 416 +++++++++-----------
 gcc/testsuite/g++.dg/concepts/diagnostic1.C |   2 +-
 2 files changed, 179 insertions(+), 239 deletions(-)

diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index cf319b34da0..31f32c25dfe 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -100,17 +100,30 @@ struct subst_info
 
 /* Provides additional context for satisfaction.
 
-   The flag noisy() controls whether to diagnose ill-formed satisfaction,
-   such as the satisfaction value of an atom being non-bool or non-constant.
-
-   The flag diagnose_unsatisfaction_p() controls whether to explain why
-   a constraint is not satisfied.
-
-   The entrypoints to satisfaction for which we set noisy+unsat are
-   diagnose_constraints and diagnose_nested_requirement.  The entrypoint for
-   which we set noisy-unsat is the replay inside constraint_satisfaction_value.
-   From constraints_satisfied_p, we enter satisfaction quietly (both flags
-   cleared).  */
+   During satisfaction:
+    - The flag noisy() controls whether to diagnose ill-formed satisfaction,
+      such as the satisfaction value of an atom being non-bool or non-constant.
+    - The flag diagnose_unsatisfaction_p() controls whether to explain why
+      a constraint is not satisfied.
+    - We enter satisfaction with noisy+unsat from diagnose_constraints.
+    - We enter satisfaction with noisy-unsat from the replay inside
+      constraint_satisfaction_value.
+    - We enter satisfaction quietly (both flags cleared) from
+      constraints_satisfied_p.
+
+   During evaluation of a requires-expression:
+    - The flag noisy() controls whether to diagnose ill-formed types and
+      expressions inside its requirements.
+    - The flag diagnose_unsatisfaction_p() controls whether to explain why
+      the requires-expression evaluates to false.
+    - We enter tsubst_requires_expr with noisy+unsat from diagnose_constraints
+      and from diagnose_atomic_constraint.
+    - We enter tsubst_requires_expr with noisy-unsat from
+      cp_parser_requires_expression when processing a requires-expression that
+      appears outside a template.
+    - We enter tsubst_requires_expr quietly (both flags cleared) when
+      substituting through a requires-expression as part of template
+      instantiation.  */
 
 struct sat_info : subst_info
 {
@@ -1926,22 +1939,44 @@ hash_placeholder_constraint (tree c)
   return val;
 }
 
-/* Substitute through the simple requirement.  */
+/* Substitute through the expression of a simple requirement or
+   compound requirement.  */
 
 static tree
-tsubst_valid_expression_requirement (tree t, tree args, subst_info info)
+tsubst_valid_expression_requirement (tree t, tree args, sat_info info)
 {
-  tree r = tsubst_expr (t, args, info.complain, info.in_decl, false);
-  if (convert_to_void (r, ICV_STATEMENT, info.complain) == error_mark_node)
-    return error_mark_node;
-  return r;
+  tree r = tsubst_expr (t, args, tf_none, info.in_decl, false);
+  if (convert_to_void (r, ICV_STATEMENT, tf_none) != error_mark_node)
+    return r;
+
+  if (info.diagnose_unsatisfaction_p ())
+    {
+      location_t loc = cp_expr_loc_or_input_loc (t);
+      if (diagnosing_failed_constraint::replay_errors_p ())
+	{
+	  inform (loc, "the required expression %qE is invalid, because", t);
+	  if (r == error_mark_node)
+	    tsubst_expr (t, args, info.complain, info.in_decl, false);
+	  else
+	    convert_to_void (r, ICV_STATEMENT, info.complain);
+	}
+      else
+	inform (loc, "the required expression %qE is invalid", t);
+    }
+  else if (info.noisy ())
+    {
+      r = tsubst_expr (t, args, info.complain, info.in_decl, false);
+      convert_to_void (r, ICV_STATEMENT, info.complain);
+    }
+
+  return error_mark_node;
 }
 
 
 /* Substitute through the simple requirement.  */
 
 static tree
-tsubst_simple_requirement (tree t, tree args, subst_info info)
+tsubst_simple_requirement (tree t, tree args, sat_info info)
 {
   tree t0 = TREE_OPERAND (t, 0);
   tree expr = tsubst_valid_expression_requirement (t0, args, info);
@@ -1950,13 +1985,41 @@ tsubst_simple_requirement (tree t, tree args, subst_info info)
   return boolean_true_node;
 }
 
+/* Subroutine of tsubst_type_requirement that performs the actual substitution
+   and diagnosing.  Also used by tsubst_compound_requirement.  */
+
+static tree
+tsubst_type_requirement_1 (tree t, tree args, sat_info info, location_t loc)
+{
+  tree r = tsubst (t, args, tf_none, info.in_decl);
+  if (r != error_mark_node)
+    return r;
+
+  if (info.diagnose_unsatisfaction_p ())
+    {
+      if (diagnosing_failed_constraint::replay_errors_p ())
+	{
+	  /* Replay the substitution error.  */
+	  inform (loc, "the required type %qT is invalid, because", t);
+	  tsubst (t, args, info.complain, info.in_decl);
+	}
+      else
+	inform (loc, "the required type %qT is invalid", t);
+    }
+  else if (info.noisy ())
+    tsubst (t, args, info.complain, info.in_decl);
+
+  return error_mark_node;
+}
+
+
 /* Substitute through the type requirement.  */
 
 static tree
-tsubst_type_requirement (tree t, tree args, subst_info info)
+tsubst_type_requirement (tree t, tree args, sat_info info)
 {
   tree t0 = TREE_OPERAND (t, 0);
-  tree type = tsubst (t0, args, info.complain, info.in_decl);
+  tree type = tsubst_type_requirement_1 (t0, args, info, EXPR_LOCATION (t));
   if (type == error_mark_node)
     return error_mark_node;
   return boolean_true_node;
@@ -2013,7 +2076,7 @@ expression_convertible_p (tree expr, tree type, subst_info info)
 /* Substitute through the compound requirement.  */
 
 static tree
-tsubst_compound_requirement (tree t, tree args, subst_info info)
+tsubst_compound_requirement (tree t, tree args, sat_info info)
 {
   tree t0 = TREE_OPERAND (t, 0);
   tree t1 = TREE_OPERAND (t, 1);
@@ -2021,13 +2084,20 @@ tsubst_compound_requirement (tree t, tree args, subst_info info)
   if (expr == error_mark_node)
     return error_mark_node;
 
+  location_t loc = cp_expr_loc_or_input_loc (expr);
+
   /* Check the noexcept condition.  */
   bool noexcept_p = COMPOUND_REQ_NOEXCEPT_P (t);
   if (noexcept_p && !expr_noexcept_p (expr, tf_none))
-    return error_mark_node;
+    {
+      if (info.diagnose_unsatisfaction_p ())
+	inform (loc, "%qE is not %<noexcept%>", expr);
+      else
+	return error_mark_node;
+    }
 
   /* Substitute through the type expression, if any.  */
-  tree type = tsubst (t1, args, info.complain, info.in_decl);
+  tree type = tsubst_type_requirement_1 (t1, args, info, EXPR_LOCATION (t));
   if (type == error_mark_node)
     return error_mark_node;
 
@@ -2039,29 +2109,76 @@ tsubst_compound_requirement (tree t, tree args, subst_info info)
       if (tree placeholder = type_uses_auto (type))
 	{
 	  if (!type_deducible_p (expr, type, placeholder, args, quiet))
-	    return error_mark_node;
+	    {
+	      if (info.diagnose_unsatisfaction_p ())
+		{
+		  if (diagnosing_failed_constraint::replay_errors_p ())
+		    {
+		      inform (loc,
+			      "%qE does not satisfy return-type-requirement, "
+			      "because", t0);
+		      /* Further explain the reason for the error.  */
+		      type_deducible_p (expr, type, placeholder, args, info);
+		    }
+		  else
+		    inform (loc,
+			    "%qE does not satisfy return-type-requirement", t0);
+		}
+	      return error_mark_node;
+	    }
 	}
       else if (!expression_convertible_p (expr, type, quiet))
-	return error_mark_node;
+	{
+	  if (info.diagnose_unsatisfaction_p ())
+	    {
+	      if (diagnosing_failed_constraint::replay_errors_p ())
+		{
+		  inform (loc, "cannot convert %qE to %qT because", t0, type);
+		  /* Further explain the reason for the error.  */
+		  expression_convertible_p (expr, type, info);
+		}
+	      else
+		inform (loc, "cannot convert %qE to %qT", t0, type);
+	    }
+	  return error_mark_node;
+	}
     }
 
   return boolean_true_node;
 }
 
+/* Substitute through the nested requirement.  */
+
 static tree
-tsubst_nested_requirement (tree t, tree args, subst_info info)
+tsubst_nested_requirement (tree t, tree args, sat_info info)
 {
   sat_info quiet (tf_none, info.in_decl);
   tree result = constraint_satisfaction_value (t, args, quiet);
-  if (result != boolean_true_node)
-    return error_mark_node;
-  return boolean_true_node;
+  if (result == boolean_true_node)
+    return boolean_true_node;
+
+  if (result == boolean_false_node
+      && info.diagnose_unsatisfaction_p ())
+    {
+      tree expr = TREE_OPERAND (t, 0);
+      location_t loc = cp_expr_location (t);
+      if (diagnosing_failed_constraint::replay_errors_p ())
+	{
+	  /* Replay the substitution error.  */
+	  inform (loc, "nested requirement %qE is not satisfied, because", expr);
+	  constraint_satisfaction_value (t, args, info);
+	}
+      else
+	inform (loc, "nested requirement %qE is not satisfied", expr);
+    }
+
+  return error_mark_node;
 }
 
 /* Substitute ARGS into the requirement T.  */
 
 static tree
-tsubst_requirement (tree t, tree args, subst_info info)
+tsubst_requirement (tree t, tree args, sat_info info)
 {
   iloc_sentinel loc_s (cp_expr_location (t));
   switch (TREE_CODE (t))
@@ -2151,30 +2268,22 @@ tsubst_constraint_variables (tree t, tree args, subst_info info)
    in its requirements ... In such cases, the expression evaluates
    to false; it does not cause the program to be ill-formed.
 
-   However, there are cases where substitution must produce a
-   new requires-expression, that is not a template constraint.
-   For example:
+   When substituting through a REQUIRES_EXPR as part of template
+   instantiation, we call this routine with info.quiet() true.
 
-        template<typename T>
-        class X {
-          template<typename U>
-          static constexpr bool var = requires (U u) { T::fn(u); };
-        };
+   When evaluating a REQUIRES_EXPR that appears outside a template in
+   cp_parser_requires_expression, we call this routine with
+   info.noisy() true.
 
-   In the instantiation of X<Y> (assuming Y defines fn), then the
-   instantiated requires-expression would include Y::fn(u). If any
-   substitution in the requires-expression fails, we can immediately
-   fold the expression to false, as would be the case e.g., when
-   instantiation X<int>.  */
+   Finally, when diagnosing unsatisfaction from diagnose_atomic_constraint
+   and when diagnosing a false REQUIRES_EXPR via diagnose_constraints,
+   we call this routine with info.diagnose_unsatisfaction_p() true.  */
 
-tree
-tsubst_requires_expr (tree t, tree args,
-		      tsubst_flags_t complain, tree in_decl)
+static tree
+tsubst_requires_expr (tree t, tree args, sat_info info)
 {
   local_specialization_stack stack (lss_copy);
 
-  subst_info info (complain, in_decl);
-
   /* A requires-expression is an unevaluated context.  */
   cp_unevaluated u;
 
@@ -2186,7 +2295,7 @@ tsubst_requires_expr (tree t, tree args,
 	 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);
+      REQUIRES_EXPR_EXTRA_ARGS (t) = build_extra_args (t, args, info.complain);
       return t;
     }
 
@@ -2207,6 +2316,16 @@ tsubst_requires_expr (tree t, tree args,
   return boolean_true_node;
 }
 
+/* Public wrapper for the above.  */
+
+tree
+tsubst_requires_expr (tree t, tree args,
+		      tsubst_flags_t complain, tree in_decl)
+{
+  sat_info info (complain, in_decl);
+  return tsubst_requires_expr (t, args, info);
+}
+
 /* Substitute ARGS into the constraint information CI, producing a new
    constraint record.  */
 
@@ -2790,7 +2909,7 @@ get_mapped_args (tree map)
   return args;
 }
 
-static void diagnose_atomic_constraint (tree, tree, tree, subst_info);
+static void diagnose_atomic_constraint (tree, tree, tree, sat_info);
 
 /* Compute the satisfaction of an atomic constraint.  */
 
@@ -3440,11 +3559,10 @@ get_constraint_error_location (tree t)
 
 /* Emit a diagnostic for a failed trait.  */
 
-void
-diagnose_trait_expr (tree expr, tree map)
+static void
+diagnose_trait_expr (tree expr, tree args)
 {
   location_t loc = cp_expr_location (expr);
-  tree args = get_mapped_args (map);
 
   /* Build a "fake" version of the instantiated trait, so we can
      get the instantiated types from result.  */
@@ -3524,192 +3642,11 @@ diagnose_trait_expr (tree expr, tree map)
     }
 }
 
-static tree
-diagnose_valid_expression (tree expr, tree args, tree in_decl)
-{
-  tree result = tsubst_expr (expr, args, tf_none, in_decl, false);
-  if (result != error_mark_node
-      && convert_to_void (result, ICV_STATEMENT, tf_none) != error_mark_node)
-    return result;
-
-  location_t loc = cp_expr_loc_or_input_loc (expr);
-  if (diagnosing_failed_constraint::replay_errors_p ())
-    {
-      /* Replay the substitution error.  */
-      inform (loc, "the required expression %qE is invalid, because", expr);
-      if (result == error_mark_node)
-	tsubst_expr (expr, args, tf_error, in_decl, false);
-      else
-	convert_to_void (result, ICV_STATEMENT, tf_error);
-    }
-  else
-    inform (loc, "the required expression %qE is invalid", expr);
-
-  return error_mark_node;
-}
-
-static tree
-diagnose_valid_type (tree type, tree args, tree in_decl)
-{
-  tree result = tsubst (type, args, tf_none, in_decl);
-  if (result != error_mark_node)
-    return result;
-
-  location_t loc = cp_expr_loc_or_input_loc (type);
-  if (diagnosing_failed_constraint::replay_errors_p ())
-    {
-      /* Replay the substitution error.  */
-      inform (loc, "the required type %qT is invalid, because", type);
-      tsubst (type, args, tf_error, in_decl);
-    }
-  else
-    inform (loc, "the required type %qT is invalid", type);
-
-  return error_mark_node;
-}
-
-static void
-diagnose_simple_requirement (tree req, tree args, tree in_decl)
-{
-  diagnose_valid_expression (TREE_OPERAND (req, 0), args, in_decl);
-}
-
-static void
-diagnose_compound_requirement (tree req, tree args, tree in_decl)
-{
-  tree expr = TREE_OPERAND (req, 0);
-  expr = diagnose_valid_expression (expr, args, in_decl);
-  if (expr == error_mark_node)
-    return;
-
-  location_t loc = cp_expr_loc_or_input_loc (expr);
-
-  /* Check the noexcept condition.  */
-  if (COMPOUND_REQ_NOEXCEPT_P (req) && !expr_noexcept_p (expr, tf_none))
-    inform (loc, "%qE is not %<noexcept%>", expr);
-
-  tree type = TREE_OPERAND (req, 1);
-  type = diagnose_valid_type (type, args, in_decl);
-  if (type == error_mark_node)
-    return;
-
-  if (type)
-    {
-      subst_info quiet (tf_none, in_decl);
-      subst_info noisy (tf_error, in_decl);
-
-      /* Check the expression against the result type.  */
-      if (tree placeholder = type_uses_auto (type))
-	{
-	  if (!type_deducible_p (expr, type, placeholder, args, quiet))
-	    {
-	      tree orig_expr = TREE_OPERAND (req, 0);
-	      if (diagnosing_failed_constraint::replay_errors_p ())
-		{
-		  inform (loc,
-			  "%qE does not satisfy return-type-requirement, "
-			  "because", orig_expr);
-		  /* Further explain the reason for the error.  */
-		  type_deducible_p (expr, type, placeholder, args, noisy);
-		}
-	      else
-		inform (loc, "%qE does not satisfy return-type-requirement",
-			orig_expr);
-	    }
-	}
-      else if (!expression_convertible_p (expr, type, quiet))
-	{
-	  tree orig_expr = TREE_OPERAND (req, 0);
-	  if (diagnosing_failed_constraint::replay_errors_p ())
-	    {
-	      inform (loc, "cannot convert %qE to %qT because", orig_expr, type);
-	      /* Further explain the reason for the error.  */
-	      expression_convertible_p (expr, type, noisy);
-	    }
-	  else
-	    inform (loc, "cannot convert %qE to %qT", orig_expr, type);
-	}
-    }
-}
-
-static void
-diagnose_type_requirement (tree req, tree args, tree in_decl)
-{
-  tree type = TREE_OPERAND (req, 0);
-  diagnose_valid_type (type, args, in_decl);
-}
-
-static void
-diagnose_nested_requirement (tree req, tree args)
-{
-  /* Quietly check for satisfaction first.  */
-  sat_info quiet (tf_none, NULL_TREE);
-  tree result = satisfy_nondeclaration_constraints (req, args, quiet);
-  if (result == boolean_true_node)
-    return;
-
-  tree expr = TREE_OPERAND (req, 0);
-  location_t loc = cp_expr_location (expr);
-  if (diagnosing_failed_constraint::replay_errors_p ())
-    {
-      /* Replay the substitution error with re-normalized requirements.  */
-      inform (loc, "nested requirement %qE is not satisfied, because", expr);
-
-      sat_info noisy (tf_warning_or_error, NULL_TREE, /*diag_unsat=*/true);
-      satisfy_nondeclaration_constraints (req, args, noisy);
-    }
-  else
-    inform (loc, "nested requirement %qE is not satisfied", expr);
-
-}
-
-static void
-diagnose_requirement (tree req, tree args, tree in_decl)
-{
-  iloc_sentinel loc_s (cp_expr_location (req));
-  switch (TREE_CODE (req))
-    {
-    case SIMPLE_REQ:
-      return diagnose_simple_requirement (req, args, in_decl);
-    case COMPOUND_REQ:
-      return diagnose_compound_requirement (req, args, in_decl);
-    case TYPE_REQ:
-      return diagnose_type_requirement (req, args, in_decl);
-    case NESTED_REQ:
-      return diagnose_nested_requirement (req, args);
-    default:
-       gcc_unreachable ();
-    }
-}
-
-static void
-diagnose_requires_expr (tree expr, tree map, tree in_decl)
-{
-  local_specialization_stack stack (lss_copy);
-  tree parms = TREE_OPERAND (expr, 0);
-  tree body = TREE_OPERAND (expr, 1);
-  tree args = get_mapped_args (map);
-
-  cp_unevaluated u;
-  subst_info info (tf_warning_or_error, NULL_TREE);
-  tree vars = tsubst_constraint_variables (parms, args, info);
-  if (vars == error_mark_node)
-    return;
-
-  tree p = body;
-  while (p)
-    {
-      tree req = TREE_VALUE (p);
-      diagnose_requirement (req, args, in_decl);
-      p = TREE_CHAIN (p);
-    }
-}
-
 /* Diagnose a substitution failure in the atomic constraint T when applied
    with the instantiated parameter mapping MAP.  */
 
 static void
-diagnose_atomic_constraint (tree t, tree map, tree result, subst_info info)
+diagnose_atomic_constraint (tree t, tree map, tree result, sat_info info)
 {
   /* If the constraint is already ill-formed, we've previously diagnosed
      the reason. We should still say why the constraints aren't satisfied.  */
@@ -3730,13 +3667,16 @@ diagnose_atomic_constraint (tree t, tree map, tree result, subst_info info)
   /* Generate better diagnostics for certain kinds of expressions.  */
   tree expr = ATOMIC_CONSTR_EXPR (t);
   STRIP_ANY_LOCATION_WRAPPER (expr);
+  tree args = get_mapped_args (map);
   switch (TREE_CODE (expr))
     {
     case TRAIT_EXPR:
-      diagnose_trait_expr (expr, map);
+      diagnose_trait_expr (expr, args);
       break;
     case REQUIRES_EXPR:
-      diagnose_requires_expr (expr, map, info.in_decl);
+      gcc_checking_assert (info.diagnose_unsatisfaction_p ());
+      info.in_decl = NULL_TREE;
+      tsubst_requires_expr (expr, args, info);
       break;
     default:
       if (!same_type_p (TREE_TYPE (result), boolean_type_node))
@@ -3807,7 +3747,7 @@ diagnose_constraints (location_t loc, tree t, tree args)
     {
       gcc_assert (!args);
       ++current_constraint_diagnosis_depth;
-      diagnose_requires_expr (t, /*map=*/NULL_TREE, /*in_decl=*/NULL_TREE);
+      tsubst_requires_expr (t, /*args=*/NULL_TREE, noisy);
       --current_constraint_diagnosis_depth;
     }
   else
diff --git a/gcc/testsuite/g++.dg/concepts/diagnostic1.C b/gcc/testsuite/g++.dg/concepts/diagnostic1.C
index 29c78c4c730..23bd592411e 100644
--- a/gcc/testsuite/g++.dg/concepts/diagnostic1.C
+++ b/gcc/testsuite/g++.dg/concepts/diagnostic1.C
@@ -8,7 +8,7 @@ concept bool SameAs = __is_same_as(T, U);
 template <class T>
 concept bool R1 = requires (T& t) { // { dg-message "in requirements" }
   { t.begin() } -> T;		// { dg-error "no match" }
-  { t.end() } -> SameAs<T*>;	// { dg-message "does not satisfy" }
+  { t.end() } -> SameAs<T*>;
 };
 
 template <class T>
-- 
2.31.0.rc0


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 3/4] c++: Delay normalizing nested requirements until satisfaction
  2021-02-28 17:40         ` Patrick Palka
@ 2021-03-01 22:23           ` Jason Merrill
  2021-03-01 23:09             ` Patrick Palka
  0 siblings, 1 reply; 29+ messages in thread
From: Jason Merrill @ 2021-03-01 22:23 UTC (permalink / raw)
  To: Patrick Palka; +Cc: gcc-patches

On 2/28/21 12:40 PM, Patrick Palka wrote:
> On Fri, 12 Feb 2021, Jason Merrill wrote:
> 
>> On 2/10/21 9:41 AM, Patrick Palka wrote:
>>> On Tue, 9 Feb 2021, Jason Merrill wrote:
>>>
>>>> On 2/8/21 2:03 PM, Patrick Palka wrote:
>>>>> This sets up the functionality for controlling the initial set of
>>>>> template parameters to pass to normalization when dealing with a
>>>>> constraint-expression that is not associated with some constrained
>>>>> declaration, for instance when normalizing a nested requirement of a
>>>>> requires expression, or the constraints on a placeholder type.
>>>>>
>>>>> The main new ingredient here is the data member norm_info::initial_parms
>>>>> which can be set by callers of the normalization routines to communicate
>>>>> the in-scope template parameters for the supplied constraint-expression,
>>>>> rather than always falling back to using current_template_parms.
>>>>>
>>>>> This patch then uses this functionality in our handling of nested
>>>>> requirements so that we can delay normalizing them until needed for
>>>>> satisfaction.  We currently immediately normalize nested requirements at
>>>>> parse time, where we have the necessary template context, and cache the
>>>>> normal form in their TREE_TYPE node.  With this patch, we now delay
>>>>> normalization until needed (as with other constraint expressions), and
>>>>> instead store the current value of current_template_parms in their
>>>>> TREE_TYPE node (which we use to restore the template context at
>>>>> normalization time).
>>>>>
>>>>> In the subsequent patch, this functionality will also be used to
>>>>> normalize placeholder type constraints during auto deduction.
>>>>>
>>>>> gcc/cp/ChangeLog:
>>>>>
>>>>> 	* constraint.cc (build_parameter_mapping): Rely on the caller to
>>>>> 	determine the in-scope template parameters.
>>>>> 	(norm_info::norm_info): Delegate the one-parameter constructor
>>>>> 	to the two-parameter constructor.  In the two-parameter
>>>>> 	constructor, fold in the definition of make_context, set
>>>>> 	initial_parms appropriately, and don't set the now-removed
>>>>> 	orig_decl member.
>>>>> 	(norm_info::make_context): Remove, now that its only use is
>>>>> 	inlined into the caller.
>>>>> 	(norm_info::update_context): Adjust call to
>>>>> 	build_parameter_mapping to pass in the relevant set of in-scope
>>>>> 	template parameters.
>>>>> 	(norm_info::ctx_parms): Define this member function.
>>>>> 	(norm_info::context): Initialize to NULL_TREE.
>>>>> 	(norm_info::orig_decl): Remove this data member.
>>>>> 	(norm_info::initial_parms): Define this data member.
>>>>> 	(normalize_atom): Adjust call to build_parameter_mapping to pass
>>>>> 	in the relevant set of in-scope template parameters.  Use
>>>>> 	info.initial_parms instead of info.orig_decl.
>>>>> 	(normalize_constraint_expression): Define an overload that takes
>>>>> 	a norm_info object.  Cache the result of normalization.  Define
>>>>> 	the other overload in terms of this one, and handle a NESTED_REQ
>>>>> 	argument by setting info.initial_parms appropriately.
>>>>> 	(tsubst_nested_requirement): Go through
>>>>> 	satisfy_constraint_expression so that we normalize on demand.
>>>>> 	(finish_nested_requirement): Set the TREE_TYPE of the NESTED_REQ
>>>>> 	to current_template_parms.
>>>>> 	(diagnose_nested_requirements): Go through
>>>>> 	satisfy_constraint_expression, as with tsubst_nested_requirement.
>>>>> ---
>>>>>     gcc/cp/constraint.cc | 140
>>>>> +++++++++++++++++++++++--------------------
>>>>>     gcc/cp/cp-tree.h     |   4 +-
>>>>>     2 files changed, 78 insertions(+), 66 deletions(-)
>>>>>
>>>>> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
>>>>> index 39c97986082..56134f8b2bf 100644
>>>>> --- a/gcc/cp/constraint.cc
>>>>> +++ b/gcc/cp/constraint.cc
>>>>> @@ -133,7 +133,7 @@ struct sat_info : subst_info
>>>>>       bool diagnose_unsatisfaction;
>>>>>     };
>>>>>     -static tree satisfy_constraint (tree, tree, sat_info);
>>>>> +static tree satisfy_constraint_expression (tree, tree, sat_info);
>>>>>       /* True if T is known to be some type other than bool. Note that
>>>>> this
>>>>>        is false for dependent types and errors.  */
>>>>> @@ -594,26 +594,12 @@ map_arguments (tree parms, tree args)
>>>>>       return parms;
>>>>>     }
>>>>>     -/* Build the parameter mapping for EXPR using ARGS.  */
>>>>> +/* Build the parameter mapping for EXPR using ARGS, where CTX_PARMS
>>>>> +   are the template parameters in scope for EXPR.  */
>>>>>       static tree
>>>>> -build_parameter_mapping (tree expr, tree args, tree decl)
>>>>> +build_parameter_mapping (tree expr, tree args, tree ctx_parms)
>>>>>     {
>>>>> -  tree ctx_parms = NULL_TREE;
>>>>> -  if (decl)
>>>>> -    {
>>>>> -      gcc_assert (TREE_CODE (decl) == TEMPLATE_DECL);
>>>>> -      ctx_parms = DECL_TEMPLATE_PARMS (decl);
>>>>> -    }
>>>>> -  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.  */
>>>>> -      ctx_parms = current_template_parms;
>>>>> -    }
>>>>> -
>>>>>       tree parms = find_template_parameters (expr, ctx_parms);
>>>>>       tree map = map_arguments (parms, args);
>>>>>       return map;
>>>>> @@ -645,53 +631,63 @@ parameter_mapping_equivalent_p (tree t1, tree t2)
>>>>>       struct norm_info : subst_info
>>>>>     {
>>>>> -  explicit norm_info (tsubst_flags_t complain)
>>>>> -    : subst_info (tf_warning_or_error | complain, NULL_TREE),
>>>>> -      context()
>>>>> +  explicit norm_info (tsubst_flags_t cmp)
>>>>> +    : norm_info (NULL_TREE, cmp)
>>>>>       {}
>>>>>         /* Construct a top-level context for DECL.  */
>>>>>         norm_info (tree in_decl, tsubst_flags_t complain)
>>>>> -    : subst_info (tf_warning_or_error | complain, in_decl),
>>>>> -      context (make_context (in_decl)),
>>>>> -      orig_decl (in_decl)
>>>>> -  {}
>>>>> -
>>>>> -  bool generate_diagnostics() const
>>>>> +    : subst_info (tf_warning_or_error | complain, in_decl)
>>>>>       {
>>>>> -    return complain & tf_norm;
>>>>> +    if (in_decl)
>>>>> +      {
>>>>> +	initial_parms = DECL_TEMPLATE_PARMS (in_decl);
>>>>> +	if (generate_diagnostics ())
>>>>> +	  context = build_tree_list (NULL_TREE, in_decl);
>>>>> +      }
>>>>> +    else
>>>>> +      initial_parms = current_template_parms;
>>>>>       }
>>>>>     -  tree make_context(tree in_decl)
>>>>> +  bool generate_diagnostics() const
>>>>>       {
>>>>> -    if (generate_diagnostics ())
>>>>> -      return build_tree_list (NULL_TREE, in_decl);
>>>>> -    return NULL_TREE;
>>>>> +    return complain & tf_norm;
>>>>>       }
>>>>>         void update_context(tree expr, tree args)
>>>>>       {
>>>>>         if (generate_diagnostics ())
>>>>>           {
>>>>> -	tree map = build_parameter_mapping (expr, args, in_decl);
>>>>> +	tree map = build_parameter_mapping (expr, args, ctx_parms ());
>>>>>     	context = tree_cons (map, expr, context);
>>>>>           }
>>>>>         in_decl = get_concept_check_template (expr);
>>>>>       }
>>>>>     +  /* Returns the template parameters that are in scope for the
>>>>> current
>>>>> +     normalization context.  */
>>>>> +
>>>>> +  tree ctx_parms()
>>>>> +  {
>>>>> +    if (in_decl)
>>>>> +      return DECL_TEMPLATE_PARMS (in_decl);
>>>>> +    else
>>>>> +      return initial_parms;
>>>>> +  }
>>>>> +
>>>>>       /* Provides information about the source of a constraint. This is a
>>>>>          TREE_LIST whose VALUE is either a concept check or a constrained
>>>>>          declaration. The PURPOSE, for concept checks is a parameter
>>>>> mapping
>>>>>          for that check.  */
>>>>>     -  tree context;
>>>>> +  tree context = NULL_TREE;
>>>>>         /* The declaration whose constraints we're normalizing.  The
>>>>> targets
>>>>>          of the parameter mapping of each atom will be in terms of the
>>>>>          template parameters of ORIG_DECL.  */
>>>>>     -  tree orig_decl = NULL_TREE;
>>>>> +  tree initial_parms = NULL_TREE;
>>>>>     };
>>>>>       static tree normalize_expression (tree, tree, norm_info);
>>>>> @@ -773,7 +769,7 @@ normalize_atom (tree t, tree args, norm_info info)
>>>>>         return normalize_concept_check (t, args, info);
>>>>>         /* Build the parameter mapping for the atom.  */
>>>>> -  tree map = build_parameter_mapping (t, args, info.in_decl);
>>>>> +  tree map = build_parameter_mapping (t, args, info.ctx_parms ());
>>>>>         /* Build a new info object for the atom.  */
>>>>>       tree ci = build_tree_list (t, info.context);
>>>>> @@ -803,10 +799,8 @@ normalize_atom (tree t, tree args, norm_info info)
>>>>>     	      tree target = TREE_PURPOSE (node);
>>>>>     	      TREE_VEC_ELT (targets, i++) = target;
>>>>>     	    }
>>>>> -	  tree ctx_parms = (info.orig_decl
>>>>> -			    ? DECL_TEMPLATE_PARMS (info.orig_decl)
>>>>> -			    : current_template_parms);
>>>>> -	  tree target_parms = find_template_parameters (targets, ctx_parms);
>>>>> +	  tree target_parms = find_template_parameters (targets,
>>>>> +							info.initial_parms);
>>>>>     	  TREE_TYPE (map) = target_parms;
>>>>>     	}
>>>>>     @@ -983,17 +977,43 @@ normalize_nontemplate_requirements (tree decl,
>>>>> bool
>>>>> diag = false)
>>>>>     /* Normalize an EXPR as a constraint.  */
>>>>>       static tree
>>>>> -normalize_constraint_expression (tree expr, bool diag)
>>>>> +normalize_constraint_expression (tree expr, norm_info info)
>>>>>     {
>>>>>       if (!expr || expr == error_mark_node)
>>>>>         return expr;
>>>>> +
>>>>> +  if (!info.generate_diagnostics ())
>>>>> +    if (tree *p = hash_map_safe_get (normalized_map, expr))
>>>>> +      return *p;
>>>>
>>>> It seems like we only want this for NESTED_REQ.
>>>
>>> I figured it'd also be beneficial to cache the normal form of a
>>> placeholder type constraint, which will also goes through this overload.
>>
>> True.  And if we change REQUIRES_EXPR handling to not go through here, it
>> should be just the two.  OK, let's leave this alone.
>>
>>>>>       ++processing_template_decl;
>>>>> -  norm_info info (diag ? tf_norm : tf_none);
>>>>>       tree norm = get_normalized_constraints (expr, info);
>>>>>       --processing_template_decl;
>>>>> +
>>>>> +  if (!info.generate_diagnostics ())
>>>>> +    hash_map_safe_put<hm_ggc> (normalized_map, expr, norm);
>>>>> +
>>>>>       return norm;
>>>>>     }
>>>>>     +/* High-level wrapper for the above.  */
>>>>> +
>>>>> +static tree
>>>>> +normalize_constraint_expression (tree expr, bool diag)
>>>>> +{
>>>>> +  norm_info info (diag ? tf_norm : tf_none);
>>>>
>>>> I wonder if we want to add a norm_info constructor taking a sat_info so we
>>>> don't need to mediate passing from one into the other with bool "diag"
>>>> parameters in various functions.  That doesn't need to happen in this
>>>> patch.
>>>
>>> Sounds good.  I think such a constructor would let us eliminate the
>>> bool-taking overload of normalize_constraint_expression altogether, if
>>> we move the special handling of NESTED_REQ to the norm_info-taking
>>> overload or to satisfy_constraint_expression.
> 
> Here's v2 of this patch, which refrains from adding a second overload of
> normalize_constraint_expression.  Handling of NESTED_REQs now happens in
> satisfy_constraint_expression.
> 
> The rest of the cleanups mentioned here will be performed in another
> patch.
> 
> (For now, I didn't add a norm_info(sat_info) constructor since it wasn't
> clear to me if this constructor should propagate in_decl or not.  For > sat_info, in_decl is used purely for diagnostics, whereas for norm_info,
> in_decl keeps track of the current normalization context, so carrying
> over the value of in_decl might not always make sense.

> Perhaps it might be better to add a norm_info(bool diag) constructor
> instead?

I was thinking the norm_info(sat_info) constructor would do the 
equivalent of

+      norm_info ninfo (info.noisy () ? tf_norm : tf_none);

> While we're at it, I think we could remove the tf_norm flag.)

Sure.

> -- >8 --
> 
> Subject: [PATCH 3/6] c++: Delay normalizing nested requirements until
>   satisfaction
> 
> This sets up the functionality for controlling the initial set of
> template parameters to pass to normalization when dealing with a
> constraint-expression that is not associated with some constrained
> declaration, for instance when normalizing a nested requirement of a
> requires expression, or the constraints on a placeholder type.
> 
> The main new ingredient here is the data member norm_info::initial_parms
> which can be set by callers of the normalization routines to communicate
> the in-scope template parameters for the supplied constraint-expression,
> rather than always falling back to using current_template_parms.
> 
> This patch then uses this functionality in our handling of nested
> requirements so that we can delay normalizing them until needed for
> satisfaction.  We currently immediately normalize nested requirements at
> parse time, where we have the necessary template context, and cache the
> normal form in their TREE_TYPE node.  With this patch, we now delay
> normalization until needed (as with other constraint expressions), and
> instead store the current value of current_template_parms in their
> TREE_TYPE node (which we use to restore the template context at
> normalization time).
> 
> In the subsequent patch, this functionality will also be used to
> normalize placeholder type constraints during auto deduction.
> 
> gcc/cp/ChangeLog:
> 
> 	* constraint.cc (build_parameter_mapping): Rely on the caller to
> 	determine the in-scope template parameters.
> 	(norm_info::norm_info): Delegate the tsubst_flags_t constructor
> 	to the two-parameter constructor.  In the two-parameter
> 	constructor, fold in the definition of make_context, set
> 	initial_parms appropriately, and don't set the now-removed
> 	orig_decl member.
> 	(norm_info::make_context): Remove, now that its only use is
> 	inlined into the caller.
> 	(norm_info::update_context): Adjust call to
> 	build_parameter_mapping to pass in the relevant set of in-scope
> 	template parameters.
> 	(norm_info::ctx_parms): Define this member function.
> 	(norm_info::context): Initialize to NULL_TREE.
> 	(norm_info::orig_decl): Remove this data member.
> 	(norm_info::initial_parms): Define this data member.
> 	(normalize_atom): Adjust call to build_parameter_mapping to pass
> 	in the relevant set of in-scope template parameters.  Use
> 	info.initial_parms instead of info.orig_decl.
> 	(normalize_constraint_expression): Take a norm_info object
> 	instead of a bool.  Cache the result of normalization.
> 	(tsubst_nested_requirement): Call satisfy_constraint_expression
> 	instead of satisfy_constraint, so that we normalize on demand.
> 	(satisfy_constraint_expression): Handle a NESTED_REQ argument.
> 	Adjust call to normalize_constraint_expression.
> 	(finish_nested_requirement): Set the TREE_TYPE of the NESTED_REQ
> 	to current_template_parms.
> 	(diagnose_nested_requirements): Go through
> 	satisfy_constraint_expression, as with tsubst_nested_requirement.
> ---
>   gcc/cp/constraint.cc | 138 ++++++++++++++++++++++---------------------
>   1 file changed, 72 insertions(+), 66 deletions(-)
> 
> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> index 39c97986082..fcb249a642f 100644
> --- a/gcc/cp/constraint.cc
> +++ b/gcc/cp/constraint.cc
> @@ -133,7 +133,7 @@ struct sat_info : subst_info
>     bool diagnose_unsatisfaction;
>   };
>   
> -static tree satisfy_constraint (tree, tree, sat_info);
> +static tree satisfy_constraint_expression (tree, tree, sat_info);
>   
>   /* True if T is known to be some type other than bool. Note that this
>      is false for dependent types and errors.  */
> @@ -594,26 +594,12 @@ map_arguments (tree parms, tree args)
>     return parms;
>   }
>   
> -/* Build the parameter mapping for EXPR using ARGS.  */
> +/* Build the parameter mapping for EXPR using ARGS, where CTX_PARMS
> +   are the template parameters in scope for EXPR.  */
>   
>   static tree
> -build_parameter_mapping (tree expr, tree args, tree decl)
> +build_parameter_mapping (tree expr, tree args, tree ctx_parms)
>   {
> -  tree ctx_parms = NULL_TREE;
> -  if (decl)
> -    {
> -      gcc_assert (TREE_CODE (decl) == TEMPLATE_DECL);
> -      ctx_parms = DECL_TEMPLATE_PARMS (decl);
> -    }
> -  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.  */
> -      ctx_parms = current_template_parms;
> -    }
> -
>     tree parms = find_template_parameters (expr, ctx_parms);
>     tree map = map_arguments (parms, args);
>     return map;
> @@ -645,53 +631,63 @@ parameter_mapping_equivalent_p (tree t1, tree t2)
>   
>   struct norm_info : subst_info
>   {
> -  explicit norm_info (tsubst_flags_t complain)
> -    : subst_info (tf_warning_or_error | complain, NULL_TREE),
> -      context()
> +  explicit norm_info (tsubst_flags_t cmp)
> +    : norm_info (NULL_TREE, cmp)
>     {}
>   
>     /* Construct a top-level context for DECL.  */
>   
>     norm_info (tree in_decl, tsubst_flags_t complain)
> -    : subst_info (tf_warning_or_error | complain, in_decl),
> -      context (make_context (in_decl)),
> -      orig_decl (in_decl)
> -  {}
> -
> -  bool generate_diagnostics() const
> +    : subst_info (tf_warning_or_error | complain, in_decl)
>     {
> -    return complain & tf_norm;
> +    if (in_decl)
> +      {
> +	initial_parms = DECL_TEMPLATE_PARMS (in_decl);
> +	if (generate_diagnostics ())
> +	  context = build_tree_list (NULL_TREE, in_decl);
> +      }
> +    else
> +      initial_parms = current_template_parms;
>     }
>   
> -  tree make_context(tree in_decl)
> +  bool generate_diagnostics() const
>     {
> -    if (generate_diagnostics ())
> -      return build_tree_list (NULL_TREE, in_decl);
> -    return NULL_TREE;
> +    return complain & tf_norm;
>     }
>   
>     void update_context(tree expr, tree args)
>     {
>       if (generate_diagnostics ())
>         {
> -	tree map = build_parameter_mapping (expr, args, in_decl);
> +	tree map = build_parameter_mapping (expr, args, ctx_parms ());
>   	context = tree_cons (map, expr, context);
>         }
>       in_decl = get_concept_check_template (expr);
>     }
>   
> +  /* Returns the template parameters that are in scope for the current
> +     normalization context.  */
> +
> +  tree ctx_parms()
> +  {
> +    if (in_decl)
> +      return DECL_TEMPLATE_PARMS (in_decl);
> +    else
> +      return initial_parms;
> +  }

Why prefer in_decl to initial_parms?  In fact, why look at in_decl here 
at all, when we already used it to set initial_parms in the constructor?

>     /* Provides information about the source of a constraint. This is a
>        TREE_LIST whose VALUE is either a concept check or a constrained
>        declaration. The PURPOSE, for concept checks is a parameter mapping
>        for that check.  */
>   
> -  tree context;
> +  tree context = NULL_TREE;
>   
>     /* The declaration whose constraints we're normalizing.  The targets
>        of the parameter mapping of each atom will be in terms of the
>        template parameters of ORIG_DECL.  */
>   
> -  tree orig_decl = NULL_TREE;
> +  tree initial_parms = NULL_TREE;
>   };
>   
>   static tree normalize_expression (tree, tree, norm_info);
> @@ -773,7 +769,7 @@ normalize_atom (tree t, tree args, norm_info info)
>       return normalize_concept_check (t, args, info);
>   
>     /* Build the parameter mapping for the atom.  */
> -  tree map = build_parameter_mapping (t, args, info.in_decl);
> +  tree map = build_parameter_mapping (t, args, info.ctx_parms ());
>   
>     /* Build a new info object for the atom.  */
>     tree ci = build_tree_list (t, info.context);
> @@ -803,10 +799,8 @@ normalize_atom (tree t, tree args, norm_info info)
>   	      tree target = TREE_PURPOSE (node);
>   	      TREE_VEC_ELT (targets, i++) = target;
>   	    }
> -	  tree ctx_parms = (info.orig_decl
> -			    ? DECL_TEMPLATE_PARMS (info.orig_decl)
> -			    : current_template_parms);
> -	  tree target_parms = find_template_parameters (targets, ctx_parms);
> +	  tree target_parms = find_template_parameters (targets,
> +							info.initial_parms);
>   	  TREE_TYPE (map) = target_parms;
>   	}
>   
> @@ -983,14 +977,22 @@ normalize_nontemplate_requirements (tree decl, bool diag = false)
>   /* Normalize an EXPR as a constraint.  */
>   
>   static tree
> -normalize_constraint_expression (tree expr, bool diag)
> +normalize_constraint_expression (tree expr, norm_info info)
>   {
>     if (!expr || expr == error_mark_node)
>       return expr;
> +
> +  if (!info.generate_diagnostics ())
> +    if (tree *p = hash_map_safe_get (normalized_map, expr))
> +      return *p;
> +
>     ++processing_template_decl;
> -  norm_info info (diag ? tf_norm : tf_none);
>     tree norm = get_normalized_constraints (expr, info);
>     --processing_template_decl;
> +
> +  if (!info.generate_diagnostics ())
> +    hash_map_safe_put<hm_ggc> (normalized_map, expr, norm);
> +
>     return norm;
>   }
>   
> @@ -2086,16 +2088,14 @@ tsubst_compound_requirement (tree t, tree args, subst_info info)
>   static tree
>   tsubst_nested_requirement (tree t, tree args, subst_info info)
>   {
> -  /* Perform satisfaction quietly with the regular normal form.  */
> +  /* Perform satisfaction quietly first.  */
>     sat_info quiet (tf_none, info.in_decl);
> -  tree norm = TREE_VALUE (TREE_TYPE (t));
> -  tree diag_norm = TREE_PURPOSE (TREE_TYPE (t));
> -  tree result = satisfy_constraint (norm, args, quiet);
> +  tree result = satisfy_constraint_expression (t, args, quiet);
>     if (result == error_mark_node)
>       {
> -      /* Replay the error using the diagnostic normal form.  */
> +      /* Replay the error.  */
>         sat_info noisy (tf_warning_or_error, info.in_decl);
> -      satisfy_constraint (diag_norm, args, noisy);
> +      satisfy_constraint_expression (t, args, noisy);
>       }
>     if (result != boolean_true_node)
>       return error_mark_node;
> @@ -3040,8 +3040,22 @@ satisfy_constraint_expression (tree t, tree args, sat_info info)
>         tree tmpl = get_concept_check_template (id);
>         norm = normalize_concept_definition (tmpl, info.noisy ());
>       }
> +  else if (TREE_CODE (t) == NESTED_REQ)
> +    {
> +      norm_info ninfo (info.noisy () ? tf_norm : tf_none);
> +      /* The TREE_TYPE contains the set of template parameters that were in
> +	 scope for this nested requirement; use them as the initial template
> +	 parameters for normalization.  */
> +      ninfo.initial_parms = TREE_TYPE (t);
> +      norm = normalize_constraint_expression (TREE_OPERAND (t, 0), ninfo);
> +    }
> +  else if (EXPR_P (t))
> +    {
> +      norm_info ninfo (info.noisy () ? tf_norm : tf_none);
> +      norm = normalize_constraint_expression (t, ninfo);
> +    }
>     else
> -    norm = normalize_constraint_expression (t, info.noisy ());
> +    gcc_unreachable ();
>   
>     /* Perform satisfaction.  */
>     return satisfy_constraint (norm, args, info);
> @@ -3301,15 +3315,9 @@ finish_compound_requirement (location_t loc, tree expr, tree type, bool noexcept
>   tree
>   finish_nested_requirement (location_t loc, tree expr)
>   {
> -  /* We need to normalize the constraints now, at parse time, while
> -     we have the necessary template context.  We normalize twice,
> -     once without diagnostic information and once with, which we'll
> -     later use for quiet and noisy satisfaction respectively.  */
> -  tree norm = normalize_constraint_expression (expr, /*diag=*/false);
> -  tree diag_norm = normalize_constraint_expression (expr, /*diag=*/true);
> -
> -  /* Build the constraint, saving its two normalizations as its type.  */
> -  tree r = build1 (NESTED_REQ, build_tree_list (diag_norm, norm), expr);
> +  /* Build the requirement, saving the set of in-scope template
> +     parameters as its type.  */
> +  tree r = build1 (NESTED_REQ, current_template_parms, expr);
>     SET_EXPR_LOCATION (r, loc);
>     return r;
>   }
> @@ -3710,12 +3718,9 @@ diagnose_type_requirement (tree req, tree args, tree in_decl)
>   static void
>   diagnose_nested_requirement (tree req, tree args)
>   {
> -  /* Quietly check for satisfaction first using the regular normal form.
> -     We can elaborate details later if needed.  */
> -  tree norm = TREE_VALUE (TREE_TYPE (req));
> -  tree diag_norm = TREE_PURPOSE (TREE_TYPE (req));
> -  sat_info info (tf_none, NULL_TREE);
> -  tree result = satisfy_constraint (norm, args, info);
> +  /* Quietly check for satisfaction first.  */
> +  sat_info quiet (tf_none, NULL_TREE);
> +  tree result = satisfy_constraint_expression (req, args, quiet);
>     if (result == boolean_true_node)
>       return;
>   
> @@ -3723,10 +3728,11 @@ diagnose_nested_requirement (tree req, tree args)
>     location_t loc = cp_expr_location (expr);
>     if (diagnosing_failed_constraint::replay_errors_p ())
>       {
> -      /* Replay the substitution error using the diagnostic normal form.  */
> +      /* Replay the substitution error with re-normalized requirements.  */
>         inform (loc, "nested requirement %qE is not satisfied, because", expr);
> +
>         sat_info noisy (tf_warning_or_error, NULL_TREE, /*diag_unsat=*/true);
> -      satisfy_constraint (diag_norm, args, noisy);
> +      satisfy_constraint_expression (req, args, noisy);
>       }
>     else
>       inform (loc, "nested requirement %qE is not satisfied", expr);
> 


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 4/4] c++: dependent constraint on placeholder 'auto' [PR96443]
  2021-02-28 17:55         ` Patrick Palka
@ 2021-03-01 22:31           ` Jason Merrill
  0 siblings, 0 replies; 29+ messages in thread
From: Jason Merrill @ 2021-03-01 22:31 UTC (permalink / raw)
  To: Patrick Palka; +Cc: gcc-patches

On 2/28/21 12:55 PM, Patrick Palka wrote:
> On Fri, 12 Feb 2021, Jason Merrill wrote:
> 
>> On 2/11/21 5:14 PM, Patrick Palka wrote:
>>> On Thu, 11 Feb 2021, Jason Merrill wrote:
>>>
>>>> On 2/8/21 2:03 PM, Patrick Palka wrote:
>>>>> This fixes the way we check satisfaction of constraints on placeholder
>>>>> types in various contexts, and in particular when the constraint is
>>>>> dependent.
>>>>>
>>>>> Firstly, when evaluating the return type requirement of a compound
>>>>> requirement, we currently substitute the outer template arguments into
>>>>> the constraint before checking satisfaction. But we should instead be
>>>>> passing in the complete set of template arguments to satisfaction and
>>>>> not do a prior separate substitution.  Our current approach leads to us
>>>>> incorrectly rejecting the testcase concepts-return-req2.C below.
>>>>>
>>>>> Secondly, when checking the constraints on a placeholder variable or
>>>>> return type, we don't substitute the template arguments of the enclosing
>>>>> context at all.  This leads to bogus errors during satisfaction when the
>>>>> constraint is dependent as in the testcase concepts-placeholder3.C
>>>>> below.
>>>>>
>>>>> In order to fix these two issues, we need to be able to properly
>>>>> normalize the constraints on a placeholder 'auto', which in turn
>>>>> requires us to know the template parameters that were in-scope where an
>>>>> 'auto' was introduced.  This information currently doesn't seem to be
>>>>> easily available when we need it, so this patch adds an auxiliary hash
>>>>> table that keeps track of the value of current_template_parms when each
>>>>> constrained 'auto' was formed.
>>>>>
>>>>> This patch also removes some seemingly wrong handling of placeholder
>>>>> type arguments from tsubst_parameter_mapping.  The code doesn't trigger
>>>>> with the example used in the comments, because type_uses_auto doesn't
>>>>> look inside non-deduced contexts such as the operand of decltype.  And
>>>>> the call to do_auto_deduction seems confused because if 'arg' is a type,
>>>>> then so is 'parm', and therefore 'init' too is a type, but
>>>>> do_auto_deduction expects it to be an expression.  Before this patch,
>>>>> this code was dead (as far as our testsuite can tell), but now it breaks
>>>>> other parts of this patch, so let's remove it.
>>>>>
>>>>> gcc/cp/ChangeLog:
>>>>>
>>>>> 	PR c++/96443
>>>>> 	* constraint.cc (type_deducible_p): Don't substitute into the
>>>>> 	constraints, and instead just pass 'args' to do_auto_deduction
>>>>> 	as the outer template arguments.
>>>>> 	(tsubst_parameter_mapping): Remove confused code for handling
>>>>> 	placeholder type arguments.
>>>>> 	(normalize_placeholder_type_constraint): Define.
>>>>> 	(satisfy_constraint_expression): Use it to handle placeholder
>>>>> 	'auto' types.
>>>>> 	* cp-tree.h (get_constrained_auto_context): Declare.
>>>>> 	* pt.c (constrained_auto_context_map): Define.
>>>>> 	(get_placeholder_type_constraint_context): Define.
>>>>> 	(set_placeholder_type_constraints): Define.
>>>>> 	(copy_placeholder_type_constraints): Define.
>>>>> 	(tsubst) <case TEMPLATE_TYPE_PARM>: Use
>>>>> 	copy_placeholder_type_constraints.
>>>>> 	(make_constrained_placeholder_type): Use
>>>>> 	set_placeholder_type_constraints.
>>>>> 	(do_auto_deduction): Clarify comments about the outer_targs
>>>>> 	parameter.  Rework satisfaction of a placeholder type constraint
>>>>> 	to pass in the complete set of template arguments directly to
>>>>> 	constraints_satisfied_p.
>>>>> 	(splice_late_return_type): Use copy_placeholder_type_constraints.
>>>>>
>>>>> gcc/testsuite/ChangeLog:
>>>>>
>>>>> 	PR c++/96443
>>>>> 	* g++.dg/cpp2a/concepts-placeholder3.C: New test.
>>>>> 	* g++.dg/cpp2a/concepts-return-req2.C: New test.
>>>>> 	* g++.dg/concepts-ts1.C: Add dg-bogus directive to the call to
>>>>> 	f15 that we expect to accept.
>>>>> ---
>>>>>     gcc/cp/constraint.cc                          | 106
>>>>> ++++++++----------
>>>>>     gcc/cp/cp-tree.h                              |   1 +
>>>>>     gcc/cp/pt.c                                   | 101 +++++++++++------
>>>>>     .../g++.dg/cpp2a/concepts-placeholder3.C      |  19 ++++
>>>>>     .../g++.dg/cpp2a/concepts-return-req2.C       |  13 +++
>>>>>     gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C     |   2 +-
>>>>>     6 files changed, 146 insertions(+), 96 deletions(-)
>>>>>     create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-placeholder3.C
>>>>>     create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-return-req2.C
>>>>>
>>>>> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
>>>>> index 56134f8b2bf..53588047d44 100644
>>>>> --- a/gcc/cp/constraint.cc
>>>>> +++ b/gcc/cp/constraint.cc
>>>>> @@ -2007,39 +2007,19 @@ type_deducible_p (tree expr, tree type, tree
>>>>> placeholder, tree args,
>>>>>          references are preserved in the result.  */
>>>>>       expr = force_paren_expr_uneval (expr);
>>>>>     -  /* Replace the constraints with the instantiated constraints. This
>>>>> -     substitutes args into any template parameters in the trailing
>>>>> -     result type.  */
>>>>> -  tree saved_constr = PLACEHOLDER_TYPE_CONSTRAINTS (placeholder);
>>>>> -  tree subst_constr
>>>>> -    = tsubst_constraint (saved_constr,
>>>>> -			 args,
>>>>> -			 info.complain | tf_partial,
>>>>> -			 info.in_decl);
>>>>> -
>>>>> -  if (subst_constr == error_mark_node)
>>>>> -    return false;
>>>>> -
>>>>> -  PLACEHOLDER_TYPE_CONSTRAINTS (placeholder) = subst_constr;
>>>>> -
>>>>> -  /* Temporarily unlink the canonical type.  */
>>>>> -  tree saved_type = TYPE_CANONICAL (placeholder);
>>>>> -  TYPE_CANONICAL (placeholder) = NULL_TREE;
>>>>> -
>>>>> -  tree deduced_type
>>>>> -    = do_auto_deduction (type,
>>>>> -			 expr,
>>>>> -			 placeholder,
>>>>> -			 info.complain,
>>>>> -			 adc_requirement);
>>>>> -
>>>>> -  PLACEHOLDER_TYPE_CONSTRAINTS (placeholder) = saved_constr;
>>>>> -  TYPE_CANONICAL (placeholder) = saved_type;
>>>>> +  /* When args is empty, we're evaluating a non-templated requires
>>>>> expression,
>>>>> +     but even those are parsed under processing_template_decl == 1, and
>>>>> so
>>>>> the
>>>>> +     placeholder 'auto' inside this return-type-requirement has level
>>>>> 2.
>>>>> In
>>>>> +     order to have all parms and arguments match up for satisfaction,
>>>>> we
>>>>> need
>>>>> +     to pass a single level as OUTER_TARGS in this case.  */
>>>>> +  if (!args)
>>>>> +    args = make_tree_vec (0);
>>>>>     -  if (deduced_type == error_mark_node)
>>>>> -    return false;
>>>>> +  tree deduced_type = do_auto_deduction (type, expr, placeholder,
>>>>> +					 info.complain, adc_requirement,
>>>>> +					 /*outer_targs=*/args);
>>>>>     -  return true;
>>>>> +  return deduced_type != error_mark_node;
>>>>>     }
>>>>>       /* True if EXPR can not be converted to TYPE.  */
>>>>> @@ -2304,35 +2284,10 @@ tsubst_parameter_mapping (tree map, tree args,
>>>>> subst_info info)
>>>>>             return error_mark_node;
>>>>>           tree parm = TREE_VALUE (p);
>>>>>           tree arg = TREE_PURPOSE (p);
>>>>> -      tree new_arg = NULL_TREE;
>>>>> -      if (TYPE_P (arg))
>>>>> -        {
>>>>> -          /* If a template parameter is declared with a placeholder, we
>>>>> can
>>>>> -             get those in the argument list if decltype is applied to
>>>>> the
>>>>> -             placeholder. For example:
>>>>> -
>>>>> -		template<auto T>
>>>>> -		  requires C<decltype(T)>
>>>>> -		void f() { }
>>>>> -
>>>>> -	     The normalized argument for C will be an auto type, so we'll
>>>>> -             need to deduce the actual argument from the corresponding
>>>>> -             initializer (whatever argument is provided for T), and use
>>>>> -             that result in the instantiated parameter mapping.  */
>>>>> -          if (tree auto_node = type_uses_auto (arg))
>>>>> -            {
>>>>> -              int level;
>>>>> -              int index;
>>>>> -	      template_parm_level_and_index (parm, &level, &index);
>>>>> -	      tree init = TMPL_ARG (args, level, index);
>>>>> -              new_arg = do_auto_deduction (arg, init, auto_node,
>>>>> -					   complain, adc_variable_type,
>>>>> -					   make_tree_vec (0));
>>>>> -            }
>>>>> -        }
>>>>> -      else if (ARGUMENT_PACK_P (arg))
>>>>> +      tree new_arg;
>>>>> +      if (ARGUMENT_PACK_P (arg))
>>>>>     	new_arg = tsubst_argument_pack (arg, args, complain, in_decl);
>>>>> -      if (!new_arg)
>>>>> +      else
>>>>>     	{
>>>>>     	  new_arg = tsubst_template_arg (arg, args, complain,
>>>>> in_decl);
>>>>>     	  if (TYPE_P (new_arg))
>>>>> @@ -3038,6 +2993,31 @@ satisfy_associated_constraints (tree t, tree
>>>>> args,
>>>>> sat_info info)
>>>>>       return satisfy_constraint (t, args, info);
>>>>>     }
>>>>>     +/* Normalize the constraints on the placeholder 'auto' type T.  */
>>>>> +
>>>>> +tree
>>>>> +normalize_placeholder_type_constraints (tree t, bool diag)
>>>>> +{
>>>>> +  gcc_assert (is_auto (t));
>>>>> +  tree constr = PLACEHOLDER_TYPE_CONSTRAINTS (t);
>>>>> +  if (!constr)
>>>>> +    return NULL_TREE;
>>>>> +
>>>>> +  tree initial_parms = get_constrained_auto_context (t);
>>>>> +  /* The 'auto' itself is used as the first argument in its own
>>>>> constraints,
>>>>> +     and its level is one greater than its template context, so in
>>>>> order to
>>>>> +     capture all used template parameters we need to add an extra level
>>>>> of
>>>>> +     template parameters to the context; a dummy level suffices.  */
>>>>> +  initial_parms
>>>>> +    = tree_cons (size_int (initial_parms
>>>>> +			   ? TMPL_PARMS_DEPTH (initial_parms) + 1 : 1),
>>>>> +		 make_tree_vec (0), initial_parms);
>>>>> +
>>>>> +  norm_info info (diag ? tf_norm : tf_none);
>>>>> +  info.initial_parms = initial_parms;
>>>>> +  return normalize_constraint_expression (constr, info);
>>>>> +}
>>>>> +
>>>>>     /* Evaluate EXPR as a constraint expression using ARGS, returning a
>>>>>        satisfaction value. */
>>>>>     @@ -3047,8 +3027,6 @@ satisfy_constraint_expression (tree t, tree
>>>>> args,
>>>>> sat_info info)
>>>>>       if (t == error_mark_node)
>>>>>         return error_mark_node;
>>>>>     -  gcc_assert (EXPR_P (t));
>>>>> -
>>>>>       /* Get the normalized constraints.  */
>>>>>       tree norm;
>>>>>       if (args == NULL_TREE && concept_check_p (t))
>>>>> @@ -3058,8 +3036,12 @@ satisfy_constraint_expression (tree t, tree args,
>>>>> sat_info info)
>>>>>           tree tmpl = get_concept_check_template (id);
>>>>>           norm = normalize_concept_definition (tmpl, info.noisy ());
>>>>>         }
>>>>> -  else
>>>>> +  else if (EXPR_P (t))
>>>>>         norm = normalize_constraint_expression (t, info.noisy ());
>>>>> +  else if (is_auto (t))
>>>>> +    norm = normalize_placeholder_type_constraints (t, info.noisy ());
>>>>> +  else
>>>>> +    gcc_unreachable ();
>>>>>         /* Perform satisfaction.  */
>>>>>       return satisfy_constraint (norm, args, info);
>>>>> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
>>>>> index 26fbf1eb663..ca5550ccb6d 100644
>>>>> --- a/gcc/cp/cp-tree.h
>>>>> +++ b/gcc/cp/cp-tree.h
>>>>> @@ -7090,6 +7090,7 @@ extern tree make_auto
>>>>> (void);
>>>>>     extern tree make_decltype_auto			(void);
>>>>>     extern tree make_constrained_auto		(tree, tree);
>>>>>     extern tree make_constrained_decltype_auto	(tree, tree);
>>>>> +extern tree get_constrained_auto_context	(tree);
>>>>>     extern tree make_template_placeholder		(tree);
>>>>>     extern bool template_placeholder_p		(tree);
>>>>>     extern bool ctad_template_p			(tree);
>>>>> diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
>>>>> index bceb942e79a..bcf9c9a3d9a 100644
>>>>> --- a/gcc/cp/pt.c
>>>>> +++ b/gcc/cp/pt.c
>>>>> @@ -15364,6 +15364,46 @@ tsubst_tree_list (tree t, tree args,
>>>>> tsubst_flags_t
>>>>> complain, tree in_decl)
>>>>>       return chain;
>>>>>     }
>>>>>     +/* A hash table mapping a constrained 'auto' to the set of in-scope
>>>>> +   template parameters from where the 'auto' was introduced.  */
>>>>> +
>>>>> +static GTY((cache)) decl_tree_cache_map *constrained_auto_context_map;
>>>>
>>>> How about turning PLACEHOLDER_TYPE_CONSTRAINTS into a TREE_LIST instead of
>>>> introducing a new hash table?
>>>
>>> Nice, this cleaner approach works well, and was quite straightforward to
>>> implement.  How does this look?  Tested on x86_64-pc-linux-gnu,
>>> range-v3  and cmcstl2.
>>
>> Looks good.
> 
> Here's v3 of this patch, which correctly addresses the FIXME in
> splice_late_return_type added by r11-7409, and also adds a testcase
> abbrev9.C which verifies this.
> 
>  From the r11-7409 thread:
>>>> +	/* FIXME: We should also rebuild the constraint to refer to the new
>>>> +	   auto.  */
>>>> +	PLACEHOLDER_TYPE_CONSTRAINTS (new_auto)
>>>> +	  = PLACEHOLDER_TYPE_CONSTRAINTS (auto_node);
>>>> +	TYPE_CANONICAL (new_auto) = canonical_type_parameter (new_auto);
> 
>> For the FIXME it would be nice to adjust
>> make_constrained_auto/_placeholder_type to handle this for you.
> 
> Hmm, I tried, but I couldn't see a good way to adjust these functions to
> handle rebuilding the constraint for us.

Ah, well.  The patch is OK.

> -- >8 --
> 
> Subject: [PATCH 4/6] c++: Fix satisfaction of placeholder type constraints
>   [PR96443]
> 
> This fixes the way we check satisfaction of constraints on placeholder
> types in various deduction contexts, and in particular when the
> constraint is dependent.
> 
> Firstly, when evaluating the return type requirement of a compound
> requirement, we currently substitute the outer template arguments into
> the constraint before checking satisfaction. But we should instead be
> passing in the complete set of template arguments to satisfaction and
> not do a prior separate substitution.  Our current approach leads to us
> incorrectly rejecting the testcase concepts-return-req2.C below.
> 
> Secondly, when checking the constraints on a placeholder variable or
> return type, we don't consider the template arguments of the enclosing
> context at all.  This leads to bogus errors during satisfaction when the
> constraint is dependent as in the testcase concepts-placeholder3.C
> below.
> 
> In order to fix these two issues, we need to be able to normalize the
> constraints on a placeholder 'auto', which in turn requires us to know
> the template parameters that were in scope where the 'auto' was
> introduced.  This information currently doesn't seem to be easily
> available when we need it, so this patch turns PLACEHOLDER_TYPE_CONSTRAINTS
> into a TREE_LIST whose TREE_PURPOSE additionally holds the value of
> current_template_parms whence a constrained 'auto' was formed.
> 
> This patch also removes some seemingly wrong handling of placeholder
> type arguments from tsubst_parameter_mapping.  The code doesn't trigger
> with the example used in the comments, because type_uses_auto doesn't
> look inside non-deduced contexts such as the operand of decltype.  And
> the call to do_auto_deduction seems confused because if 'arg' is a type,
> then so is 'parm', and therefore 'init' too is a type, but
> do_auto_deduction expects it to be an expression.  Before this patch,
> this code was dead (as far as our testsuite can tell), but now it breaks
> other parts of this patch, so let's remove it.
> 
> gcc/cp/ChangeLog:
> 
> 	PR c++/96443
> 	PR c++/96960
> 	* constraint.cc (type_deducible_p): Don't substitute into the
> 	constraints, and instead just pass 'args' to do_auto_deduction
> 	as the outer template arguments.
> 	(tsubst_parameter_mapping): Remove confused code for handling
> 	placeholder type arguments.
> 	(normalize_placeholder_type_constraint): Define.
> 	(satisfy_constraint_expression): Use it to handle placeholder
> 	'auto' types.
> 	* cp-tree.h (PLACEHOLDER_TYPE_CONSTRAINTS_INFO): Define.
> 	(PLACEHOLDER_TYPE_CONSTRAINTS): Redefine in terms of the above.
> 	* pt.c (tsubst) <case TEMPLATE_TYPE_PARM>: Use
> 	PLACEHOLDER_TYPE_CONSTRAINTS_INFO instead.
> 	(make_constrained_placeholder_type): Set
> 	PLACEHOLDER_TYPE_CONSTRAINTS_INFO instead.
> 	(do_auto_deduction): Clarify comments about the outer_targs
> 	parameter.  Rework satisfaction of a placeholder type constraint
> 	to pass in the complete set of template arguments directly to
> 	constraints_satisfied_p.
> 	(splice_late_return_type): Use PLACEHOLDER_TYPE_CONSTRAINTS_INFO
> 	instead.  Also rebuild the the constraint info on the new auto.
> 
> gcc/testsuite/ChangeLog:
> 
> 	PR c++/96443
> 	PR c++/96960
> 	* g++.dg/concepts/abbrev9.C: New test.
> 	* g++.dg/cpp2a/concepts-lambda15.C: New test.
> 	* g++.dg/cpp2a/concepts-placeholder3.C: New test.
> 	* g++.dg/cpp2a/concepts-return-req2.C: New test.
> 	* g++.dg/concepts-ts1.C: Add dg-bogus directive to the call to
> 	f15 that we expect to accept.
> ---
>   gcc/cp/constraint.cc                          | 111 ++++++++----------
>   gcc/cp/cp-tree.h                              |  16 ++-
>   gcc/cp/pt.c                                   |  73 ++++++------
>   gcc/testsuite/g++.dg/concepts/abbrev9.C       |  26 ++++
>   .../g++.dg/cpp2a/concepts-lambda15.C          |  16 +++
>   .../g++.dg/cpp2a/concepts-placeholder3.C      |  19 +++
>   .../g++.dg/cpp2a/concepts-return-req2.C       |  13 ++
>   gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C     |   2 +-
>   8 files changed, 172 insertions(+), 104 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/concepts/abbrev9.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-lambda15.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-placeholder3.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-return-req2.C
> 
> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> index fcb249a642f..2b61ad8d9ea 100644
> --- a/gcc/cp/constraint.cc
> +++ b/gcc/cp/constraint.cc
> @@ -1989,39 +1989,19 @@ type_deducible_p (tree expr, tree type, tree placeholder, tree args,
>        references are preserved in the result.  */
>     expr = force_paren_expr_uneval (expr);
>   
> -  /* Replace the constraints with the instantiated constraints. This
> -     substitutes args into any template parameters in the trailing
> -     result type.  */
> -  tree saved_constr = PLACEHOLDER_TYPE_CONSTRAINTS (placeholder);
> -  tree subst_constr
> -    = tsubst_constraint (saved_constr,
> -			 args,
> -			 info.complain | tf_partial,
> -			 info.in_decl);
> -
> -  if (subst_constr == error_mark_node)
> -    return false;
> -
> -  PLACEHOLDER_TYPE_CONSTRAINTS (placeholder) = subst_constr;
> -
> -  /* Temporarily unlink the canonical type.  */
> -  tree saved_type = TYPE_CANONICAL (placeholder);
> -  TYPE_CANONICAL (placeholder) = NULL_TREE;
> -
> -  tree deduced_type
> -    = do_auto_deduction (type,
> -			 expr,
> -			 placeholder,
> -			 info.complain,
> -			 adc_requirement);
> -
> -  PLACEHOLDER_TYPE_CONSTRAINTS (placeholder) = saved_constr;
> -  TYPE_CANONICAL (placeholder) = saved_type;
> +  /* When args is empty, we're evaluating a non-templated requires expression,
> +     but even those are parsed under processing_template_decl == 1, and so the
> +     placeholder 'auto' inside this return-type-requirement has level 2.  In
> +     order to have all parms and arguments match up for satisfaction, we need
> +     to pass a single level of OUTER_TARGS in this case.  */
> +  if (!args)
> +    args = make_tree_vec (0);
>   
> -  if (deduced_type == error_mark_node)
> -    return false;
> +  tree deduced_type = do_auto_deduction (type, expr, placeholder,
> +					 info.complain, adc_requirement,
> +					 /*outer_targs=*/args);
>   
> -  return true;
> +  return deduced_type != error_mark_node;
>   }
>   
>   /* True if EXPR can not be converted to TYPE.  */
> @@ -2286,35 +2266,10 @@ tsubst_parameter_mapping (tree map, tree args, subst_info info)
>           return error_mark_node;
>         tree parm = TREE_VALUE (p);
>         tree arg = TREE_PURPOSE (p);
> -      tree new_arg = NULL_TREE;
> -      if (TYPE_P (arg))
> -        {
> -          /* If a template parameter is declared with a placeholder, we can
> -             get those in the argument list if decltype is applied to the
> -             placeholder. For example:
> -
> -		template<auto T>
> -		  requires C<decltype(T)>
> -		void f() { }
> -
> -	     The normalized argument for C will be an auto type, so we'll
> -             need to deduce the actual argument from the corresponding
> -             initializer (whatever argument is provided for T), and use
> -             that result in the instantiated parameter mapping.  */
> -          if (tree auto_node = type_uses_auto (arg))
> -            {
> -              int level;
> -              int index;
> -	      template_parm_level_and_index (parm, &level, &index);
> -	      tree init = TMPL_ARG (args, level, index);
> -              new_arg = do_auto_deduction (arg, init, auto_node,
> -					   complain, adc_variable_type,
> -					   make_tree_vec (0));
> -            }
> -        }
> -      else if (ARGUMENT_PACK_P (arg))
> +      tree new_arg;
> +      if (ARGUMENT_PACK_P (arg))
>   	new_arg = tsubst_argument_pack (arg, args, complain, in_decl);
> -      if (!new_arg)
> +      else
>   	{
>   	  new_arg = tsubst_template_arg (arg, args, complain, in_decl);
>   	  if (TYPE_P (new_arg))
> @@ -3020,6 +2975,36 @@ satisfy_associated_constraints (tree t, tree args, sat_info info)
>     return satisfy_constraint (t, args, info);
>   }
>   
> +/* Return the normal form of the constraints on the placeholder 'auto'
> +   type T.  */
> +
> +static tree
> +normalize_placeholder_type_constraints (tree t, bool diag)
> +{
> +  gcc_assert (is_auto (t));
> +  tree ci = PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t);
> +  if (!ci)
> +    return NULL_TREE;
> +
> +  tree constr = TREE_VALUE (ci);
> +  /* The TREE_PURPOSE contains the set of template parameters that were in
> +     scope for this placeholder type; use them as the initial template
> +     parameters for normalization.  */
> +  tree initial_parms = TREE_PURPOSE (ci);
> +  /* The 'auto' itself is used as the first argument in its own constraints,
> +     and its level is one greater than its template depth.  So in order to
> +     capture all used template parameters, we need to add an extra level of
> +     template parameters to the context; a dummy level suffices.  */
> +  initial_parms
> +    = tree_cons (size_int (initial_parms
> +			   ? TMPL_PARMS_DEPTH (initial_parms) + 1 : 1),
> +		 make_tree_vec (0), initial_parms);
> +
> +  norm_info info (diag ? tf_norm : tf_none);
> +  info.initial_parms = initial_parms;
> +  return normalize_constraint_expression (constr, info);
> +}
> +
>   /* Evaluate EXPR as a constraint expression using ARGS, returning a
>      satisfaction value. */
>   
> @@ -3029,8 +3014,6 @@ satisfy_constraint_expression (tree t, tree args, sat_info info)
>     if (t == error_mark_node)
>       return error_mark_node;
>   
> -  gcc_assert (EXPR_P (t));
> -
>     /* Get the normalized constraints.  */
>     tree norm;
>     if (args == NULL_TREE && concept_check_p (t))
> @@ -3054,6 +3037,12 @@ satisfy_constraint_expression (tree t, tree args, sat_info info)
>         norm_info ninfo (info.noisy () ? tf_norm : tf_none);
>         norm = normalize_constraint_expression (t, ninfo);
>       }
> +  else if (is_auto (t))
> +    {
> +      norm = normalize_placeholder_type_constraints (t, info.noisy ());
> +      if (!norm)
> +	return boolean_true_node;
> +    }
>     else
>       gcc_unreachable ();
>   
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index 38b31e3908f..544e99538a4 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -1578,11 +1578,19 @@ check_constraint_info (tree t)
>   #define COMPOUND_REQ_NOEXCEPT_P(NODE) \
>     TREE_LANG_FLAG_0 (TREE_CHECK (NODE, COMPOUND_REQ))
>   
> -/* The constraints on an 'auto' placeholder type, used in an argument deduction
> -   constraint.  */
> -#define PLACEHOLDER_TYPE_CONSTRAINTS(NODE) \
> +/* A TREE_LIST whose TREE_VALUE is the constraints on the 'auto' placeholder
> +   type NODE, used in an argument deduction constraint.  The TREE_PURPOSE
> +   holds the set of template parameters that were in-scope when this 'auto'
> +   was formed.  */
> +#define PLACEHOLDER_TYPE_CONSTRAINTS_INFO(NODE) \
>     DECL_SIZE_UNIT (TYPE_NAME (NODE))
>   
> +/* The constraints on the 'auto' placeholder type NODE.  */
> +#define PLACEHOLDER_TYPE_CONSTRAINTS(NODE)		   \
> +  (PLACEHOLDER_TYPE_CONSTRAINTS_INFO (NODE)		   \
> +   ? TREE_VALUE (PLACEHOLDER_TYPE_CONSTRAINTS_INFO (NODE)) \
> +   : NULL_TREE)
> +
>   /* True if NODE is a constraint.  */
>   #define CONSTR_P(NODE)                  \
>     (TREE_CODE (NODE) == ATOMIC_CONSTR    \
> @@ -8420,7 +8428,7 @@ set_implicit_rvalue_p (tree ot)
>   inline bool
>   is_constrained_auto (const_tree t)
>   {
> -  return is_auto (t) && PLACEHOLDER_TYPE_CONSTRAINTS (t);
> +  return is_auto (t) && PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t);
>   }
>   
>   /* RAII class to push/pop class scope T; if T is not a class, do nothing.  */
> diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
> index f324f6a1e1b..0c7fd496935 100644
> --- a/gcc/cp/pt.c
> +++ b/gcc/cp/pt.c
> @@ -15710,15 +15710,15 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
>   			       ? tf_ignore_bad_quals : 0));
>   	      }
>   	    else if (TREE_CODE (t) == TEMPLATE_TYPE_PARM
> -		     && PLACEHOLDER_TYPE_CONSTRAINTS (t)
> +		     && PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t)
>   		     && (r = (TEMPLATE_PARM_DESCENDANTS
>   			      (TEMPLATE_TYPE_PARM_INDEX (t))))
>   		     && (r = TREE_TYPE (r))
> -		     && !PLACEHOLDER_TYPE_CONSTRAINTS (r))
> +		     && !PLACEHOLDER_TYPE_CONSTRAINTS_INFO (r))
>   	      /* Break infinite recursion when substituting the constraints
>   		 of a constrained placeholder.  */;
>   	    else if (TREE_CODE (t) == TEMPLATE_TYPE_PARM
> -		     && !PLACEHOLDER_TYPE_CONSTRAINTS (t)
> +		     && !PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t)
>   		     && !CLASS_PLACEHOLDER_TEMPLATE (t)
>   		     && (arg = TEMPLATE_TYPE_PARM_INDEX (t),
>   			 r = TEMPLATE_PARM_DESCENDANTS (arg))
> @@ -15741,8 +15741,8 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
>   		  {
>   		    /* Propagate constraints on placeholders since they are
>   		       only instantiated during satisfaction.  */
> -		    if (tree constr = PLACEHOLDER_TYPE_CONSTRAINTS (t))
> -		      PLACEHOLDER_TYPE_CONSTRAINTS (r) = constr;
> +		    if (tree ci = PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t))
> +		      PLACEHOLDER_TYPE_CONSTRAINTS_INFO (r) = ci;
>   		    else if (tree pl = CLASS_PLACEHOLDER_TEMPLATE (t))
>   		      {
>   			pl = tsubst_copy (pl, args, complain, in_decl);
> @@ -28183,7 +28183,8 @@ make_constrained_placeholder_type (tree type, tree con, tree args)
>     expr = build_concept_check (expr, type, args, tf_warning_or_error);
>     --processing_template_decl;
>   
> -  PLACEHOLDER_TYPE_CONSTRAINTS (type) = expr;
> +  PLACEHOLDER_TYPE_CONSTRAINTS_INFO (type)
> +    = build_tree_list (current_template_parms, expr);
>   
>     /* Our canonical type depends on the constraint.  */
>     TYPE_CANONICAL (type) = canonical_type_parameter (type);
> @@ -29423,9 +29424,11 @@ do_class_deduction (tree ptype, tree tmpl, tree init,
>      from INIT.  AUTO_NODE is the TEMPLATE_TYPE_PARM used for 'auto' in TYPE.
>      The CONTEXT determines the context in which auto deduction is performed
>      and is used to control error diagnostics.  FLAGS are the LOOKUP_* flags.
> -   OUTER_TARGS are used during template argument deduction
> -   (context == adc_unify) to properly substitute the result, and is ignored
> -   in other contexts.
> +
> +   OUTER_TARGS is used during template argument deduction (context == adc_unify)
> +   to properly substitute the result.  It's also used in the adc_unify and
> +   adc_requirement contexts to communicate the the necessary template arguments
> +   to satisfaction.  OUTER_TARGS is ignored in other contexts.
>   
>      For partial-concept-ids, extra args may be appended to the list of deduced
>      template arguments prior to determining constraint satisfaction.  */
> @@ -29586,30 +29589,21 @@ do_auto_deduction (tree type, tree init, tree auto_node,
>       }
>   
>     /* Check any placeholder constraints against the deduced type. */
> -  if (flag_concepts && !processing_template_decl)
> -    if (tree check = NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
> +  if (flag_concepts)
> +    if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
>         {
> -        /* Use the deduced type to check the associated constraints. If we
> -           have a partial-concept-id, rebuild the argument list so that
> -           we check using the extra arguments. */
> -	check = unpack_concept_check (check);
> -	gcc_assert (TREE_CODE (check) == TEMPLATE_ID_EXPR);
> -	tree cdecl = TREE_OPERAND (check, 0);
> -	if (OVL_P (cdecl))
> -	  cdecl = OVL_FIRST (cdecl);
> -        tree cargs = TREE_OPERAND (check, 1);
> -        if (TREE_VEC_LENGTH (cargs) > 1)
> -          {
> -            cargs = copy_node (cargs);
> -            TREE_VEC_ELT (cargs, 0) = TREE_VEC_ELT (targs, 0);
> -          }
> -        else
> -          cargs = targs;
> +	if (processing_template_decl)
> +	  /* In general we can't check satisfaction until we know all
> +	     template arguments.  */
> +	  return type;
>   
> -	/* Rebuild the check using the deduced arguments.  */
> -	check = build_concept_check (cdecl, cargs, tf_none);
> +	if ((context == adc_return_type || context == adc_variable_type)
> +	    && current_function_decl
> +	    && DECL_TEMPLATE_INFO (current_function_decl))
> +	  outer_targs = DECL_TI_ARGS (current_function_decl);
>   
> -	if (!constraints_satisfied_p (check))
> +	tree complete_targs = add_to_template_args (outer_targs, targs);
> +	if (!constraints_satisfied_p (auto_node, complete_targs))
>             {
>               if (complain & tf_warning_or_error)
>                 {
> @@ -29634,15 +29628,16 @@ do_auto_deduction (tree type, tree init, tree auto_node,
>                              "placeholder constraints");
>                       break;
>                     }
> -		diagnose_constraints (input_location, check, targs);
> +		diagnose_constraints (input_location, auto_node, complete_targs);
>                 }
>               return error_mark_node;
>             }
>         }
>   
> -  if (processing_template_decl && context != adc_unify)
> -    outer_targs = current_template_args ();
> -  targs = add_to_template_args (outer_targs, targs);
> +  if (context == adc_unify)
> +    targs = add_to_template_args (outer_targs, targs);
> +  else if (processing_template_decl)
> +    targs = add_to_template_args (current_template_args (), targs);
>     return tsubst (type, targs, complain, NULL_TREE);
>   }
>   
> @@ -29669,10 +29664,12 @@ splice_late_return_type (tree type, tree late_return_type)
>   	TREE_VEC_ELT (auto_vec, 0) = new_auto;
>   	tree targs = add_outermost_template_args (current_template_args (),
>   						  auto_vec);
> -	/* FIXME: We should also rebuild the constraint to refer to the new
> -	   auto.  */
> -	PLACEHOLDER_TYPE_CONSTRAINTS (new_auto)
> -	  = PLACEHOLDER_TYPE_CONSTRAINTS (auto_node);
> +	/* Also rebuild the constraint info in terms of the new auto.  */
> +	if (tree ci = PLACEHOLDER_TYPE_CONSTRAINTS_INFO (auto_node))
> +	  PLACEHOLDER_TYPE_CONSTRAINTS_INFO (new_auto)
> +	    = build_tree_list (current_template_parms,
> +			       tsubst_constraint (TREE_VALUE (ci), targs,
> +						  tf_none, NULL_TREE));
>   	TYPE_CANONICAL (new_auto) = canonical_type_parameter (new_auto);
>   	return tsubst (type, targs, tf_none, NULL_TREE);
>         }
> diff --git a/gcc/testsuite/g++.dg/concepts/abbrev9.C b/gcc/testsuite/g++.dg/concepts/abbrev9.C
> new file mode 100644
> index 00000000000..865b44c6a63
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/concepts/abbrev9.C
> @@ -0,0 +1,26 @@
> +// { dg-do compile { target concepts } }
> +
> +template <class T, class U> concept same_as = __is_same(T, U);
> +
> +same_as<int> auto f(auto, auto y) {
> +  return y; // { dg-error "deduced return type" }
> +}
> +
> +template <class>
> +struct A {
> +  static auto g(auto x, auto y) -> same_as<decltype(x)> auto {
> +    return y; // { dg-error "deduced return type" }
> +  }
> +};
> +
> +int main() {
> +  f(0, 0);   // { dg-bogus "" }
> +  f("", 0);  // { dg-bogus "" }
> +  f(0, "");  // { dg-message "required from here" }
> +  f("", ""); // { dg-message "required from here" }
> +
> +  A<void>::g(0, 0);   // { dg-bogus "" }
> +  A<void>::g("", 0);  // { dg-message "required from here" }
> +  A<void>::g(0, "");  // { dg-message "required from here" }
> +  A<void>::g("", ""); // { dg-bogus "" }
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-lambda15.C b/gcc/testsuite/g++.dg/cpp2a/concepts-lambda15.C
> new file mode 100644
> index 00000000000..29df5d0b1ac
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-lambda15.C
> @@ -0,0 +1,16 @@
> +// PR c++/96960
> +// { dg-do compile { target c++20 } }
> +
> +template <class, class> concept C0 = true;
> +
> +template <class T>
> +concept C = requires(T t) {
> +  { 42 } -> C0<char [([] { return 42; }())]>;
> +};
> +
> +static_assert(C<int>);
> +
> +C0<char [([] { return 42; }())]> auto x = 42;
> +
> +int f(C0<char [([] { return 42; }())]> auto x);
> +int y = f(42);
> diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder3.C b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder3.C
> new file mode 100644
> index 00000000000..87e3c093e28
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder3.C
> @@ -0,0 +1,19 @@
> +// PR c++/96443
> +// { dg-do compile { target c++20 } }
> +
> +template <class T, class U> concept same_as = __is_same(T, U);
> +
> +auto f(auto x) -> same_as<decltype(x)> auto { return 0; }; // { dg-error "constraints" }
> +void g(auto x) { same_as<decltype(x)> auto y = 0; } // { dg-error "constraints" }
> +auto h(auto x) -> same_as<decltype(x.missing)> auto { return 0; } // { dg-error "constraints|missing" }
> +template <class T, same_as<T> auto N> void i() {}
> +
> +int main() {
> +  f(0); // { dg-bogus "" }
> +  f(true); // { dg-message "required from here" }
> +  g(0); // { dg-bogus "" }
> +  g(true); // { dg-message "required from here" }
> +  h(0); // { dg-message "required from here" }
> +  i<int, 0>(); // { dg-bogus "" }
> +  i<int, true>(); // { dg-error "no match|constraints" }
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-return-req2.C b/gcc/testsuite/g++.dg/cpp2a/concepts-return-req2.C
> new file mode 100644
> index 00000000000..77208bb7069
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-return-req2.C
> @@ -0,0 +1,13 @@
> +// Verify we check return-type-requirements by passing the entire set of
> +// template arguments to normalization rather than first substituting into
> +// the constraint.  The latter approach would induce a substitution failure and
> +// cause the requires-expression to evaluate to false here.
> +// { dg-do compile { target c++20 } }
> +
> +template <class, class>
> +concept C1 = true;
> +
> +template <class T>
> +concept C2 = requires { { 0 } -> C1<typename T::type>; };
> +
> +static_assert(C2<int>);
> diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C b/gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C
> index 1cefe3b243f..a116cac4ea4 100644
> --- a/gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C
> +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-ts1.C
> @@ -40,7 +40,7 @@ void driver()
>     f3('a'); // { dg-error "" }
>     f4(0, 0);
>     f4(0, 'a'); // { dg-error "" }
> -  f15(0);
> +  f15(0); // { dg-bogus "" }
>     f15('a'); // { dg-message "" }
>   }
>   
> 


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 5/6] c++: Clean up normalization / satisfaction routines
  2021-02-28 17:58 ` [PATCH 5/6] c++: Clean up normalization / satisfaction routines Patrick Palka
@ 2021-03-01 22:50   ` Jason Merrill
  2021-03-02 16:25     ` Patrick Palka
  0 siblings, 1 reply; 29+ messages in thread
From: Jason Merrill @ 2021-03-01 22:50 UTC (permalink / raw)
  To: Patrick Palka, gcc-patches

On 2/28/21 12:58 PM, Patrick Palka wrote:
> This patch mostly performs some straightforward refactoring:
> 
>    - Renamed satisfy_constraint to satisfy_normalized_constraints
>    - Renamed the three-parameter version of satisfy_constraint_expression
>      to satisfy_nondeclaration_constraints
>    - Removed normalize_(non)?template_requirements
>    - Removed satisfy_associated_constraints (and made its callers
>      check for dependent template args sooner, before normalization)
>    - Removed the tsubst_flags_t parameter of evaluate_concept_check
>    - Combined the two versions of constraint_satisfaction_value
>    - Combined the two versions of constraint_satisfied_p
> 
> Additionally, this patch removes the handling of bare
> constraint-expressions from satisfy_nondeclaration_constraints, and
> hence constraints_satisfied_p and constraint_satisfaction_value now only
> take things that carry their own template information needed for
> normalization.  In practice, this only means it's no longer possible to
> evaluate bare REQUIRES_EXPRs via the satisfaction routines, and so this
> patch adjusts the affected callers to instead use tsubst_requires_expr.

It's probably better to have a different entry point than 
tsubst_requires_expr for callers that have nothing to do with templates. 
  Whether that's a one-line wrapper for the call to tsubst_requires_expr 
("evaluate_requires_expr"?) or calling tsubst_requires_expr from 
satisfy_nondeclaration_constraints, I don't have an opinion either way.

> For convenience, the function diagnose_constraints continues to accept
> REQUIRES_EXPRs, but it now handles them by calling diagnose_require_expr
> directly.

This might argue for the latter choice above.

> (That we used to evaluate REQUIRES_EXPR via satisfaction might even be a
> correctness issue: since we cache satisfaction in special ways that don't
> apply to regular evaluation, going through satisfaction could in theory
> cause us to reuse a cached value for a REQUIRES_EXPR when we shouldn't
> have.)

> gcc/cp/ChangeLog:
> 
> 	* constexpr.c (cxx_eval_call_expression): Adjust call to
> 	evaluate_concept_check.
> 	(cxx_eval_constant_expression) <case REQUIRES_EXPR>: Use
> 	tsubst_requires_expr instead of satisfy_constraint_expression.
> 	<case TEMPLATE_ID_EXPR>: Adjust call to evaluate_concept_check.
> 	* constraint.cc (struct sat_info): Adjust comment about which
> 	satisfaction entrypoints use noisy-unsat.
> 	(normalize_template_requirements): Remove (and adjust callers
> 	appropriately).
> 	(normalize_nontemplate_requirements): Likewise.
> 	(tsubst_nested_requirement): Use constraint_satisfaction_value
> 	instead of satisfy_constraint_expression, which'll do the
> 	noisy replaying of ill-formed quiet satisfaction for us.
> 	(decl_satisfied_cache): Adjust comment.
> 	(satisfy_constraint): Rename to ...
> 	(satisfy_normalized_constraints): ... this.
> 	(satisfy_associated_constraints): Remove (and make its
> 	callers check for dependent arguments).
> 	(satisfy_constraint_expression): Rename to ...
> 	(satisfy_nondeclaration_constraints): ... this.  Assert that
> 	'args' is empty when 't' is a concept-id.  Removing handling
> 	bare constraint-expressions.  Adjust comment accordingly.
> 	(satisfy_declaration_constraints): Assert in the two-parameter
> 	version that 't' is not a TEMPLATE_DECL.  Adjust following
> 	removal of normalize_(non)?template_requirements and
> 	satisfy_asociated_constraints.
> 	(constraint_satisfaction_value): Combine the two- and
> 	three-parameter versions in the natural way.
> 	(constraints_satisfied_p): Combine the one- and two-parameter
> 	versions in the natural way.  Improve documentation.
> 	(evaluate_concept_check): Remove 'complain' parameter.  Use
> 	constraint_satisfaction_value instead of
> 	satisfy_constraint_expression.
> 	(diagnose_nested_requirement): Adjust following renaming of
> 	satisfy_constraint_expression.
> 	(diagnose_constraints): Handle REQUIRES_EXPR by going through
> 	diagnose_requires_expr directly instead of treating it as a
> 	constraint-expression.  Improve documentation.
> 	* cp-gimplify.c (cp_genericize_r) <case CALL_EXPR>: Adjust call
> 	to evaluate_concept_check.
> 	<case REQUIRES_EXPR>: Use tsubst_requires_expr instead of
> 	constraints_satisfied_p.
> 	<case TEMPLATE_ID_EXPR>: Adjust call to evaluate_concept_check.
> 	* cp-tree.h (evaluate_concept_check): Remove tsubst_flag_t
> 	parameter.
> 	(satisfy_constraint_expression): Remove declaration.
> 	(constraints_satisfied_p): Remove one-parameter declaration.
> 	Add a default argument to the two-parameter declaration.
> 	* cvt.c (convert_to_void): Adjust call to
> 	evaluate_concept_check.
> ---
>   gcc/cp/constexpr.c   |   6 +-
>   gcc/cp/constraint.cc | 210 ++++++++++++++++---------------------------
>   gcc/cp/cp-gimplify.c |   7 +-
>   gcc/cp/cp-tree.h     |   6 +-
>   gcc/cp/cvt.c         |   2 +-
>   5 files changed, 85 insertions(+), 146 deletions(-)
> 
> diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c
> index cd0a68e9fd6..f940e3e5985 100644
> --- a/gcc/cp/constexpr.c
> +++ b/gcc/cp/constexpr.c
> @@ -2257,7 +2257,7 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
>   {
>     /* Handle concept checks separately.  */
>     if (concept_check_p (t))
> -    return evaluate_concept_check (t, tf_warning_or_error);
> +    return evaluate_concept_check (t);
>   
>     location_t loc = cp_expr_loc_or_input_loc (t);
>     tree fun = get_function_named_in_call (t);
> @@ -6905,7 +6905,7 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
>            '!requires (T t) { ... }' which is not transformed into
>            a constraint.  */
>         if (!processing_template_decl)
> -        return satisfy_constraint_expression (t);
> +	return tsubst_requires_expr (t, NULL_TREE, tf_none, NULL_TREE);
>         else
>           *non_constant_p = true;
>         return t;
> @@ -6941,7 +6941,7 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
>   
>   	if (!processing_template_decl
>   	    && !uid_sensitive_constexpr_evaluation_p ())
> -	  r = evaluate_concept_check (t, tf_warning_or_error);
> +	  r = evaluate_concept_check (t);
>   	else
>   	  *non_constant_p = true;
>   
> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> index 2b61ad8d9ea..cf319b34da0 100644
> --- a/gcc/cp/constraint.cc
> +++ b/gcc/cp/constraint.cc
> @@ -107,10 +107,9 @@ struct subst_info
>      a constraint is not satisfied.
>   
>      The entrypoints to satisfaction for which we set noisy+unsat are
> -   diagnose_constraints and diagnose_nested_requirement.  The entrypoints for
> -   which we set noisy-unsat are the replays inside constraint_satisfaction_value,
> -   evaluate_concept_check and tsubst_nested_requirement.  In other entrypoints,
> -   e.g. constraints_satisfied_p, we enter satisfaction quietly (both flags
> +   diagnose_constraints and diagnose_nested_requirement.  The entrypoint for
> +   which we set noisy-unsat is the replay inside constraint_satisfaction_value.
> +   From constraints_satisfied_p, we enter satisfaction quietly (both flags
>      cleared).  */
>   
>   struct sat_info : subst_info
> @@ -133,7 +132,7 @@ struct sat_info : subst_info
>     bool diagnose_unsatisfaction;
>   };
>   
> -static tree satisfy_constraint_expression (tree, tree, sat_info);
> +static tree constraint_satisfaction_value (tree, tree, sat_info);
>   
>   /* True if T is known to be some type other than bool. Note that this
>      is false for dependent types and errors.  */
> @@ -958,22 +957,6 @@ normalize_concept_definition (tree tmpl, bool diag = false)
>     return norm;
>   }
>   
> -/* Returns the normal form of TMPL's requirements.  */
> -
> -static tree
> -normalize_template_requirements (tree tmpl, bool diag = false)
> -{
> -  return get_normalized_constraints_from_decl (tmpl, diag);
> -}
> -
> -/* Returns the normal form of TMPL's requirements.  */
> -
> -static tree
> -normalize_nontemplate_requirements (tree decl, bool diag = false)
> -{
> -  return get_normalized_constraints_from_decl (decl, diag);
> -}
> -
>   /* Normalize an EXPR as a constraint.  */
>   
>   static tree
> @@ -2068,15 +2051,8 @@ tsubst_compound_requirement (tree t, tree args, subst_info info)
>   static tree
>   tsubst_nested_requirement (tree t, tree args, subst_info info)
>   {
> -  /* Perform satisfaction quietly first.  */
>     sat_info quiet (tf_none, info.in_decl);
> -  tree result = satisfy_constraint_expression (t, args, quiet);
> -  if (result == error_mark_node)
> -    {
> -      /* Replay the error.  */
> -      sat_info noisy (tf_warning_or_error, info.in_decl);
> -      satisfy_constraint_expression (t, args, noisy);
> -    }
> +  tree result = constraint_satisfaction_value (t, args, quiet);
>     if (result != boolean_true_node)
>       return error_mark_node;
>     return boolean_true_node;
> @@ -2459,7 +2435,7 @@ struct sat_hasher : ggc_ptr_hash<sat_entry>
>   /* Cache the result of satisfy_atom.  */
>   static GTY((deletable)) hash_table<sat_hasher> *sat_cache;
>   
> -/* Cache the result of constraint_satisfaction_value.  */
> +/* Cache the result of satisfy_declaration_constraints.  */
>   static GTY((deletable)) hash_map<tree, tree> *decl_satisfied_cache;
>   
>   /* A tool used by satisfy_atom to help manage satisfaction caching and to
> @@ -2941,7 +2917,7 @@ satisfy_constraint_r (tree t, tree args, sat_info info)
>   /* Check that the normalized constraint T is satisfied for ARGS.  */
>   
>   static tree
> -satisfy_constraint (tree t, tree args, sat_info info)
> +satisfy_normalized_constraints (tree t, tree args, sat_info info)
>   {
>     auto_timevar time (TV_CONSTRAINT_SAT);
>   
> @@ -2957,24 +2933,6 @@ satisfy_constraint (tree t, tree args, sat_info info)
>     return satisfy_constraint_r (t, args, info);
>   }
>   
> -/* Check the normalized constraints T against ARGS, returning a satisfaction
> -   value (either true, false, or error).  */
> -
> -static tree
> -satisfy_associated_constraints (tree t, tree args, sat_info info)
> -{
> -  /* If there are no constraints then this is trivially satisfied.  */
> -  if (!t)
> -    return boolean_true_node;
> -
> -  /* If any arguments depend on template parameters, we can't
> -     check constraints. Pretend they're satisfied for now.  */
> -  if (args && uses_template_parms (args))
> -    return boolean_true_node;
> -
> -  return satisfy_constraint (t, args, info);
> -}
> -
>   /* Return the normal form of the constraints on the placeholder 'auto'
>      type T.  */
>   
> @@ -3005,19 +2963,20 @@ normalize_placeholder_type_constraints (tree t, bool diag)
>     return normalize_constraint_expression (constr, info);
>   }
>   
> -/* Evaluate EXPR as a constraint expression using ARGS, returning a
> -   satisfaction value. */
> +/* Evaluate the constraints of T using ARGS, returning a satisfaction value.
> +   Here, T can be a concept-id, nested-requirement or placeholder 'auto'.  */
>   
>   static tree
> -satisfy_constraint_expression (tree t, tree args, sat_info info)
> +satisfy_nondeclaration_constraints (tree t, tree args, sat_info info)
>   {
>     if (t == error_mark_node)
>       return error_mark_node;
>   
>     /* Get the normalized constraints.  */
>     tree norm;
> -  if (args == NULL_TREE && concept_check_p (t))
> +  if (concept_check_p (t))
>       {
> +      gcc_assert (!args);
>         tree id = unpack_concept_check (t);
>         args = TREE_OPERAND (id, 1);
>         tree tmpl = get_concept_check_template (id);
> @@ -3032,11 +2991,6 @@ satisfy_constraint_expression (tree t, tree args, sat_info info)
>         ninfo.initial_parms = TREE_TYPE (t);
>         norm = normalize_constraint_expression (TREE_OPERAND (t, 0), ninfo);
>       }
> -  else if (EXPR_P (t))
> -    {
> -      norm_info ninfo (info.noisy () ? tf_norm : tf_none);
> -      norm = normalize_constraint_expression (t, ninfo);
> -    }
>     else if (is_auto (t))
>       {
>         norm = normalize_placeholder_type_constraints (t, info.noisy ());
> @@ -3047,23 +3001,16 @@ satisfy_constraint_expression (tree t, tree args, sat_info info)
>       gcc_unreachable ();
>   
>     /* Perform satisfaction.  */
> -  return satisfy_constraint (norm, args, info);
> +  return satisfy_normalized_constraints (norm, args, info);
>   }
>   
> -/* Used only to evaluate requires-expressions during constant expression
> -   evaluation.  */
> -
> -tree
> -satisfy_constraint_expression (tree expr)
> -{
> -  sat_info info (tf_none, NULL_TREE);
> -  return satisfy_constraint_expression (expr, NULL_TREE, info);
> -}
> +/* Evaluate the associated constraints of the template specialization T
> +   according to INFO, returning a satisfaction value.  */
>   
>   static tree
>   satisfy_declaration_constraints (tree t, sat_info info)
>   {
> -  gcc_assert (DECL_P (t));
> +  gcc_assert (DECL_P (t) && TREE_CODE (t) != TEMPLATE_DECL);
>     const tree saved_t = t;
>   
>     /* For inherited constructors, consider the original declaration;
> @@ -3083,26 +3030,24 @@ satisfy_declaration_constraints (tree t, sat_info info)
>       if (tree *result = hash_map_safe_get (decl_satisfied_cache, saved_t))
>         return *result;
>   
> -  /* Get the normalized constraints.  */
> -  tree norm = NULL_TREE;
>     tree args = NULL_TREE;
>     if (tree ti = DECL_TEMPLATE_INFO (t))
>       {
> -      tree tmpl = TI_TEMPLATE (ti);
> -      norm = normalize_template_requirements (tmpl, info.noisy ());
> -
>         /* The initial parameter mapping is the complete set of
>   	 template arguments substituted into the declaration.  */
>         args = TI_ARGS (ti);
>         if (inh_ctor_targs)
>   	args = add_outermost_template_args (args, inh_ctor_targs);
> -    }
> -  else
> -    {
> -      /* These should be empty until we allow constraints on non-templates.  */
> -      norm = normalize_nontemplate_requirements (t, info.noisy ());
> +
> +      /* If any arguments depend on template parameters, we can't
> +	 check constraints. Pretend they're satisfied for now.  */
> +      if (uses_template_parms (args))
> +	return boolean_true_node;
>       }
>   
> +  /* Get the normalized constraints.  */
> +  tree norm = get_normalized_constraints_from_decl (t, info.noisy ());
> +
>     unsigned ftc_count = vec_safe_length (failed_type_completions);
>   
>     tree result = boolean_true_node;
> @@ -3111,7 +3056,7 @@ satisfy_declaration_constraints (tree t, sat_info info)
>         if (!push_tinst_level (t))
>   	return result;
>         push_access_scope (t);
> -      result = satisfy_associated_constraints (norm, args, info);
> +      result = satisfy_normalized_constraints (norm, args, info);
>         pop_access_scope (t);
>         pop_tinst_level ();
>       }
> @@ -3134,6 +3079,10 @@ satisfy_declaration_constraints (tree t, sat_info info)
>     return result;
>   }
>   
> +/* Evaluate the associated constraints of the template T using ARGS as the
> +   innermost set of template arguments and according to INFO, returning a
> +   satisfaction value.  */
> +
>   static tree
>   satisfy_declaration_constraints (tree t, tree args, sat_info info)
>   {
> @@ -3144,14 +3093,19 @@ satisfy_declaration_constraints (tree t, tree args, sat_info info)
>   
>     args = add_outermost_template_args (t, args);
>   
> +  /* If any arguments depend on template parameters, we can't
> +     check constraints. Pretend they're satisfied for now.  */
> +  if (uses_template_parms (args))
> +    return boolean_true_node;
> +
>     tree result = boolean_true_node;
> -  if (tree norm = normalize_template_requirements (t, info.noisy ()))
> +  if (tree norm = get_normalized_constraints_from_decl (t, info.noisy ()))
>       {
>         if (!push_tinst_level (t, args))
>   	return result;
>         tree pattern = DECL_TEMPLATE_RESULT (t);
>         push_access_scope (pattern);
> -      result = satisfy_associated_constraints (norm, args, info);
> +      result = satisfy_normalized_constraints (norm, args, info);
>         pop_access_scope (pattern);
>         pop_tinst_level ();
>       }
> @@ -3159,62 +3113,50 @@ satisfy_declaration_constraints (tree t, tree args, sat_info info)
>     return result;
>   }
>   
> +/* A wrapper around satisfy_declaration_constraints and
> +   satisfy_nondeclaration_constraints which additionally replays
> +   quiet ill-formed satisfaction noisily, so that ill-formed
> +   satisfaction always gets diagnosed.  */
> +
>   static tree
> -constraint_satisfaction_value (tree t, sat_info info)
> +constraint_satisfaction_value (tree t, tree args, sat_info info)
>   {
>     tree r;
>     if (DECL_P (t))
> -    r = satisfy_declaration_constraints (t, info);
> +    {
> +      if (args)
> +	r = satisfy_declaration_constraints (t, args, info);
> +      else
> +	r = satisfy_declaration_constraints (t, info);
> +    }
>     else
> -    r = satisfy_constraint_expression (t, NULL_TREE, info);
> +    r = satisfy_nondeclaration_constraints (t, args, info);
>     if (r == error_mark_node && info.quiet ()
>         && !(DECL_P (t) && TREE_NO_WARNING (t)))
>       {
> -      /* Replay the error with re-normalized requirements.  */
> +      /* Replay the error noisily.  */
>         sat_info noisy (tf_warning_or_error, info.in_decl);
> -      constraint_satisfaction_value (t, noisy);
> -      if (DECL_P (t))
> +      constraint_satisfaction_value (t, args, noisy);
> +      if (DECL_P (t) && !args)
>   	/* Avoid giving these errors again.  */
>   	TREE_NO_WARNING (t) = true;
>       }
>     return r;
>   }
>   
> -static tree
> -constraint_satisfaction_value (tree t, tree args, sat_info info)
> -{
> -  tree r;
> -  if (DECL_P (t))
> -    r = satisfy_declaration_constraints (t, args, info);
> -  else
> -    r = satisfy_constraint_expression (t, args, info);
> -  if (r == error_mark_node && info.quiet ())
> -    {
> -      /* Replay the error with re-normalized requirements.  */
> -      sat_info noisy (tf_warning_or_error, info.in_decl);
> -      constraint_satisfaction_value (t, args, noisy);
> -    }
> -  return r;
> -}
> -
> -/* True iff the result of satisfying T is BOOLEAN_TRUE_NODE and false
> -   otherwise, even in the case of errors.  */
> +/* True iff the result of satisfying T using ARGS is BOOLEAN_TRUE_NODE
> +   and false otherwise, even in the case of errors.
>   
> -bool
> -constraints_satisfied_p (tree t)
> -{
> -  if (!flag_concepts)
> -    return true;
> -
> -  sat_info quiet (tf_none, NULL_TREE);
> -  return constraint_satisfaction_value (t, quiet) == boolean_true_node;
> -}
> -
> -/* True iff the result of satisfying T with ARGS is BOOLEAN_TRUE_NODE
> -    and false otherwise, even in the case of errors.  */
> +   Here, T can be:
> +     - a template declaration (in which case ARGS is an innermost template
> +       argument set for T)
> +     - a template specialization (in which case ARGS must be empty)
> +     - a concept-id (in which case ARGS must be empty)
> +     - a nested-requirement (in which case ARGS is a complete argument set)
> +     - a placeholder 'auto' (in which case ARGS is a complete argument set).  */
>   
>   bool
> -constraints_satisfied_p (tree t, tree args)
> +constraints_satisfied_p (tree t, tree args/*= NULL_TREE */)
>   {
>     if (!flag_concepts)
>       return true;
> @@ -3227,7 +3169,7 @@ constraints_satisfied_p (tree t, tree args)
>      evaluation of template-ids as id-expressions.  */
>   
>   tree
> -evaluate_concept_check (tree check, tsubst_flags_t complain)
> +evaluate_concept_check (tree check)
>   {
>     if (check == error_mark_node)
>       return error_mark_node;
> @@ -3236,14 +3178,7 @@ evaluate_concept_check (tree check, tsubst_flags_t complain)
>   
>     /* Check for satisfaction without diagnostics.  */
>     sat_info quiet (tf_none, NULL_TREE);
> -  tree result = satisfy_constraint_expression (check, NULL_TREE, quiet);
> -  if (result == error_mark_node && (complain & tf_error))
> -    {
> -      /* Replay the error with re-normalized requirements.  */
> -      sat_info noisy (tf_warning_or_error, NULL_TREE);
> -      satisfy_constraint_expression (check, NULL_TREE, noisy);
> -    }
> -  return result;
> +  return constraint_satisfaction_value (check, /*args=*/NULL_TREE, quiet);
>   }
>   
>   /*---------------------------------------------------------------------------
> @@ -3709,7 +3644,7 @@ diagnose_nested_requirement (tree req, tree args)
>   {
>     /* Quietly check for satisfaction first.  */
>     sat_info quiet (tf_none, NULL_TREE);
> -  tree result = satisfy_constraint_expression (req, args, quiet);
> +  tree result = satisfy_nondeclaration_constraints (req, args, quiet);
>     if (result == boolean_true_node)
>       return;
>   
> @@ -3721,7 +3656,7 @@ diagnose_nested_requirement (tree req, tree args)
>         inform (loc, "nested requirement %qE is not satisfied, because", expr);
>   
>         sat_info noisy (tf_warning_or_error, NULL_TREE, /*diag_unsat=*/true);
> -      satisfy_constraint_expression (req, args, noisy);
> +      satisfy_nondeclaration_constraints (req, args, noisy);
>       }
>     else
>       inform (loc, "nested requirement %qE is not satisfied", expr);
> @@ -3854,7 +3789,9 @@ diagnosing_failed_constraint::replay_errors_p ()
>   }
>   
>   /* Emit diagnostics detailing the failure ARGS to satisfy the constraints
> -   of T. Here, T can be either a constraint or a declaration.  */
> +   of T.  Here, T and ARGS are as in constraints_satisfied_p, except that T
> +   can also be a REQUIRES_EXPR (in which case ARGS is must be empty).  The
> +   latter is used by finish_static_assert to diagnose a false REQUIRES_EXPR.  */
>   
>   void
>   diagnose_constraints (location_t loc, tree t, tree args)
> @@ -3866,8 +3803,13 @@ diagnose_constraints (location_t loc, tree t, tree args)
>   
>     /* Replay satisfaction, but diagnose unsatisfaction.  */
>     sat_info noisy (tf_warning_or_error, NULL_TREE, /*diag_unsat=*/true);
> -  if (!args)
> -    constraint_satisfaction_value (t, noisy);
> +  if (TREE_CODE (t) == REQUIRES_EXPR)
> +    {
> +      gcc_assert (!args);
> +      ++current_constraint_diagnosis_depth;
> +      diagnose_requires_expr (t, /*map=*/NULL_TREE, /*in_decl=*/NULL_TREE);
> +      --current_constraint_diagnosis_depth;
> +    }
>     else
>       constraint_satisfaction_value (t, args, noisy);
>   
> diff --git a/gcc/cp/cp-gimplify.c b/gcc/cp/cp-gimplify.c
> index abb8a6ef078..64b1dc1b433 100644
> --- a/gcc/cp/cp-gimplify.c
> +++ b/gcc/cp/cp-gimplify.c
> @@ -1381,7 +1381,7 @@ cp_genericize_r (tree *stmt_p, int *walk_subtrees, void *data)
>   	 normal functions.  */
>         if (concept_check_p (stmt))
>   	{
> -	  *stmt_p = evaluate_concept_check (stmt, tf_warning_or_error);
> +	  *stmt_p = evaluate_concept_check (stmt);
>   	  * walk_subtrees = 0;
>   	  break;
>   	}
> @@ -1453,15 +1453,14 @@ cp_genericize_r (tree *stmt_p, int *walk_subtrees, void *data)
>   
>       case REQUIRES_EXPR:
>         /* Emit the value of the requires-expression.  */
> -      *stmt_p = constant_boolean_node (constraints_satisfied_p (stmt),
> -				       boolean_type_node);
> +      *stmt_p = tsubst_requires_expr (stmt, NULL_TREE, tf_none, NULL_TREE);
>         *walk_subtrees = 0;
>         break;
>   
>       case TEMPLATE_ID_EXPR:
>         gcc_assert (concept_check_p (stmt));
>         /* Emit the value of the concept check.  */
> -      *stmt_p = evaluate_concept_check (stmt, tf_warning_or_error);
> +      *stmt_p = evaluate_concept_check (stmt);
>         walk_subtrees = 0;
>         break;
>   
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index 544e99538a4..995e13f4a6e 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -8123,10 +8123,8 @@ struct processing_constraint_expression_sentinel
>   extern bool processing_constraint_expression_p	();
>   
>   extern tree unpack_concept_check		(tree);
> -extern tree evaluate_concept_check              (tree, tsubst_flags_t);
> -extern tree satisfy_constraint_expression	(tree);
> -extern bool constraints_satisfied_p		(tree);
> -extern bool constraints_satisfied_p		(tree, tree);
> +extern tree evaluate_concept_check              (tree);
> +extern bool constraints_satisfied_p		(tree, tree = NULL_TREE);
>   extern bool* lookup_subsumption_result          (tree, tree);
>   extern bool save_subsumption_result             (tree, tree, bool);
>   extern tree find_template_parameters		(tree, tree);
> diff --git a/gcc/cp/cvt.c b/gcc/cp/cvt.c
> index e809f0e4068..3f5467c8283 100644
> --- a/gcc/cp/cvt.c
> +++ b/gcc/cp/cvt.c
> @@ -1170,7 +1170,7 @@ convert_to_void (tree expr, impl_conv_void implicit, tsubst_flags_t complain)
>     /* Explicitly evaluate void-converted concept checks since their
>        satisfaction may produce ill-formed programs.  */
>      if (concept_check_p (expr))
> -     expr = evaluate_concept_check (expr, tf_warning_or_error);
> +     expr = evaluate_concept_check (expr);
>   
>     if (VOID_TYPE_P (TREE_TYPE (expr)))
>       return expr;
> 


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 3/4] c++: Delay normalizing nested requirements until satisfaction
  2021-03-01 22:23           ` Jason Merrill
@ 2021-03-01 23:09             ` Patrick Palka
  2021-03-02  2:26               ` Jason Merrill
  0 siblings, 1 reply; 29+ messages in thread
From: Patrick Palka @ 2021-03-01 23:09 UTC (permalink / raw)
  To: Jason Merrill; +Cc: Patrick Palka, gcc-patches

On Mon, 1 Mar 2021, Jason Merrill wrote:

> On 2/28/21 12:40 PM, Patrick Palka wrote:
> > On Fri, 12 Feb 2021, Jason Merrill wrote:
> > 
> > > On 2/10/21 9:41 AM, Patrick Palka wrote:
> > > > On Tue, 9 Feb 2021, Jason Merrill wrote:
> > > > 
> > > > > On 2/8/21 2:03 PM, Patrick Palka wrote:
> > > > > > This sets up the functionality for controlling the initial set of
> > > > > > template parameters to pass to normalization when dealing with a
> > > > > > constraint-expression that is not associated with some constrained
> > > > > > declaration, for instance when normalizing a nested requirement of a
> > > > > > requires expression, or the constraints on a placeholder type.
> > > > > > 
> > > > > > The main new ingredient here is the data member
> > > > > > norm_info::initial_parms
> > > > > > which can be set by callers of the normalization routines to
> > > > > > communicate
> > > > > > the in-scope template parameters for the supplied
> > > > > > constraint-expression,
> > > > > > rather than always falling back to using current_template_parms.
> > > > > > 
> > > > > > This patch then uses this functionality in our handling of nested
> > > > > > requirements so that we can delay normalizing them until needed for
> > > > > > satisfaction.  We currently immediately normalize nested
> > > > > > requirements at
> > > > > > parse time, where we have the necessary template context, and cache
> > > > > > the
> > > > > > normal form in their TREE_TYPE node.  With this patch, we now delay
> > > > > > normalization until needed (as with other constraint expressions),
> > > > > > and
> > > > > > instead store the current value of current_template_parms in their
> > > > > > TREE_TYPE node (which we use to restore the template context at
> > > > > > normalization time).
> > > > > > 
> > > > > > In the subsequent patch, this functionality will also be used to
> > > > > > normalize placeholder type constraints during auto deduction.
> > > > > > 
> > > > > > gcc/cp/ChangeLog:
> > > > > > 
> > > > > > 	* constraint.cc (build_parameter_mapping): Rely on the caller
> > > > > > to
> > > > > > 	determine the in-scope template parameters.
> > > > > > 	(norm_info::norm_info): Delegate the one-parameter constructor
> > > > > > 	to the two-parameter constructor.  In the two-parameter
> > > > > > 	constructor, fold in the definition of make_context, set
> > > > > > 	initial_parms appropriately, and don't set the now-removed
> > > > > > 	orig_decl member.
> > > > > > 	(norm_info::make_context): Remove, now that its only use is
> > > > > > 	inlined into the caller.
> > > > > > 	(norm_info::update_context): Adjust call to
> > > > > > 	build_parameter_mapping to pass in the relevant set of
> > > > > > in-scope
> > > > > > 	template parameters.
> > > > > > 	(norm_info::ctx_parms): Define this member function.
> > > > > > 	(norm_info::context): Initialize to NULL_TREE.
> > > > > > 	(norm_info::orig_decl): Remove this data member.
> > > > > > 	(norm_info::initial_parms): Define this data member.
> > > > > > 	(normalize_atom): Adjust call to build_parameter_mapping to
> > > > > > pass
> > > > > > 	in the relevant set of in-scope template parameters.  Use
> > > > > > 	info.initial_parms instead of info.orig_decl.
> > > > > > 	(normalize_constraint_expression): Define an overload that
> > > > > > takes
> > > > > > 	a norm_info object.  Cache the result of normalization.
> > > > > > Define
> > > > > > 	the other overload in terms of this one, and handle a
> > > > > > NESTED_REQ
> > > > > > 	argument by setting info.initial_parms appropriately.
> > > > > > 	(tsubst_nested_requirement): Go through
> > > > > > 	satisfy_constraint_expression so that we normalize on demand.
> > > > > > 	(finish_nested_requirement): Set the TREE_TYPE of the
> > > > > > NESTED_REQ
> > > > > > 	to current_template_parms.
> > > > > > 	(diagnose_nested_requirements): Go through
> > > > > > 	satisfy_constraint_expression, as with
> > > > > > tsubst_nested_requirement.
> > > > > > ---
> > > > > >     gcc/cp/constraint.cc | 140
> > > > > > +++++++++++++++++++++++--------------------
> > > > > >     gcc/cp/cp-tree.h     |   4 +-
> > > > > >     2 files changed, 78 insertions(+), 66 deletions(-)
> > > > > > 
> > > > > > diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> > > > > > index 39c97986082..56134f8b2bf 100644
> > > > > > --- a/gcc/cp/constraint.cc
> > > > > > +++ b/gcc/cp/constraint.cc
> > > > > > @@ -133,7 +133,7 @@ struct sat_info : subst_info
> > > > > >       bool diagnose_unsatisfaction;
> > > > > >     };
> > > > > >     -static tree satisfy_constraint (tree, tree, sat_info);
> > > > > > +static tree satisfy_constraint_expression (tree, tree, sat_info);
> > > > > >       /* True if T is known to be some type other than bool. Note
> > > > > > that
> > > > > > this
> > > > > >        is false for dependent types and errors.  */
> > > > > > @@ -594,26 +594,12 @@ map_arguments (tree parms, tree args)
> > > > > >       return parms;
> > > > > >     }
> > > > > >     -/* Build the parameter mapping for EXPR using ARGS.  */
> > > > > > +/* Build the parameter mapping for EXPR using ARGS, where CTX_PARMS
> > > > > > +   are the template parameters in scope for EXPR.  */
> > > > > >       static tree
> > > > > > -build_parameter_mapping (tree expr, tree args, tree decl)
> > > > > > +build_parameter_mapping (tree expr, tree args, tree ctx_parms)
> > > > > >     {
> > > > > > -  tree ctx_parms = NULL_TREE;
> > > > > > -  if (decl)
> > > > > > -    {
> > > > > > -      gcc_assert (TREE_CODE (decl) == TEMPLATE_DECL);
> > > > > > -      ctx_parms = DECL_TEMPLATE_PARMS (decl);
> > > > > > -    }
> > > > > > -  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.
> > > > > > */
> > > > > > -      ctx_parms = current_template_parms;
> > > > > > -    }
> > > > > > -
> > > > > >       tree parms = find_template_parameters (expr, ctx_parms);
> > > > > >       tree map = map_arguments (parms, args);
> > > > > >       return map;
> > > > > > @@ -645,53 +631,63 @@ parameter_mapping_equivalent_p (tree t1, tree
> > > > > > t2)
> > > > > >       struct norm_info : subst_info
> > > > > >     {
> > > > > > -  explicit norm_info (tsubst_flags_t complain)
> > > > > > -    : subst_info (tf_warning_or_error | complain, NULL_TREE),
> > > > > > -      context()
> > > > > > +  explicit norm_info (tsubst_flags_t cmp)
> > > > > > +    : norm_info (NULL_TREE, cmp)
> > > > > >       {}
> > > > > >         /* Construct a top-level context for DECL.  */
> > > > > >         norm_info (tree in_decl, tsubst_flags_t complain)
> > > > > > -    : subst_info (tf_warning_or_error | complain, in_decl),
> > > > > > -      context (make_context (in_decl)),
> > > > > > -      orig_decl (in_decl)
> > > > > > -  {}
> > > > > > -
> > > > > > -  bool generate_diagnostics() const
> > > > > > +    : subst_info (tf_warning_or_error | complain, in_decl)
> > > > > >       {
> > > > > > -    return complain & tf_norm;
> > > > > > +    if (in_decl)
> > > > > > +      {
> > > > > > +	initial_parms = DECL_TEMPLATE_PARMS (in_decl);
> > > > > > +	if (generate_diagnostics ())
> > > > > > +	  context = build_tree_list (NULL_TREE, in_decl);
> > > > > > +      }
> > > > > > +    else
> > > > > > +      initial_parms = current_template_parms;
> > > > > >       }
> > > > > >     -  tree make_context(tree in_decl)
> > > > > > +  bool generate_diagnostics() const
> > > > > >       {
> > > > > > -    if (generate_diagnostics ())
> > > > > > -      return build_tree_list (NULL_TREE, in_decl);
> > > > > > -    return NULL_TREE;
> > > > > > +    return complain & tf_norm;
> > > > > >       }
> > > > > >         void update_context(tree expr, tree args)
> > > > > >       {
> > > > > >         if (generate_diagnostics ())
> > > > > >           {
> > > > > > -	tree map = build_parameter_mapping (expr, args, in_decl);
> > > > > > +	tree map = build_parameter_mapping (expr, args, ctx_parms ());
> > > > > >     	context = tree_cons (map, expr, context);
> > > > > >           }
> > > > > >         in_decl = get_concept_check_template (expr);
> > > > > >       }
> > > > > >     +  /* Returns the template parameters that are in scope for the
> > > > > > current
> > > > > > +     normalization context.  */
> > > > > > +
> > > > > > +  tree ctx_parms()
> > > > > > +  {
> > > > > > +    if (in_decl)
> > > > > > +      return DECL_TEMPLATE_PARMS (in_decl);
> > > > > > +    else
> > > > > > +      return initial_parms;
> > > > > > +  }
> > > > > > +
> > > > > >       /* Provides information about the source of a constraint. This
> > > > > > is a
> > > > > >          TREE_LIST whose VALUE is either a concept check or a
> > > > > > constrained
> > > > > >          declaration. The PURPOSE, for concept checks is a parameter
> > > > > > mapping
> > > > > >          for that check.  */
> > > > > >     -  tree context;
> > > > > > +  tree context = NULL_TREE;
> > > > > >         /* The declaration whose constraints we're normalizing.  The
> > > > > > targets
> > > > > >          of the parameter mapping of each atom will be in terms of
> > > > > > the
> > > > > >          template parameters of ORIG_DECL.  */
> > > > > >     -  tree orig_decl = NULL_TREE;
> > > > > > +  tree initial_parms = NULL_TREE;
> > > > > >     };
> > > > > >       static tree normalize_expression (tree, tree, norm_info);
> > > > > > @@ -773,7 +769,7 @@ normalize_atom (tree t, tree args, norm_info
> > > > > > info)
> > > > > >         return normalize_concept_check (t, args, info);
> > > > > >         /* Build the parameter mapping for the atom.  */
> > > > > > -  tree map = build_parameter_mapping (t, args, info.in_decl);
> > > > > > +  tree map = build_parameter_mapping (t, args, info.ctx_parms ());
> > > > > >         /* Build a new info object for the atom.  */
> > > > > >       tree ci = build_tree_list (t, info.context);
> > > > > > @@ -803,10 +799,8 @@ normalize_atom (tree t, tree args, norm_info
> > > > > > info)
> > > > > >     	      tree target = TREE_PURPOSE (node);
> > > > > >     	      TREE_VEC_ELT (targets, i++) = target;
> > > > > >     	    }
> > > > > > -	  tree ctx_parms = (info.orig_decl
> > > > > > -			    ? DECL_TEMPLATE_PARMS (info.orig_decl)
> > > > > > -			    : current_template_parms);
> > > > > > -	  tree target_parms = find_template_parameters (targets,
> > > > > > ctx_parms);
> > > > > > +	  tree target_parms = find_template_parameters (targets,
> > > > > > +
> > > > > > info.initial_parms);
> > > > > >     	  TREE_TYPE (map) = target_parms;
> > > > > >     	}
> > > > > >     @@ -983,17 +977,43 @@ normalize_nontemplate_requirements (tree
> > > > > > decl,
> > > > > > bool
> > > > > > diag = false)
> > > > > >     /* Normalize an EXPR as a constraint.  */
> > > > > >       static tree
> > > > > > -normalize_constraint_expression (tree expr, bool diag)
> > > > > > +normalize_constraint_expression (tree expr, norm_info info)
> > > > > >     {
> > > > > >       if (!expr || expr == error_mark_node)
> > > > > >         return expr;
> > > > > > +
> > > > > > +  if (!info.generate_diagnostics ())
> > > > > > +    if (tree *p = hash_map_safe_get (normalized_map, expr))
> > > > > > +      return *p;
> > > > > 
> > > > > It seems like we only want this for NESTED_REQ.
> > > > 
> > > > I figured it'd also be beneficial to cache the normal form of a
> > > > placeholder type constraint, which will also goes through this overload.
> > > 
> > > True.  And if we change REQUIRES_EXPR handling to not go through here, it
> > > should be just the two.  OK, let's leave this alone.
> > > 
> > > > > >       ++processing_template_decl;
> > > > > > -  norm_info info (diag ? tf_norm : tf_none);
> > > > > >       tree norm = get_normalized_constraints (expr, info);
> > > > > >       --processing_template_decl;
> > > > > > +
> > > > > > +  if (!info.generate_diagnostics ())
> > > > > > +    hash_map_safe_put<hm_ggc> (normalized_map, expr, norm);
> > > > > > +
> > > > > >       return norm;
> > > > > >     }
> > > > > >     +/* High-level wrapper for the above.  */
> > > > > > +
> > > > > > +static tree
> > > > > > +normalize_constraint_expression (tree expr, bool diag)
> > > > > > +{
> > > > > > +  norm_info info (diag ? tf_norm : tf_none);
> > > > > 
> > > > > I wonder if we want to add a norm_info constructor taking a sat_info
> > > > > so we
> > > > > don't need to mediate passing from one into the other with bool "diag"
> > > > > parameters in various functions.  That doesn't need to happen in this
> > > > > patch.
> > > > 
> > > > Sounds good.  I think such a constructor would let us eliminate the
> > > > bool-taking overload of normalize_constraint_expression altogether, if
> > > > we move the special handling of NESTED_REQ to the norm_info-taking
> > > > overload or to satisfy_constraint_expression.
> > 
> > Here's v2 of this patch, which refrains from adding a second overload of
> > normalize_constraint_expression.  Handling of NESTED_REQs now happens in
> > satisfy_constraint_expression.
> > 
> > The rest of the cleanups mentioned here will be performed in another
> > patch.
> > 
> > (For now, I didn't add a norm_info(sat_info) constructor since it wasn't
> > clear to me if this constructor should propagate in_decl or not.  For >
> > sat_info, in_decl is used purely for diagnostics, whereas for norm_info,
> > in_decl keeps track of the current normalization context, so carrying
> > over the value of in_decl might not always make sense.
> 
> > Perhaps it might be better to add a norm_info(bool diag) constructor
> > instead?
> 
> I was thinking the norm_info(sat_info) constructor would do the equivalent of
> 
> +      norm_info ninfo (info.noisy () ? tf_norm : tf_none);

Ah, sounds good.  Shall I do that (and the tf_norm removal) in a
followup patch, or update this one?

> 
> > While we're at it, I think we could remove the tf_norm flag.)
> 
> Sure.
> 
> > -- >8 --
> > 
> > Subject: [PATCH 3/6] c++: Delay normalizing nested requirements until
> >   satisfaction
> > 
> > This sets up the functionality for controlling the initial set of
> > template parameters to pass to normalization when dealing with a
> > constraint-expression that is not associated with some constrained
> > declaration, for instance when normalizing a nested requirement of a
> > requires expression, or the constraints on a placeholder type.
> > 
> > The main new ingredient here is the data member norm_info::initial_parms
> > which can be set by callers of the normalization routines to communicate
> > the in-scope template parameters for the supplied constraint-expression,
> > rather than always falling back to using current_template_parms.
> > 
> > This patch then uses this functionality in our handling of nested
> > requirements so that we can delay normalizing them until needed for
> > satisfaction.  We currently immediately normalize nested requirements at
> > parse time, where we have the necessary template context, and cache the
> > normal form in their TREE_TYPE node.  With this patch, we now delay
> > normalization until needed (as with other constraint expressions), and
> > instead store the current value of current_template_parms in their
> > TREE_TYPE node (which we use to restore the template context at
> > normalization time).
> > 
> > In the subsequent patch, this functionality will also be used to
> > normalize placeholder type constraints during auto deduction.
> > 
> > gcc/cp/ChangeLog:
> > 
> > 	* constraint.cc (build_parameter_mapping): Rely on the caller to
> > 	determine the in-scope template parameters.
> > 	(norm_info::norm_info): Delegate the tsubst_flags_t constructor
> > 	to the two-parameter constructor.  In the two-parameter
> > 	constructor, fold in the definition of make_context, set
> > 	initial_parms appropriately, and don't set the now-removed
> > 	orig_decl member.
> > 	(norm_info::make_context): Remove, now that its only use is
> > 	inlined into the caller.
> > 	(norm_info::update_context): Adjust call to
> > 	build_parameter_mapping to pass in the relevant set of in-scope
> > 	template parameters.
> > 	(norm_info::ctx_parms): Define this member function.
> > 	(norm_info::context): Initialize to NULL_TREE.
> > 	(norm_info::orig_decl): Remove this data member.
> > 	(norm_info::initial_parms): Define this data member.
> > 	(normalize_atom): Adjust call to build_parameter_mapping to pass
> > 	in the relevant set of in-scope template parameters.  Use
> > 	info.initial_parms instead of info.orig_decl.
> > 	(normalize_constraint_expression): Take a norm_info object
> > 	instead of a bool.  Cache the result of normalization.
> > 	(tsubst_nested_requirement): Call satisfy_constraint_expression
> > 	instead of satisfy_constraint, so that we normalize on demand.
> > 	(satisfy_constraint_expression): Handle a NESTED_REQ argument.
> > 	Adjust call to normalize_constraint_expression.
> > 	(finish_nested_requirement): Set the TREE_TYPE of the NESTED_REQ
> > 	to current_template_parms.
> > 	(diagnose_nested_requirements): Go through
> > 	satisfy_constraint_expression, as with tsubst_nested_requirement.
> > ---
> >   gcc/cp/constraint.cc | 138 ++++++++++++++++++++++---------------------
> >   1 file changed, 72 insertions(+), 66 deletions(-)
> > 
> > diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> > index 39c97986082..fcb249a642f 100644
> > --- a/gcc/cp/constraint.cc
> > +++ b/gcc/cp/constraint.cc
> > @@ -133,7 +133,7 @@ struct sat_info : subst_info
> >     bool diagnose_unsatisfaction;
> >   };
> >   -static tree satisfy_constraint (tree, tree, sat_info);
> > +static tree satisfy_constraint_expression (tree, tree, sat_info);
> >     /* True if T is known to be some type other than bool. Note that this
> >      is false for dependent types and errors.  */
> > @@ -594,26 +594,12 @@ map_arguments (tree parms, tree args)
> >     return parms;
> >   }
> >   -/* Build the parameter mapping for EXPR using ARGS.  */
> > +/* Build the parameter mapping for EXPR using ARGS, where CTX_PARMS
> > +   are the template parameters in scope for EXPR.  */
> >     static tree
> > -build_parameter_mapping (tree expr, tree args, tree decl)
> > +build_parameter_mapping (tree expr, tree args, tree ctx_parms)
> >   {
> > -  tree ctx_parms = NULL_TREE;
> > -  if (decl)
> > -    {
> > -      gcc_assert (TREE_CODE (decl) == TEMPLATE_DECL);
> > -      ctx_parms = DECL_TEMPLATE_PARMS (decl);
> > -    }
> > -  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.  */
> > -      ctx_parms = current_template_parms;
> > -    }
> > -
> >     tree parms = find_template_parameters (expr, ctx_parms);
> >     tree map = map_arguments (parms, args);
> >     return map;
> > @@ -645,53 +631,63 @@ parameter_mapping_equivalent_p (tree t1, tree t2)
> >     struct norm_info : subst_info
> >   {
> > -  explicit norm_info (tsubst_flags_t complain)
> > -    : subst_info (tf_warning_or_error | complain, NULL_TREE),
> > -      context()
> > +  explicit norm_info (tsubst_flags_t cmp)
> > +    : norm_info (NULL_TREE, cmp)
> >     {}
> >       /* Construct a top-level context for DECL.  */
> >       norm_info (tree in_decl, tsubst_flags_t complain)
> > -    : subst_info (tf_warning_or_error | complain, in_decl),
> > -      context (make_context (in_decl)),
> > -      orig_decl (in_decl)
> > -  {}
> > -
> > -  bool generate_diagnostics() const
> > +    : subst_info (tf_warning_or_error | complain, in_decl)
> >     {
> > -    return complain & tf_norm;
> > +    if (in_decl)
> > +      {
> > +	initial_parms = DECL_TEMPLATE_PARMS (in_decl);
> > +	if (generate_diagnostics ())
> > +	  context = build_tree_list (NULL_TREE, in_decl);
> > +      }
> > +    else
> > +      initial_parms = current_template_parms;
> >     }
> >   -  tree make_context(tree in_decl)
> > +  bool generate_diagnostics() const
> >     {
> > -    if (generate_diagnostics ())
> > -      return build_tree_list (NULL_TREE, in_decl);
> > -    return NULL_TREE;
> > +    return complain & tf_norm;
> >     }
> >       void update_context(tree expr, tree args)
> >     {
> >       if (generate_diagnostics ())
> >         {
> > -	tree map = build_parameter_mapping (expr, args, in_decl);
> > +	tree map = build_parameter_mapping (expr, args, ctx_parms ());
> >   	context = tree_cons (map, expr, context);
> >         }
> >       in_decl = get_concept_check_template (expr);
> >     }
> >   +  /* Returns the template parameters that are in scope for the current
> > +     normalization context.  */
> > +
> > +  tree ctx_parms()
> > +  {
> > +    if (in_decl)
> > +      return DECL_TEMPLATE_PARMS (in_decl);
> > +    else
> > +      return initial_parms;
> > +  }
> 
> Why prefer in_decl to initial_parms?  In fact, why look at in_decl here at
> all, when we already used it to set initial_parms in the constructor?

IIUC, that's true at the start of normalization, but in_decl gets
continuously updated as we recurse into a concept-id and normalize the
concept's definition (in normalize_concept_check via
norm_info::update_context), whereas initial_parms gets set at the start
of normalization (upon construction) and remains unchanged.

> 
> >     /* Provides information about the source of a constraint. This is a
> >        TREE_LIST whose VALUE is either a concept check or a constrained
> >        declaration. The PURPOSE, for concept checks is a parameter mapping
> >        for that check.  */
> >   -  tree context;
> > +  tree context = NULL_TREE;
> >       /* The declaration whose constraints we're normalizing.  The targets
> >        of the parameter mapping of each atom will be in terms of the
> >        template parameters of ORIG_DECL.  */
> >   -  tree orig_decl = NULL_TREE;
> > +  tree initial_parms = NULL_TREE;
> >   };
> >     static tree normalize_expression (tree, tree, norm_info);
> > @@ -773,7 +769,7 @@ normalize_atom (tree t, tree args, norm_info info)
> >       return normalize_concept_check (t, args, info);
> >       /* Build the parameter mapping for the atom.  */
> > -  tree map = build_parameter_mapping (t, args, info.in_decl);
> > +  tree map = build_parameter_mapping (t, args, info.ctx_parms ());
> >       /* Build a new info object for the atom.  */
> >     tree ci = build_tree_list (t, info.context);
> > @@ -803,10 +799,8 @@ normalize_atom (tree t, tree args, norm_info info)
> >   	      tree target = TREE_PURPOSE (node);
> >   	      TREE_VEC_ELT (targets, i++) = target;
> >   	    }
> > -	  tree ctx_parms = (info.orig_decl
> > -			    ? DECL_TEMPLATE_PARMS (info.orig_decl)
> > -			    : current_template_parms);
> > -	  tree target_parms = find_template_parameters (targets, ctx_parms);
> > +	  tree target_parms = find_template_parameters (targets,
> > +							info.initial_parms);
> >   	  TREE_TYPE (map) = target_parms;
> >   	}
> >   @@ -983,14 +977,22 @@ normalize_nontemplate_requirements (tree decl, bool
> > diag = false)
> >   /* Normalize an EXPR as a constraint.  */
> >     static tree
> > -normalize_constraint_expression (tree expr, bool diag)
> > +normalize_constraint_expression (tree expr, norm_info info)
> >   {
> >     if (!expr || expr == error_mark_node)
> >       return expr;
> > +
> > +  if (!info.generate_diagnostics ())
> > +    if (tree *p = hash_map_safe_get (normalized_map, expr))
> > +      return *p;
> > +
> >     ++processing_template_decl;
> > -  norm_info info (diag ? tf_norm : tf_none);
> >     tree norm = get_normalized_constraints (expr, info);
> >     --processing_template_decl;
> > +
> > +  if (!info.generate_diagnostics ())
> > +    hash_map_safe_put<hm_ggc> (normalized_map, expr, norm);
> > +
> >     return norm;
> >   }
> >   @@ -2086,16 +2088,14 @@ tsubst_compound_requirement (tree t, tree args,
> > subst_info info)
> >   static tree
> >   tsubst_nested_requirement (tree t, tree args, subst_info info)
> >   {
> > -  /* Perform satisfaction quietly with the regular normal form.  */
> > +  /* Perform satisfaction quietly first.  */
> >     sat_info quiet (tf_none, info.in_decl);
> > -  tree norm = TREE_VALUE (TREE_TYPE (t));
> > -  tree diag_norm = TREE_PURPOSE (TREE_TYPE (t));
> > -  tree result = satisfy_constraint (norm, args, quiet);
> > +  tree result = satisfy_constraint_expression (t, args, quiet);
> >     if (result == error_mark_node)
> >       {
> > -      /* Replay the error using the diagnostic normal form.  */
> > +      /* Replay the error.  */
> >         sat_info noisy (tf_warning_or_error, info.in_decl);
> > -      satisfy_constraint (diag_norm, args, noisy);
> > +      satisfy_constraint_expression (t, args, noisy);
> >       }
> >     if (result != boolean_true_node)
> >       return error_mark_node;
> > @@ -3040,8 +3040,22 @@ satisfy_constraint_expression (tree t, tree args,
> > sat_info info)
> >         tree tmpl = get_concept_check_template (id);
> >         norm = normalize_concept_definition (tmpl, info.noisy ());
> >       }
> > +  else if (TREE_CODE (t) == NESTED_REQ)
> > +    {
> > +      norm_info ninfo (info.noisy () ? tf_norm : tf_none);
> > +      /* The TREE_TYPE contains the set of template parameters that were in
> > +	 scope for this nested requirement; use them as the initial template
> > +	 parameters for normalization.  */
> > +      ninfo.initial_parms = TREE_TYPE (t);
> > +      norm = normalize_constraint_expression (TREE_OPERAND (t, 0), ninfo);
> > +    }
> > +  else if (EXPR_P (t))
> > +    {
> > +      norm_info ninfo (info.noisy () ? tf_norm : tf_none);
> > +      norm = normalize_constraint_expression (t, ninfo);
> > +    }
> >     else
> > -    norm = normalize_constraint_expression (t, info.noisy ());
> > +    gcc_unreachable ();
> >       /* Perform satisfaction.  */
> >     return satisfy_constraint (norm, args, info);
> > @@ -3301,15 +3315,9 @@ finish_compound_requirement (location_t loc, tree
> > expr, tree type, bool noexcept
> >   tree
> >   finish_nested_requirement (location_t loc, tree expr)
> >   {
> > -  /* We need to normalize the constraints now, at parse time, while
> > -     we have the necessary template context.  We normalize twice,
> > -     once without diagnostic information and once with, which we'll
> > -     later use for quiet and noisy satisfaction respectively.  */
> > -  tree norm = normalize_constraint_expression (expr, /*diag=*/false);
> > -  tree diag_norm = normalize_constraint_expression (expr, /*diag=*/true);
> > -
> > -  /* Build the constraint, saving its two normalizations as its type.  */
> > -  tree r = build1 (NESTED_REQ, build_tree_list (diag_norm, norm), expr);
> > +  /* Build the requirement, saving the set of in-scope template
> > +     parameters as its type.  */
> > +  tree r = build1 (NESTED_REQ, current_template_parms, expr);
> >     SET_EXPR_LOCATION (r, loc);
> >     return r;
> >   }
> > @@ -3710,12 +3718,9 @@ diagnose_type_requirement (tree req, tree args, tree
> > in_decl)
> >   static void
> >   diagnose_nested_requirement (tree req, tree args)
> >   {
> > -  /* Quietly check for satisfaction first using the regular normal form.
> > -     We can elaborate details later if needed.  */
> > -  tree norm = TREE_VALUE (TREE_TYPE (req));
> > -  tree diag_norm = TREE_PURPOSE (TREE_TYPE (req));
> > -  sat_info info (tf_none, NULL_TREE);
> > -  tree result = satisfy_constraint (norm, args, info);
> > +  /* Quietly check for satisfaction first.  */
> > +  sat_info quiet (tf_none, NULL_TREE);
> > +  tree result = satisfy_constraint_expression (req, args, quiet);
> >     if (result == boolean_true_node)
> >       return;
> >   @@ -3723,10 +3728,11 @@ diagnose_nested_requirement (tree req, tree args)
> >     location_t loc = cp_expr_location (expr);
> >     if (diagnosing_failed_constraint::replay_errors_p ())
> >       {
> > -      /* Replay the substitution error using the diagnostic normal form.
> > */
> > +      /* Replay the substitution error with re-normalized requirements.  */
> >         inform (loc, "nested requirement %qE is not satisfied, because",
> > expr);
> > +
> >         sat_info noisy (tf_warning_or_error, NULL_TREE,
> > /*diag_unsat=*/true);
> > -      satisfy_constraint (diag_norm, args, noisy);
> > +      satisfy_constraint_expression (req, args, noisy);
> >       }
> >     else
> >       inform (loc, "nested requirement %qE is not satisfied", expr);
> > 
> 
> 


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 6/6] c++: Consolidate REQUIRES_EXPR evaluation/diagnostic routines
  2021-02-28 17:59 ` [PATCH 6/6] c++: Consolidate REQUIRES_EXPR evaluation/diagnostic routines Patrick Palka
@ 2021-03-02  2:18   ` Jason Merrill
  2021-03-02 16:45     ` Patrick Palka
  0 siblings, 1 reply; 29+ messages in thread
From: Jason Merrill @ 2021-03-02  2:18 UTC (permalink / raw)
  To: Patrick Palka, gcc-patches

On 2/28/21 12:59 PM, Patrick Palka wrote:
> This folds the diagnose_requires_expr routines into the corresponding
> tsubst_requires_expr ones.  This is achieved by making the latter
> routines take a sat_info instead of a subst_info, and assigning the
> appropriate meanings to the flags sat_info::noisy and
> sat_info::diagnose_unsatisfaction_p during tsubst_requires_expr:
> info.noisy() controls whether to diagnose invalid types and expressions
> inside the requires-expression, and info.diagnose_unsatisfaction_p()
> controls whether to diagnose why the requires-expression evaluates to
> false.
> 
> gcc/cp/ChangeLog:
> 
> 	* constraint.cc (struct sat_info): Document the different
> 	meanings of noisy() and diagnose_unsatisfaction_p() during
> 	satisfaction and requires-expression evaluation.
> 	(tsubst_valid_expression_requirement): Take a sat_info instead
> 	of a subst_info.  Perform the substitution quietly first.  Fold
> 	in error-replaying code from diagnose_valid_expression.
> 	(tsubst_simple_requirement): Take a sat_info instead of a
> 	subst_info.
> 	(tsubst_type_requirement_1): New.  Fold in error-replaying code
> 	from diagnose_valid_type.
> 	(tsubst_type_requirement): Use the above.  Take a sat_info
> 	instead of a subst_info.
> 	(tsubst_compound_requirement): Likewise.  Fold in
> 	error-replaying code from diagnose_compound_requirement.
> 	(tsubst_nested_requirement): Take a sat_info instead of a
> 	subst_info.  Fold in error-replaying code from
> 	diagnose_nested_requirement.
> 	(tsubst_requirement): Take a sat_info instead of a subst_info.
> 	(tsubst_requires_expr): Split into two versions, one that takes
> 	a sat_info argument and another that takes a complain and
> 	in_decl argument.  Remove outdated documentation.  Document the
> 	effects of the sat_info argument.
> 	(diagnose_trait_expr): Make static.  Take a template argument
> 	vector instead of a parameter mapping.
> 	(diagnose_valid_expression): Remove.
> 	(diagnose_valid_type): Remove.
> 	(diagnose_simple_requirement): Remove.
> 	(diagnose_compound_requirement): Remove.
> 	(diagnose_type_requirement): Remove.
> 	(diagnose_nested_requirement): Remove.
> 	(diagnose_requirement): Remove.
> 	(diagnose_requires_expr): Remove.
> 	(diagnose_atomic_constraint): Take a sat_info instead of a
> 	subst_info.  Adjust call to diagnose_trait_expr.  Call
> 	tsubst_requires_expr instead of diagnose_requires_expr.
> 	(diagnose_constraints): Call tsubst_requires_expr instead of
> 	diagnose_requires_expr.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/concepts/diagnostic1.C: Adjust expected diagnostics
> 	now that we diagnose only the first failed requirement of a
> 	requires-expression.
> ---
>   gcc/cp/constraint.cc                        | 416 +++++++++-----------
>   gcc/testsuite/g++.dg/concepts/diagnostic1.C |   2 +-
>   2 files changed, 179 insertions(+), 239 deletions(-)
> 
> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> index cf319b34da0..31f32c25dfe 100644
> --- a/gcc/cp/constraint.cc
> +++ b/gcc/cp/constraint.cc
> @@ -100,17 +100,30 @@ struct subst_info
>   
>   /* Provides additional context for satisfaction.
>   
> -   The flag noisy() controls whether to diagnose ill-formed satisfaction,
> -   such as the satisfaction value of an atom being non-bool or non-constant.
> -
> -   The flag diagnose_unsatisfaction_p() controls whether to explain why
> -   a constraint is not satisfied.
> -
> -   The entrypoints to satisfaction for which we set noisy+unsat are
> -   diagnose_constraints and diagnose_nested_requirement.  The entrypoint for
> -   which we set noisy-unsat is the replay inside constraint_satisfaction_value.
> -   From constraints_satisfied_p, we enter satisfaction quietly (both flags
> -   cleared).  */
> +   During satisfaction:
> +    - The flag noisy() controls whether to diagnose ill-formed satisfaction,
> +      such as the satisfaction value of an atom being non-bool or non-constant.
> +    - The flag diagnose_unsatisfaction_p() controls whether to explain why
> +      a constraint is not satisfied.
> +    - We enter satisfaction with noisy+unsat from diagnose_constraints.
> +    - We enter satisfaction with noisy-unsat from the replay inside
> +      constraint_satisfaction_value.
> +    - We enter satisfaction quietly (both flags cleared) from
> +      constraints_satisfied_p.
> +
> +   During evaluation of a requires-expression:
> +    - The flag noisy() controls whether to diagnose ill-formed types and
> +      expressions inside its requirements.
> +    - The flag diagnose_unsatisfaction_p() controls whether to explain why
> +      the requires-expression evaluates to false.
> +    - We enter tsubst_requires_expr with noisy+unsat from diagnose_constraints
> +      and from diagnose_atomic_constraint.
> +    - We enter tsubst_requires_expr with noisy-unsat from
> +      cp_parser_requires_expression when processing a requires-expression that
> +      appears outside a template.
> +    - We enter tsubst_requires_expr quietly (both flags cleared) when
> +      substituting through a requires-expression as part of template
> +      instantiation.  */
>   
>   struct sat_info : subst_info
>   {
> @@ -1926,22 +1939,44 @@ hash_placeholder_constraint (tree c)
>     return val;
>   }
>   
> -/* Substitute through the simple requirement.  */
> +/* Substitute through the expression of a simple requirement or
> +   compound requirement.  */
>   
>   static tree
> -tsubst_valid_expression_requirement (tree t, tree args, subst_info info)
> +tsubst_valid_expression_requirement (tree t, tree args, sat_info info)
>   {
> -  tree r = tsubst_expr (t, args, info.complain, info.in_decl, false);
> -  if (convert_to_void (r, ICV_STATEMENT, info.complain) == error_mark_node)
> -    return error_mark_node;
> -  return r;
> +  tree r = tsubst_expr (t, args, tf_none, info.in_decl, false);
> +  if (convert_to_void (r, ICV_STATEMENT, tf_none) != error_mark_node)
> +    return r;
> +
> +  if (info.diagnose_unsatisfaction_p ())
> +    {
> +      location_t loc = cp_expr_loc_or_input_loc (t);
> +      if (diagnosing_failed_constraint::replay_errors_p ())
> +	{
> +	  inform (loc, "the required expression %qE is invalid, because", t);
> +	  if (r == error_mark_node)
> +	    tsubst_expr (t, args, info.complain, info.in_decl, false);
> +	  else
> +	    convert_to_void (r, ICV_STATEMENT, info.complain);
> +	}
> +      else
> +	inform (loc, "the required expression %qE is invalid", t);
> +    }
> +  else if (info.noisy ())
> +    {
> +      r = tsubst_expr (t, args, info.complain, info.in_decl, false);
> +      convert_to_void (r, ICV_STATEMENT, info.complain);
> +    }
> +
> +  return error_mark_node;
>   }
>   
>   
>   /* Substitute through the simple requirement.  */
>   
>   static tree
> -tsubst_simple_requirement (tree t, tree args, subst_info info)
> +tsubst_simple_requirement (tree t, tree args, sat_info info)
>   {
>     tree t0 = TREE_OPERAND (t, 0);
>     tree expr = tsubst_valid_expression_requirement (t0, args, info);
> @@ -1950,13 +1985,41 @@ tsubst_simple_requirement (tree t, tree args, subst_info info)
>     return boolean_true_node;
>   }
>   
> +/* Subroutine of tsubst_type_requirement that performs the actual substitution
> +   and diagnosing.  Also used by tsubst_compound_requirement.  */
> +
> +static tree
> +tsubst_type_requirement_1 (tree t, tree args, sat_info info, location_t loc)
> +{
> +  tree r = tsubst (t, args, tf_none, info.in_decl);
> +  if (r != error_mark_node)
> +    return r;
> +
> +  if (info.diagnose_unsatisfaction_p ())
> +    {
> +      if (diagnosing_failed_constraint::replay_errors_p ())
> +	{
> +	  /* Replay the substitution error.  */
> +	  inform (loc, "the required type %qT is invalid, because", t);
> +	  tsubst (t, args, info.complain, info.in_decl);
> +	}
> +      else
> +	inform (loc, "the required type %qT is invalid", t);
> +    }
> +  else if (info.noisy ())
> +    tsubst (t, args, info.complain, info.in_decl);
> +
> +  return error_mark_node;
> +}
> +
> +
>   /* Substitute through the type requirement.  */
>   
>   static tree
> -tsubst_type_requirement (tree t, tree args, subst_info info)
> +tsubst_type_requirement (tree t, tree args, sat_info info)
>   {
>     tree t0 = TREE_OPERAND (t, 0);
> -  tree type = tsubst (t0, args, info.complain, info.in_decl);
> +  tree type = tsubst_type_requirement_1 (t0, args, info, EXPR_LOCATION (t));
>     if (type == error_mark_node)
>       return error_mark_node;
>     return boolean_true_node;
> @@ -2013,7 +2076,7 @@ expression_convertible_p (tree expr, tree type, subst_info info)
>   /* Substitute through the compound requirement.  */
>   
>   static tree
> -tsubst_compound_requirement (tree t, tree args, subst_info info)
> +tsubst_compound_requirement (tree t, tree args, sat_info info)
>   {
>     tree t0 = TREE_OPERAND (t, 0);
>     tree t1 = TREE_OPERAND (t, 1);
> @@ -2021,13 +2084,20 @@ tsubst_compound_requirement (tree t, tree args, subst_info info)
>     if (expr == error_mark_node)
>       return error_mark_node;
>   
> +  location_t loc = cp_expr_loc_or_input_loc (expr);
> +
>     /* Check the noexcept condition.  */
>     bool noexcept_p = COMPOUND_REQ_NOEXCEPT_P (t);
>     if (noexcept_p && !expr_noexcept_p (expr, tf_none))
> -    return error_mark_node;
> +    {
> +      if (info.diagnose_unsatisfaction_p ())
> +	inform (loc, "%qE is not %<noexcept%>", expr);
> +      else
> +	return error_mark_node;
> +    }
>   
>     /* Substitute through the type expression, if any.  */
> -  tree type = tsubst (t1, args, info.complain, info.in_decl);
> +  tree type = tsubst_type_requirement_1 (t1, args, info, EXPR_LOCATION (t));
>     if (type == error_mark_node)
>       return error_mark_node;
>   
> @@ -2039,29 +2109,76 @@ tsubst_compound_requirement (tree t, tree args, subst_info info)
>         if (tree placeholder = type_uses_auto (type))
>   	{
>   	  if (!type_deducible_p (expr, type, placeholder, args, quiet))
> -	    return error_mark_node;
> +	    {
> +	      if (info.diagnose_unsatisfaction_p ())
> +		{
> +		  if (diagnosing_failed_constraint::replay_errors_p ())
> +		    {
> +		      inform (loc,
> +			      "%qE does not satisfy return-type-requirement, "
> +			      "because", t0);
> +		      /* Further explain the reason for the error.  */
> +		      type_deducible_p (expr, type, placeholder, args, info);
> +		    }
> +		  else
> +		    inform (loc,
> +			    "%qE does not satisfy return-type-requirement", t0);
> +		}
> +	      return error_mark_node;
> +	    }
>   	}
>         else if (!expression_convertible_p (expr, type, quiet))
> -	return error_mark_node;
> +	{
> +	  if (info.diagnose_unsatisfaction_p ())
> +	    {
> +	      if (diagnosing_failed_constraint::replay_errors_p ())
> +		{
> +		  inform (loc, "cannot convert %qE to %qT because", t0, type);
> +		  /* Further explain the reason for the error.  */
> +		  expression_convertible_p (expr, type, info);
> +		}
> +	      else
> +		inform (loc, "cannot convert %qE to %qT", t0, type);
> +	    }
> +	  return error_mark_node;
> +	}
>       }
>   
>     return boolean_true_node;
>   }
>   
> +/* Substitute through the nested requirement.  */
> +
>   static tree
> -tsubst_nested_requirement (tree t, tree args, subst_info info)
> +tsubst_nested_requirement (tree t, tree args, sat_info info)
>   {
>     sat_info quiet (tf_none, info.in_decl);
>     tree result = constraint_satisfaction_value (t, args, quiet);
> -  if (result != boolean_true_node)
> -    return error_mark_node;
> -  return boolean_true_node;
> +  if (result == boolean_true_node)
> +    return boolean_true_node;
> +
> +  if (result == boolean_false_node
> +      && info.diagnose_unsatisfaction_p ())
> +    {
> +      tree expr = TREE_OPERAND (t, 0);
> +      location_t loc = cp_expr_location (t);
> +      if (diagnosing_failed_constraint::replay_errors_p ())
> +	{
> +	  /* Replay the substitution error.  */
> +	  inform (loc, "nested requirement %qE is not satisfied, because", expr);
> +	  constraint_satisfaction_value (t, args, info);
> +	}
> +      else
> +	inform (loc, "nested requirement %qE is not satisfied", expr);
> +    }
> +
> +  return error_mark_node;
>   }
>   
>   /* Substitute ARGS into the requirement T.  */
>   
>   static tree
> -tsubst_requirement (tree t, tree args, subst_info info)
> +tsubst_requirement (tree t, tree args, sat_info info)
>   {
>     iloc_sentinel loc_s (cp_expr_location (t));
>     switch (TREE_CODE (t))
> @@ -2151,30 +2268,22 @@ tsubst_constraint_variables (tree t, tree args, subst_info info)
>      in its requirements ... In such cases, the expression evaluates
>      to false; it does not cause the program to be ill-formed.
>   
> -   However, there are cases where substitution must produce a
> -   new requires-expression, that is not a template constraint.
> -   For example:
> +   When substituting through a REQUIRES_EXPR as part of template
> +   instantiation, we call this routine with info.quiet() true.
>   
> -        template<typename T>
> -        class X {
> -          template<typename U>
> -          static constexpr bool var = requires (U u) { T::fn(u); };
> -        };
> +   When evaluating a REQUIRES_EXPR that appears outside a template in
> +   cp_parser_requires_expression, we call this routine with
> +   info.noisy() true.
>   
> -   In the instantiation of X<Y> (assuming Y defines fn), then the
> -   instantiated requires-expression would include Y::fn(u). If any
> -   substitution in the requires-expression fails, we can immediately
> -   fold the expression to false, as would be the case e.g., when
> -   instantiation X<int>.  */
> +   Finally, when diagnosing unsatisfaction from diagnose_atomic_constraint
> +   and when diagnosing a false REQUIRES_EXPR via diagnose_constraints,
> +   we call this routine with info.diagnose_unsatisfaction_p() true.  */
>   
> -tree
> -tsubst_requires_expr (tree t, tree args,
> -		      tsubst_flags_t complain, tree in_decl)
> +static tree
> +tsubst_requires_expr (tree t, tree args, sat_info info)
>   {
>     local_specialization_stack stack (lss_copy);
>   
> -  subst_info info (complain, in_decl);
> -
>     /* A requires-expression is an unevaluated context.  */
>     cp_unevaluated u;
>   
> @@ -2186,7 +2295,7 @@ tsubst_requires_expr (tree t, tree args,
>   	 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);
> +      REQUIRES_EXPR_EXTRA_ARGS (t) = build_extra_args (t, args, info.complain);
>         return t;
>       }
>   
> @@ -2207,6 +2316,16 @@ tsubst_requires_expr (tree t, tree args,
>     return boolean_true_node;
>   }
>   
> +/* Public wrapper for the above.  */
> +
> +tree
> +tsubst_requires_expr (tree t, tree args,
> +		      tsubst_flags_t complain, tree in_decl)
> +{
> +  sat_info info (complain, in_decl);
> +  return tsubst_requires_expr (t, args, info);
> +}
> +
>   /* Substitute ARGS into the constraint information CI, producing a new
>      constraint record.  */
>   
> @@ -2790,7 +2909,7 @@ get_mapped_args (tree map)
>     return args;
>   }
>   
> -static void diagnose_atomic_constraint (tree, tree, tree, subst_info);
> +static void diagnose_atomic_constraint (tree, tree, tree, sat_info);
>   
>   /* Compute the satisfaction of an atomic constraint.  */
>   
> @@ -3440,11 +3559,10 @@ get_constraint_error_location (tree t)
>   
>   /* Emit a diagnostic for a failed trait.  */
>   
> -void
> -diagnose_trait_expr (tree expr, tree map)
> +static void
> +diagnose_trait_expr (tree expr, tree args)
>   {
>     location_t loc = cp_expr_location (expr);
> -  tree args = get_mapped_args (map);
>   
>     /* Build a "fake" version of the instantiated trait, so we can
>        get the instantiated types from result.  */
> @@ -3524,192 +3642,11 @@ diagnose_trait_expr (tree expr, tree map)
>       }
>   }
>   
> -static tree
> -diagnose_valid_expression (tree expr, tree args, tree in_decl)
> -{
> -  tree result = tsubst_expr (expr, args, tf_none, in_decl, false);
> -  if (result != error_mark_node
> -      && convert_to_void (result, ICV_STATEMENT, tf_none) != error_mark_node)
> -    return result;
> -
> -  location_t loc = cp_expr_loc_or_input_loc (expr);
> -  if (diagnosing_failed_constraint::replay_errors_p ())
> -    {
> -      /* Replay the substitution error.  */
> -      inform (loc, "the required expression %qE is invalid, because", expr);
> -      if (result == error_mark_node)
> -	tsubst_expr (expr, args, tf_error, in_decl, false);
> -      else
> -	convert_to_void (result, ICV_STATEMENT, tf_error);
> -    }
> -  else
> -    inform (loc, "the required expression %qE is invalid", expr);
> -
> -  return error_mark_node;
> -}
> -
> -static tree
> -diagnose_valid_type (tree type, tree args, tree in_decl)
> -{
> -  tree result = tsubst (type, args, tf_none, in_decl);
> -  if (result != error_mark_node)
> -    return result;
> -
> -  location_t loc = cp_expr_loc_or_input_loc (type);
> -  if (diagnosing_failed_constraint::replay_errors_p ())
> -    {
> -      /* Replay the substitution error.  */
> -      inform (loc, "the required type %qT is invalid, because", type);
> -      tsubst (type, args, tf_error, in_decl);
> -    }
> -  else
> -    inform (loc, "the required type %qT is invalid", type);
> -
> -  return error_mark_node;
> -}
> -
> -static void
> -diagnose_simple_requirement (tree req, tree args, tree in_decl)
> -{
> -  diagnose_valid_expression (TREE_OPERAND (req, 0), args, in_decl);
> -}
> -
> -static void
> -diagnose_compound_requirement (tree req, tree args, tree in_decl)
> -{
> -  tree expr = TREE_OPERAND (req, 0);
> -  expr = diagnose_valid_expression (expr, args, in_decl);
> -  if (expr == error_mark_node)
> -    return;
> -
> -  location_t loc = cp_expr_loc_or_input_loc (expr);
> -
> -  /* Check the noexcept condition.  */
> -  if (COMPOUND_REQ_NOEXCEPT_P (req) && !expr_noexcept_p (expr, tf_none))
> -    inform (loc, "%qE is not %<noexcept%>", expr);
> -
> -  tree type = TREE_OPERAND (req, 1);
> -  type = diagnose_valid_type (type, args, in_decl);
> -  if (type == error_mark_node)
> -    return;
> -
> -  if (type)
> -    {
> -      subst_info quiet (tf_none, in_decl);
> -      subst_info noisy (tf_error, in_decl);
> -
> -      /* Check the expression against the result type.  */
> -      if (tree placeholder = type_uses_auto (type))
> -	{
> -	  if (!type_deducible_p (expr, type, placeholder, args, quiet))
> -	    {
> -	      tree orig_expr = TREE_OPERAND (req, 0);
> -	      if (diagnosing_failed_constraint::replay_errors_p ())
> -		{
> -		  inform (loc,
> -			  "%qE does not satisfy return-type-requirement, "
> -			  "because", orig_expr);
> -		  /* Further explain the reason for the error.  */
> -		  type_deducible_p (expr, type, placeholder, args, noisy);
> -		}
> -	      else
> -		inform (loc, "%qE does not satisfy return-type-requirement",
> -			orig_expr);
> -	    }
> -	}
> -      else if (!expression_convertible_p (expr, type, quiet))
> -	{
> -	  tree orig_expr = TREE_OPERAND (req, 0);
> -	  if (diagnosing_failed_constraint::replay_errors_p ())
> -	    {
> -	      inform (loc, "cannot convert %qE to %qT because", orig_expr, type);
> -	      /* Further explain the reason for the error.  */
> -	      expression_convertible_p (expr, type, noisy);
> -	    }
> -	  else
> -	    inform (loc, "cannot convert %qE to %qT", orig_expr, type);
> -	}
> -    }
> -}
> -
> -static void
> -diagnose_type_requirement (tree req, tree args, tree in_decl)
> -{
> -  tree type = TREE_OPERAND (req, 0);
> -  diagnose_valid_type (type, args, in_decl);
> -}
> -
> -static void
> -diagnose_nested_requirement (tree req, tree args)
> -{
> -  /* Quietly check for satisfaction first.  */
> -  sat_info quiet (tf_none, NULL_TREE);
> -  tree result = satisfy_nondeclaration_constraints (req, args, quiet);
> -  if (result == boolean_true_node)
> -    return;
> -
> -  tree expr = TREE_OPERAND (req, 0);
> -  location_t loc = cp_expr_location (expr);
> -  if (diagnosing_failed_constraint::replay_errors_p ())
> -    {
> -      /* Replay the substitution error with re-normalized requirements.  */
> -      inform (loc, "nested requirement %qE is not satisfied, because", expr);
> -
> -      sat_info noisy (tf_warning_or_error, NULL_TREE, /*diag_unsat=*/true);
> -      satisfy_nondeclaration_constraints (req, args, noisy);
> -    }
> -  else
> -    inform (loc, "nested requirement %qE is not satisfied", expr);
> -
> -}
> -
> -static void
> -diagnose_requirement (tree req, tree args, tree in_decl)
> -{
> -  iloc_sentinel loc_s (cp_expr_location (req));
> -  switch (TREE_CODE (req))
> -    {
> -    case SIMPLE_REQ:
> -      return diagnose_simple_requirement (req, args, in_decl);
> -    case COMPOUND_REQ:
> -      return diagnose_compound_requirement (req, args, in_decl);
> -    case TYPE_REQ:
> -      return diagnose_type_requirement (req, args, in_decl);
> -    case NESTED_REQ:
> -      return diagnose_nested_requirement (req, args);
> -    default:
> -       gcc_unreachable ();
> -    }
> -}
> -
> -static void
> -diagnose_requires_expr (tree expr, tree map, tree in_decl)
> -{
> -  local_specialization_stack stack (lss_copy);
> -  tree parms = TREE_OPERAND (expr, 0);
> -  tree body = TREE_OPERAND (expr, 1);
> -  tree args = get_mapped_args (map);
> -
> -  cp_unevaluated u;
> -  subst_info info (tf_warning_or_error, NULL_TREE);
> -  tree vars = tsubst_constraint_variables (parms, args, info);
> -  if (vars == error_mark_node)
> -    return;
> -
> -  tree p = body;
> -  while (p)
> -    {
> -      tree req = TREE_VALUE (p);
> -      diagnose_requirement (req, args, in_decl);
> -      p = TREE_CHAIN (p);
> -    }
> -}
> -
>   /* Diagnose a substitution failure in the atomic constraint T when applied
>      with the instantiated parameter mapping MAP.  */
>   
>   static void
> -diagnose_atomic_constraint (tree t, tree map, tree result, subst_info info)
> +diagnose_atomic_constraint (tree t, tree map, tree result, sat_info info)
>   {
>     /* If the constraint is already ill-formed, we've previously diagnosed
>        the reason. We should still say why the constraints aren't satisfied.  */
> @@ -3730,13 +3667,16 @@ diagnose_atomic_constraint (tree t, tree map, tree result, subst_info info)
>     /* Generate better diagnostics for certain kinds of expressions.  */
>     tree expr = ATOMIC_CONSTR_EXPR (t);
>     STRIP_ANY_LOCATION_WRAPPER (expr);
> +  tree args = get_mapped_args (map);
>     switch (TREE_CODE (expr))
>       {
>       case TRAIT_EXPR:
> -      diagnose_trait_expr (expr, map);
> +      diagnose_trait_expr (expr, args);
>         break;
>       case REQUIRES_EXPR:
> -      diagnose_requires_expr (expr, map, info.in_decl);
> +      gcc_checking_assert (info.diagnose_unsatisfaction_p ());
> +      info.in_decl = NULL_TREE;
> +      tsubst_requires_expr (expr, args, info);
>         break;
>       default:
>         if (!same_type_p (TREE_TYPE (result), boolean_type_node))
> @@ -3807,7 +3747,7 @@ diagnose_constraints (location_t loc, tree t, tree args)
>       {
>         gcc_assert (!args);
>         ++current_constraint_diagnosis_depth;
> -      diagnose_requires_expr (t, /*map=*/NULL_TREE, /*in_decl=*/NULL_TREE);
> +      tsubst_requires_expr (t, /*args=*/NULL_TREE, noisy);
>         --current_constraint_diagnosis_depth;
>       }
>     else
> diff --git a/gcc/testsuite/g++.dg/concepts/diagnostic1.C b/gcc/testsuite/g++.dg/concepts/diagnostic1.C
> index 29c78c4c730..23bd592411e 100644
> --- a/gcc/testsuite/g++.dg/concepts/diagnostic1.C
> +++ b/gcc/testsuite/g++.dg/concepts/diagnostic1.C
> @@ -8,7 +8,7 @@ concept bool SameAs = __is_same_as(T, U);
>   template <class T>
>   concept bool R1 = requires (T& t) { // { dg-message "in requirements" }
>     { t.begin() } -> T;		// { dg-error "no match" }
> -  { t.end() } -> SameAs<T*>;	// { dg-message "does not satisfy" }
> +  { t.end() } -> SameAs<T*>;

Are we no longer giving a message for this line?  That seems like a 
diagnostic quality regression.

>   };
>   
>   template <class T>
> 


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 3/4] c++: Delay normalizing nested requirements until satisfaction
  2021-03-01 23:09             ` Patrick Palka
@ 2021-03-02  2:26               ` Jason Merrill
  0 siblings, 0 replies; 29+ messages in thread
From: Jason Merrill @ 2021-03-02  2:26 UTC (permalink / raw)
  To: Patrick Palka; +Cc: gcc-patches

On 3/1/21 6:09 PM, Patrick Palka wrote:
> On Mon, 1 Mar 2021, Jason Merrill wrote:
> 
>> On 2/28/21 12:40 PM, Patrick Palka wrote:
>>> On Fri, 12 Feb 2021, Jason Merrill wrote:
>>>
>>>> On 2/10/21 9:41 AM, Patrick Palka wrote:
>>>>> On Tue, 9 Feb 2021, Jason Merrill wrote:
>>>>>
>>>>>> On 2/8/21 2:03 PM, Patrick Palka wrote:
>>>>>>> This sets up the functionality for controlling the initial set of
>>>>>>> template parameters to pass to normalization when dealing with a
>>>>>>> constraint-expression that is not associated with some constrained
>>>>>>> declaration, for instance when normalizing a nested requirement of a
>>>>>>> requires expression, or the constraints on a placeholder type.
>>>>>>>
>>>>>>> The main new ingredient here is the data member
>>>>>>> norm_info::initial_parms
>>>>>>> which can be set by callers of the normalization routines to
>>>>>>> communicate
>>>>>>> the in-scope template parameters for the supplied
>>>>>>> constraint-expression,
>>>>>>> rather than always falling back to using current_template_parms.
>>>>>>>
>>>>>>> This patch then uses this functionality in our handling of nested
>>>>>>> requirements so that we can delay normalizing them until needed for
>>>>>>> satisfaction.  We currently immediately normalize nested
>>>>>>> requirements at
>>>>>>> parse time, where we have the necessary template context, and cache
>>>>>>> the
>>>>>>> normal form in their TREE_TYPE node.  With this patch, we now delay
>>>>>>> normalization until needed (as with other constraint expressions),
>>>>>>> and
>>>>>>> instead store the current value of current_template_parms in their
>>>>>>> TREE_TYPE node (which we use to restore the template context at
>>>>>>> normalization time).
>>>>>>>
>>>>>>> In the subsequent patch, this functionality will also be used to
>>>>>>> normalize placeholder type constraints during auto deduction.
>>>>>>>
>>>>>>> gcc/cp/ChangeLog:
>>>>>>>
>>>>>>> 	* constraint.cc (build_parameter_mapping): Rely on the caller
>>>>>>> to
>>>>>>> 	determine the in-scope template parameters.
>>>>>>> 	(norm_info::norm_info): Delegate the one-parameter constructor
>>>>>>> 	to the two-parameter constructor.  In the two-parameter
>>>>>>> 	constructor, fold in the definition of make_context, set
>>>>>>> 	initial_parms appropriately, and don't set the now-removed
>>>>>>> 	orig_decl member.
>>>>>>> 	(norm_info::make_context): Remove, now that its only use is
>>>>>>> 	inlined into the caller.
>>>>>>> 	(norm_info::update_context): Adjust call to
>>>>>>> 	build_parameter_mapping to pass in the relevant set of
>>>>>>> in-scope
>>>>>>> 	template parameters.
>>>>>>> 	(norm_info::ctx_parms): Define this member function.
>>>>>>> 	(norm_info::context): Initialize to NULL_TREE.
>>>>>>> 	(norm_info::orig_decl): Remove this data member.
>>>>>>> 	(norm_info::initial_parms): Define this data member.
>>>>>>> 	(normalize_atom): Adjust call to build_parameter_mapping to
>>>>>>> pass
>>>>>>> 	in the relevant set of in-scope template parameters.  Use
>>>>>>> 	info.initial_parms instead of info.orig_decl.
>>>>>>> 	(normalize_constraint_expression): Define an overload that
>>>>>>> takes
>>>>>>> 	a norm_info object.  Cache the result of normalization.
>>>>>>> Define
>>>>>>> 	the other overload in terms of this one, and handle a
>>>>>>> NESTED_REQ
>>>>>>> 	argument by setting info.initial_parms appropriately.
>>>>>>> 	(tsubst_nested_requirement): Go through
>>>>>>> 	satisfy_constraint_expression so that we normalize on demand.
>>>>>>> 	(finish_nested_requirement): Set the TREE_TYPE of the
>>>>>>> NESTED_REQ
>>>>>>> 	to current_template_parms.
>>>>>>> 	(diagnose_nested_requirements): Go through
>>>>>>> 	satisfy_constraint_expression, as with
>>>>>>> tsubst_nested_requirement.
>>>>>>> ---
>>>>>>>      gcc/cp/constraint.cc | 140
>>>>>>> +++++++++++++++++++++++--------------------
>>>>>>>      gcc/cp/cp-tree.h     |   4 +-
>>>>>>>      2 files changed, 78 insertions(+), 66 deletions(-)
>>>>>>>
>>>>>>> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
>>>>>>> index 39c97986082..56134f8b2bf 100644
>>>>>>> --- a/gcc/cp/constraint.cc
>>>>>>> +++ b/gcc/cp/constraint.cc
>>>>>>> @@ -133,7 +133,7 @@ struct sat_info : subst_info
>>>>>>>        bool diagnose_unsatisfaction;
>>>>>>>      };
>>>>>>>      -static tree satisfy_constraint (tree, tree, sat_info);
>>>>>>> +static tree satisfy_constraint_expression (tree, tree, sat_info);
>>>>>>>        /* True if T is known to be some type other than bool. Note
>>>>>>> that
>>>>>>> this
>>>>>>>         is false for dependent types and errors.  */
>>>>>>> @@ -594,26 +594,12 @@ map_arguments (tree parms, tree args)
>>>>>>>        return parms;
>>>>>>>      }
>>>>>>>      -/* Build the parameter mapping for EXPR using ARGS.  */
>>>>>>> +/* Build the parameter mapping for EXPR using ARGS, where CTX_PARMS
>>>>>>> +   are the template parameters in scope for EXPR.  */
>>>>>>>        static tree
>>>>>>> -build_parameter_mapping (tree expr, tree args, tree decl)
>>>>>>> +build_parameter_mapping (tree expr, tree args, tree ctx_parms)
>>>>>>>      {
>>>>>>> -  tree ctx_parms = NULL_TREE;
>>>>>>> -  if (decl)
>>>>>>> -    {
>>>>>>> -      gcc_assert (TREE_CODE (decl) == TEMPLATE_DECL);
>>>>>>> -      ctx_parms = DECL_TEMPLATE_PARMS (decl);
>>>>>>> -    }
>>>>>>> -  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.
>>>>>>> */
>>>>>>> -      ctx_parms = current_template_parms;
>>>>>>> -    }
>>>>>>> -
>>>>>>>        tree parms = find_template_parameters (expr, ctx_parms);
>>>>>>>        tree map = map_arguments (parms, args);
>>>>>>>        return map;
>>>>>>> @@ -645,53 +631,63 @@ parameter_mapping_equivalent_p (tree t1, tree
>>>>>>> t2)
>>>>>>>        struct norm_info : subst_info
>>>>>>>      {
>>>>>>> -  explicit norm_info (tsubst_flags_t complain)
>>>>>>> -    : subst_info (tf_warning_or_error | complain, NULL_TREE),
>>>>>>> -      context()
>>>>>>> +  explicit norm_info (tsubst_flags_t cmp)
>>>>>>> +    : norm_info (NULL_TREE, cmp)
>>>>>>>        {}
>>>>>>>          /* Construct a top-level context for DECL.  */
>>>>>>>          norm_info (tree in_decl, tsubst_flags_t complain)
>>>>>>> -    : subst_info (tf_warning_or_error | complain, in_decl),
>>>>>>> -      context (make_context (in_decl)),
>>>>>>> -      orig_decl (in_decl)
>>>>>>> -  {}
>>>>>>> -
>>>>>>> -  bool generate_diagnostics() const
>>>>>>> +    : subst_info (tf_warning_or_error | complain, in_decl)
>>>>>>>        {
>>>>>>> -    return complain & tf_norm;
>>>>>>> +    if (in_decl)
>>>>>>> +      {
>>>>>>> +	initial_parms = DECL_TEMPLATE_PARMS (in_decl);
>>>>>>> +	if (generate_diagnostics ())
>>>>>>> +	  context = build_tree_list (NULL_TREE, in_decl);
>>>>>>> +      }
>>>>>>> +    else
>>>>>>> +      initial_parms = current_template_parms;
>>>>>>>        }
>>>>>>>      -  tree make_context(tree in_decl)
>>>>>>> +  bool generate_diagnostics() const
>>>>>>>        {
>>>>>>> -    if (generate_diagnostics ())
>>>>>>> -      return build_tree_list (NULL_TREE, in_decl);
>>>>>>> -    return NULL_TREE;
>>>>>>> +    return complain & tf_norm;
>>>>>>>        }
>>>>>>>          void update_context(tree expr, tree args)
>>>>>>>        {
>>>>>>>          if (generate_diagnostics ())
>>>>>>>            {
>>>>>>> -	tree map = build_parameter_mapping (expr, args, in_decl);
>>>>>>> +	tree map = build_parameter_mapping (expr, args, ctx_parms ());
>>>>>>>      	context = tree_cons (map, expr, context);
>>>>>>>            }
>>>>>>>          in_decl = get_concept_check_template (expr);
>>>>>>>        }
>>>>>>>      +  /* Returns the template parameters that are in scope for the
>>>>>>> current
>>>>>>> +     normalization context.  */
>>>>>>> +
>>>>>>> +  tree ctx_parms()
>>>>>>> +  {
>>>>>>> +    if (in_decl)
>>>>>>> +      return DECL_TEMPLATE_PARMS (in_decl);
>>>>>>> +    else
>>>>>>> +      return initial_parms;
>>>>>>> +  }
>>>>>>> +
>>>>>>>        /* Provides information about the source of a constraint. This
>>>>>>> is a
>>>>>>>           TREE_LIST whose VALUE is either a concept check or a
>>>>>>> constrained
>>>>>>>           declaration. The PURPOSE, for concept checks is a parameter
>>>>>>> mapping
>>>>>>>           for that check.  */
>>>>>>>      -  tree context;
>>>>>>> +  tree context = NULL_TREE;
>>>>>>>          /* The declaration whose constraints we're normalizing.  The
>>>>>>> targets
>>>>>>>           of the parameter mapping of each atom will be in terms of
>>>>>>> the
>>>>>>>           template parameters of ORIG_DECL.  */
>>>>>>>      -  tree orig_decl = NULL_TREE;
>>>>>>> +  tree initial_parms = NULL_TREE;
>>>>>>>      };
>>>>>>>        static tree normalize_expression (tree, tree, norm_info);
>>>>>>> @@ -773,7 +769,7 @@ normalize_atom (tree t, tree args, norm_info
>>>>>>> info)
>>>>>>>          return normalize_concept_check (t, args, info);
>>>>>>>          /* Build the parameter mapping for the atom.  */
>>>>>>> -  tree map = build_parameter_mapping (t, args, info.in_decl);
>>>>>>> +  tree map = build_parameter_mapping (t, args, info.ctx_parms ());
>>>>>>>          /* Build a new info object for the atom.  */
>>>>>>>        tree ci = build_tree_list (t, info.context);
>>>>>>> @@ -803,10 +799,8 @@ normalize_atom (tree t, tree args, norm_info
>>>>>>> info)
>>>>>>>      	      tree target = TREE_PURPOSE (node);
>>>>>>>      	      TREE_VEC_ELT (targets, i++) = target;
>>>>>>>      	    }
>>>>>>> -	  tree ctx_parms = (info.orig_decl
>>>>>>> -			    ? DECL_TEMPLATE_PARMS (info.orig_decl)
>>>>>>> -			    : current_template_parms);
>>>>>>> -	  tree target_parms = find_template_parameters (targets,
>>>>>>> ctx_parms);
>>>>>>> +	  tree target_parms = find_template_parameters (targets,
>>>>>>> +
>>>>>>> info.initial_parms);
>>>>>>>      	  TREE_TYPE (map) = target_parms;
>>>>>>>      	}
>>>>>>>      @@ -983,17 +977,43 @@ normalize_nontemplate_requirements (tree
>>>>>>> decl,
>>>>>>> bool
>>>>>>> diag = false)
>>>>>>>      /* Normalize an EXPR as a constraint.  */
>>>>>>>        static tree
>>>>>>> -normalize_constraint_expression (tree expr, bool diag)
>>>>>>> +normalize_constraint_expression (tree expr, norm_info info)
>>>>>>>      {
>>>>>>>        if (!expr || expr == error_mark_node)
>>>>>>>          return expr;
>>>>>>> +
>>>>>>> +  if (!info.generate_diagnostics ())
>>>>>>> +    if (tree *p = hash_map_safe_get (normalized_map, expr))
>>>>>>> +      return *p;
>>>>>>
>>>>>> It seems like we only want this for NESTED_REQ.
>>>>>
>>>>> I figured it'd also be beneficial to cache the normal form of a
>>>>> placeholder type constraint, which will also goes through this overload.
>>>>
>>>> True.  And if we change REQUIRES_EXPR handling to not go through here, it
>>>> should be just the two.  OK, let's leave this alone.
>>>>
>>>>>>>        ++processing_template_decl;
>>>>>>> -  norm_info info (diag ? tf_norm : tf_none);
>>>>>>>        tree norm = get_normalized_constraints (expr, info);
>>>>>>>        --processing_template_decl;
>>>>>>> +
>>>>>>> +  if (!info.generate_diagnostics ())
>>>>>>> +    hash_map_safe_put<hm_ggc> (normalized_map, expr, norm);
>>>>>>> +
>>>>>>>        return norm;
>>>>>>>      }
>>>>>>>      +/* High-level wrapper for the above.  */
>>>>>>> +
>>>>>>> +static tree
>>>>>>> +normalize_constraint_expression (tree expr, bool diag)
>>>>>>> +{
>>>>>>> +  norm_info info (diag ? tf_norm : tf_none);
>>>>>>
>>>>>> I wonder if we want to add a norm_info constructor taking a sat_info
>>>>>> so we
>>>>>> don't need to mediate passing from one into the other with bool "diag"
>>>>>> parameters in various functions.  That doesn't need to happen in this
>>>>>> patch.
>>>>>
>>>>> Sounds good.  I think such a constructor would let us eliminate the
>>>>> bool-taking overload of normalize_constraint_expression altogether, if
>>>>> we move the special handling of NESTED_REQ to the norm_info-taking
>>>>> overload or to satisfy_constraint_expression.
>>>
>>> Here's v2 of this patch, which refrains from adding a second overload of
>>> normalize_constraint_expression.  Handling of NESTED_REQs now happens in
>>> satisfy_constraint_expression.
>>>
>>> The rest of the cleanups mentioned here will be performed in another
>>> patch.
>>>
>>> (For now, I didn't add a norm_info(sat_info) constructor since it wasn't
>>> clear to me if this constructor should propagate in_decl or not.  For >
>>> sat_info, in_decl is used purely for diagnostics, whereas for norm_info,
>>> in_decl keeps track of the current normalization context, so carrying
>>> over the value of in_decl might not always make sense.
>>
>>> Perhaps it might be better to add a norm_info(bool diag) constructor
>>> instead?
>>
>> I was thinking the norm_info(sat_info) constructor would do the equivalent of
>>
>> +      norm_info ninfo (info.noisy () ? tf_norm : tf_none);
> 
> Ah, sounds good.  Shall I do that (and the tf_norm removal) in a
> followup patch, or update this one?

In a followup is fine.

>>> While we're at it, I think we could remove the tf_norm flag.)
>>
>> Sure.
>>
>>> -- >8 --
>>>
>>> Subject: [PATCH 3/6] c++: Delay normalizing nested requirements until
>>>    satisfaction
>>>
>>> This sets up the functionality for controlling the initial set of
>>> template parameters to pass to normalization when dealing with a
>>> constraint-expression that is not associated with some constrained
>>> declaration, for instance when normalizing a nested requirement of a
>>> requires expression, or the constraints on a placeholder type.
>>>
>>> The main new ingredient here is the data member norm_info::initial_parms
>>> which can be set by callers of the normalization routines to communicate
>>> the in-scope template parameters for the supplied constraint-expression,
>>> rather than always falling back to using current_template_parms.
>>>
>>> This patch then uses this functionality in our handling of nested
>>> requirements so that we can delay normalizing them until needed for
>>> satisfaction.  We currently immediately normalize nested requirements at
>>> parse time, where we have the necessary template context, and cache the
>>> normal form in their TREE_TYPE node.  With this patch, we now delay
>>> normalization until needed (as with other constraint expressions), and
>>> instead store the current value of current_template_parms in their
>>> TREE_TYPE node (which we use to restore the template context at
>>> normalization time).
>>>
>>> In the subsequent patch, this functionality will also be used to
>>> normalize placeholder type constraints during auto deduction.
>>>
>>> gcc/cp/ChangeLog:
>>>
>>> 	* constraint.cc (build_parameter_mapping): Rely on the caller to
>>> 	determine the in-scope template parameters.
>>> 	(norm_info::norm_info): Delegate the tsubst_flags_t constructor
>>> 	to the two-parameter constructor.  In the two-parameter
>>> 	constructor, fold in the definition of make_context, set
>>> 	initial_parms appropriately, and don't set the now-removed
>>> 	orig_decl member.
>>> 	(norm_info::make_context): Remove, now that its only use is
>>> 	inlined into the caller.
>>> 	(norm_info::update_context): Adjust call to
>>> 	build_parameter_mapping to pass in the relevant set of in-scope
>>> 	template parameters.
>>> 	(norm_info::ctx_parms): Define this member function.
>>> 	(norm_info::context): Initialize to NULL_TREE.
>>> 	(norm_info::orig_decl): Remove this data member.
>>> 	(norm_info::initial_parms): Define this data member.
>>> 	(normalize_atom): Adjust call to build_parameter_mapping to pass
>>> 	in the relevant set of in-scope template parameters.  Use
>>> 	info.initial_parms instead of info.orig_decl.
>>> 	(normalize_constraint_expression): Take a norm_info object
>>> 	instead of a bool.  Cache the result of normalization.
>>> 	(tsubst_nested_requirement): Call satisfy_constraint_expression
>>> 	instead of satisfy_constraint, so that we normalize on demand.
>>> 	(satisfy_constraint_expression): Handle a NESTED_REQ argument.
>>> 	Adjust call to normalize_constraint_expression.
>>> 	(finish_nested_requirement): Set the TREE_TYPE of the NESTED_REQ
>>> 	to current_template_parms.
>>> 	(diagnose_nested_requirements): Go through
>>> 	satisfy_constraint_expression, as with tsubst_nested_requirement.
>>> ---
>>>    gcc/cp/constraint.cc | 138 ++++++++++++++++++++++---------------------
>>>    1 file changed, 72 insertions(+), 66 deletions(-)
>>>
>>> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
>>> index 39c97986082..fcb249a642f 100644
>>> --- a/gcc/cp/constraint.cc
>>> +++ b/gcc/cp/constraint.cc
>>> @@ -133,7 +133,7 @@ struct sat_info : subst_info
>>>      bool diagnose_unsatisfaction;
>>>    };
>>>    -static tree satisfy_constraint (tree, tree, sat_info);
>>> +static tree satisfy_constraint_expression (tree, tree, sat_info);
>>>      /* True if T is known to be some type other than bool. Note that this
>>>       is false for dependent types and errors.  */
>>> @@ -594,26 +594,12 @@ map_arguments (tree parms, tree args)
>>>      return parms;
>>>    }
>>>    -/* Build the parameter mapping for EXPR using ARGS.  */
>>> +/* Build the parameter mapping for EXPR using ARGS, where CTX_PARMS
>>> +   are the template parameters in scope for EXPR.  */
>>>      static tree
>>> -build_parameter_mapping (tree expr, tree args, tree decl)
>>> +build_parameter_mapping (tree expr, tree args, tree ctx_parms)
>>>    {
>>> -  tree ctx_parms = NULL_TREE;
>>> -  if (decl)
>>> -    {
>>> -      gcc_assert (TREE_CODE (decl) == TEMPLATE_DECL);
>>> -      ctx_parms = DECL_TEMPLATE_PARMS (decl);
>>> -    }
>>> -  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.  */
>>> -      ctx_parms = current_template_parms;
>>> -    }
>>> -
>>>      tree parms = find_template_parameters (expr, ctx_parms);
>>>      tree map = map_arguments (parms, args);
>>>      return map;
>>> @@ -645,53 +631,63 @@ parameter_mapping_equivalent_p (tree t1, tree t2)
>>>      struct norm_info : subst_info
>>>    {
>>> -  explicit norm_info (tsubst_flags_t complain)
>>> -    : subst_info (tf_warning_or_error | complain, NULL_TREE),
>>> -      context()
>>> +  explicit norm_info (tsubst_flags_t cmp)
>>> +    : norm_info (NULL_TREE, cmp)
>>>      {}
>>>        /* Construct a top-level context for DECL.  */
>>>        norm_info (tree in_decl, tsubst_flags_t complain)
>>> -    : subst_info (tf_warning_or_error | complain, in_decl),
>>> -      context (make_context (in_decl)),
>>> -      orig_decl (in_decl)
>>> -  {}
>>> -
>>> -  bool generate_diagnostics() const
>>> +    : subst_info (tf_warning_or_error | complain, in_decl)
>>>      {
>>> -    return complain & tf_norm;
>>> +    if (in_decl)
>>> +      {
>>> +	initial_parms = DECL_TEMPLATE_PARMS (in_decl);
>>> +	if (generate_diagnostics ())
>>> +	  context = build_tree_list (NULL_TREE, in_decl);
>>> +      }
>>> +    else
>>> +      initial_parms = current_template_parms;
>>>      }
>>>    -  tree make_context(tree in_decl)
>>> +  bool generate_diagnostics() const
>>>      {
>>> -    if (generate_diagnostics ())
>>> -      return build_tree_list (NULL_TREE, in_decl);
>>> -    return NULL_TREE;
>>> +    return complain & tf_norm;
>>>      }
>>>        void update_context(tree expr, tree args)
>>>      {
>>>        if (generate_diagnostics ())
>>>          {
>>> -	tree map = build_parameter_mapping (expr, args, in_decl);
>>> +	tree map = build_parameter_mapping (expr, args, ctx_parms ());
>>>    	context = tree_cons (map, expr, context);
>>>          }
>>>        in_decl = get_concept_check_template (expr);
>>>      }
>>>    +  /* Returns the template parameters that are in scope for the current
>>> +     normalization context.  */
>>> +
>>> +  tree ctx_parms()
>>> +  {
>>> +    if (in_decl)
>>> +      return DECL_TEMPLATE_PARMS (in_decl);
>>> +    else
>>> +      return initial_parms;
>>> +  }
>>
>> Why prefer in_decl to initial_parms?  In fact, why look at in_decl here at
>> all, when we already used it to set initial_parms in the constructor?
> 
> IIUC, that's true at the start of normalization, but in_decl gets
> continuously updated as we recurse into a concept-id and normalize the
> concept's definition (in normalize_concept_check via
> norm_info::update_context), whereas initial_parms gets set at the start
> of normalization (upon construction) and remains unchanged.

Ah, I see.  Then this patch is OK.

>>
>>>      /* Provides information about the source of a constraint. This is a
>>>         TREE_LIST whose VALUE is either a concept check or a constrained
>>>         declaration. The PURPOSE, for concept checks is a parameter mapping
>>>         for that check.  */
>>>    -  tree context;
>>> +  tree context = NULL_TREE;
>>>        /* The declaration whose constraints we're normalizing.  The targets
>>>         of the parameter mapping of each atom will be in terms of the
>>>         template parameters of ORIG_DECL.  */
>>>    -  tree orig_decl = NULL_TREE;
>>> +  tree initial_parms = NULL_TREE;
>>>    };
>>>      static tree normalize_expression (tree, tree, norm_info);
>>> @@ -773,7 +769,7 @@ normalize_atom (tree t, tree args, norm_info info)
>>>        return normalize_concept_check (t, args, info);
>>>        /* Build the parameter mapping for the atom.  */
>>> -  tree map = build_parameter_mapping (t, args, info.in_decl);
>>> +  tree map = build_parameter_mapping (t, args, info.ctx_parms ());
>>>        /* Build a new info object for the atom.  */
>>>      tree ci = build_tree_list (t, info.context);
>>> @@ -803,10 +799,8 @@ normalize_atom (tree t, tree args, norm_info info)
>>>    	      tree target = TREE_PURPOSE (node);
>>>    	      TREE_VEC_ELT (targets, i++) = target;
>>>    	    }
>>> -	  tree ctx_parms = (info.orig_decl
>>> -			    ? DECL_TEMPLATE_PARMS (info.orig_decl)
>>> -			    : current_template_parms);
>>> -	  tree target_parms = find_template_parameters (targets, ctx_parms);
>>> +	  tree target_parms = find_template_parameters (targets,
>>> +							info.initial_parms);
>>>    	  TREE_TYPE (map) = target_parms;
>>>    	}
>>>    @@ -983,14 +977,22 @@ normalize_nontemplate_requirements (tree decl, bool
>>> diag = false)
>>>    /* Normalize an EXPR as a constraint.  */
>>>      static tree
>>> -normalize_constraint_expression (tree expr, bool diag)
>>> +normalize_constraint_expression (tree expr, norm_info info)
>>>    {
>>>      if (!expr || expr == error_mark_node)
>>>        return expr;
>>> +
>>> +  if (!info.generate_diagnostics ())
>>> +    if (tree *p = hash_map_safe_get (normalized_map, expr))
>>> +      return *p;
>>> +
>>>      ++processing_template_decl;
>>> -  norm_info info (diag ? tf_norm : tf_none);
>>>      tree norm = get_normalized_constraints (expr, info);
>>>      --processing_template_decl;
>>> +
>>> +  if (!info.generate_diagnostics ())
>>> +    hash_map_safe_put<hm_ggc> (normalized_map, expr, norm);
>>> +
>>>      return norm;
>>>    }
>>>    @@ -2086,16 +2088,14 @@ tsubst_compound_requirement (tree t, tree args,
>>> subst_info info)
>>>    static tree
>>>    tsubst_nested_requirement (tree t, tree args, subst_info info)
>>>    {
>>> -  /* Perform satisfaction quietly with the regular normal form.  */
>>> +  /* Perform satisfaction quietly first.  */
>>>      sat_info quiet (tf_none, info.in_decl);
>>> -  tree norm = TREE_VALUE (TREE_TYPE (t));
>>> -  tree diag_norm = TREE_PURPOSE (TREE_TYPE (t));
>>> -  tree result = satisfy_constraint (norm, args, quiet);
>>> +  tree result = satisfy_constraint_expression (t, args, quiet);
>>>      if (result == error_mark_node)
>>>        {
>>> -      /* Replay the error using the diagnostic normal form.  */
>>> +      /* Replay the error.  */
>>>          sat_info noisy (tf_warning_or_error, info.in_decl);
>>> -      satisfy_constraint (diag_norm, args, noisy);
>>> +      satisfy_constraint_expression (t, args, noisy);
>>>        }
>>>      if (result != boolean_true_node)
>>>        return error_mark_node;
>>> @@ -3040,8 +3040,22 @@ satisfy_constraint_expression (tree t, tree args,
>>> sat_info info)
>>>          tree tmpl = get_concept_check_template (id);
>>>          norm = normalize_concept_definition (tmpl, info.noisy ());
>>>        }
>>> +  else if (TREE_CODE (t) == NESTED_REQ)
>>> +    {
>>> +      norm_info ninfo (info.noisy () ? tf_norm : tf_none);
>>> +      /* The TREE_TYPE contains the set of template parameters that were in
>>> +	 scope for this nested requirement; use them as the initial template
>>> +	 parameters for normalization.  */
>>> +      ninfo.initial_parms = TREE_TYPE (t);
>>> +      norm = normalize_constraint_expression (TREE_OPERAND (t, 0), ninfo);
>>> +    }
>>> +  else if (EXPR_P (t))
>>> +    {
>>> +      norm_info ninfo (info.noisy () ? tf_norm : tf_none);
>>> +      norm = normalize_constraint_expression (t, ninfo);
>>> +    }
>>>      else
>>> -    norm = normalize_constraint_expression (t, info.noisy ());
>>> +    gcc_unreachable ();
>>>        /* Perform satisfaction.  */
>>>      return satisfy_constraint (norm, args, info);
>>> @@ -3301,15 +3315,9 @@ finish_compound_requirement (location_t loc, tree
>>> expr, tree type, bool noexcept
>>>    tree
>>>    finish_nested_requirement (location_t loc, tree expr)
>>>    {
>>> -  /* We need to normalize the constraints now, at parse time, while
>>> -     we have the necessary template context.  We normalize twice,
>>> -     once without diagnostic information and once with, which we'll
>>> -     later use for quiet and noisy satisfaction respectively.  */
>>> -  tree norm = normalize_constraint_expression (expr, /*diag=*/false);
>>> -  tree diag_norm = normalize_constraint_expression (expr, /*diag=*/true);
>>> -
>>> -  /* Build the constraint, saving its two normalizations as its type.  */
>>> -  tree r = build1 (NESTED_REQ, build_tree_list (diag_norm, norm), expr);
>>> +  /* Build the requirement, saving the set of in-scope template
>>> +     parameters as its type.  */
>>> +  tree r = build1 (NESTED_REQ, current_template_parms, expr);
>>>      SET_EXPR_LOCATION (r, loc);
>>>      return r;
>>>    }
>>> @@ -3710,12 +3718,9 @@ diagnose_type_requirement (tree req, tree args, tree
>>> in_decl)
>>>    static void
>>>    diagnose_nested_requirement (tree req, tree args)
>>>    {
>>> -  /* Quietly check for satisfaction first using the regular normal form.
>>> -     We can elaborate details later if needed.  */
>>> -  tree norm = TREE_VALUE (TREE_TYPE (req));
>>> -  tree diag_norm = TREE_PURPOSE (TREE_TYPE (req));
>>> -  sat_info info (tf_none, NULL_TREE);
>>> -  tree result = satisfy_constraint (norm, args, info);
>>> +  /* Quietly check for satisfaction first.  */
>>> +  sat_info quiet (tf_none, NULL_TREE);
>>> +  tree result = satisfy_constraint_expression (req, args, quiet);
>>>      if (result == boolean_true_node)
>>>        return;
>>>    @@ -3723,10 +3728,11 @@ diagnose_nested_requirement (tree req, tree args)
>>>      location_t loc = cp_expr_location (expr);
>>>      if (diagnosing_failed_constraint::replay_errors_p ())
>>>        {
>>> -      /* Replay the substitution error using the diagnostic normal form.
>>> */
>>> +      /* Replay the substitution error with re-normalized requirements.  */
>>>          inform (loc, "nested requirement %qE is not satisfied, because",
>>> expr);
>>> +
>>>          sat_info noisy (tf_warning_or_error, NULL_TREE,
>>> /*diag_unsat=*/true);
>>> -      satisfy_constraint (diag_norm, args, noisy);
>>> +      satisfy_constraint_expression (req, args, noisy);
>>>        }
>>>      else
>>>        inform (loc, "nested requirement %qE is not satisfied", expr);
>>>
>>
>>
> 


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 5/6] c++: Clean up normalization / satisfaction routines
  2021-03-01 22:50   ` Jason Merrill
@ 2021-03-02 16:25     ` Patrick Palka
  2021-03-02 22:58       ` Jason Merrill
  0 siblings, 1 reply; 29+ messages in thread
From: Patrick Palka @ 2021-03-02 16:25 UTC (permalink / raw)
  To: Jason Merrill; +Cc: Patrick Palka, gcc-patches

On Mon, 1 Mar 2021, Jason Merrill wrote:

> On 2/28/21 12:58 PM, Patrick Palka wrote:
> > This patch mostly performs some straightforward refactoring:
> > 
> >    - Renamed satisfy_constraint to satisfy_normalized_constraints
> >    - Renamed the three-parameter version of satisfy_constraint_expression
> >      to satisfy_nondeclaration_constraints
> >    - Removed normalize_(non)?template_requirements
> >    - Removed satisfy_associated_constraints (and made its callers
> >      check for dependent template args sooner, before normalization)
> >    - Removed the tsubst_flags_t parameter of evaluate_concept_check
> >    - Combined the two versions of constraint_satisfaction_value
> >    - Combined the two versions of constraint_satisfied_p
> > 
> > Additionally, this patch removes the handling of bare
> > constraint-expressions from satisfy_nondeclaration_constraints, and
> > hence constraints_satisfied_p and constraint_satisfaction_value now only
> > take things that carry their own template information needed for
> > normalization.  In practice, this only means it's no longer possible to
> > evaluate bare REQUIRES_EXPRs via the satisfaction routines, and so this
> > patch adjusts the affected callers to instead use tsubst_requires_expr.
> 
> It's probably better to have a different entry point than tsubst_requires_expr
> for callers that have nothing to do with templates.  Whether that's a one-line
> wrapper for the call to tsubst_requires_expr ("evaluate_requires_expr"?) or
> calling tsubst_requires_expr from satisfy_nondeclaration_constraints, I don't
> have an opinion either way.
> 
> > For convenience, the function diagnose_constraints continues to accept
> > REQUIRES_EXPRs, but it now handles them by calling diagnose_require_expr
> > directly.
> 
> This might argue for the latter choice above.

Ah, I didn't consider the option of continuing to handle REQUIRES_EXPRs
from satisfy_nondeclaration_constraints by evaluating them directly.

Here's a patch that implements this.  I opted to add a
evaluate_requires_expr wrapper as well, since

  evaluate_requires_expr (t)

is much more readable than

  constant_boolean_node (constraints_satisfied_p (t),
                         boolean_type_node)

(Note the added TODO in satisfy_nondeclaration_constraints, which'll be
fixed in a new version of the REQUIRES_EXPR evaluation/diagnostic
unification patch.)

-- >8 --

Subject: [PATCH] c++: Clean up normalization / satisfaction routines

This patch mostly performs some straightforward refactoring:

  - Renamed satisfy_constraint to satisfy_normalized_constraints
  - Renamed the three-parameter version of satisfy_constraint_expression
    to satisfy_nondeclaration_constraints
  - Removed normalize_(non)?template_requirements
  - Removed satisfy_associated_constraints (and made its callers
    check for dependent template args sooner, before normalization)
  - Removed the tsubst_flags_t parameter of evaluate_concept_check
  - Combined the two versions of constraint_satisfaction_value
  - Combined the two versions of constraint_satisfied_p

Additionally, this patch removes the handling of general
constraint-expressions from satisfy_nondeclaration_constraints, and
hence constraints_satisfied_p and constraint_satisfaction_value now take
only things that carry their own template information needed for
normalization, and, as a special case, REQUIRES_EXPRs.  But the latter
now get evaluated directly via tsubst_requires_expr rather than going
through satisfaction.

(That we used to evaluate REQUIRES_EXPR via satisfaction might even be a
correctness issue: since we cache satisfaction in special ways that don't
apply to regular evaluation, going through satisfaction could in theory
cause us to reuse a cached value for a REQUIRES_EXPR when we shouldn't
have.)

gcc/cp/ChangeLog:

	* constexpr.c (cxx_eval_call_expression): Adjust call to
	evaluate_concept_check.
	(cxx_eval_constant_expression) <case REQUIRES_EXPR>: Use
	evaluate_requires_expression instead of
	satisfy_constraint_expression.
	<case TEMPLATE_ID_EXPR>: Adjust call to evaluate_concept_check.
	* constraint.cc (struct sat_info): Adjust comment about which
	satisfaction entrypoints use noisy-unsat.
	(normalize_template_requirements): Remove (and adjust callers
	appropriately).
	(normalize_nontemplate_requirements): Likewise.
	(tsubst_nested_requirement): Use constraint_satisfaction_value
	instead of satisfy_constraint_expression, which'll do the
	noisy replaying of ill-formed quiet satisfaction for us.
	(decl_satisfied_cache): Adjust comment.
	(satisfy_constraint): Rename to ...
	(satisfy_normalized_constraints): ... this.
	(satisfy_associated_constraints): Remove (and make its
	callers check for dependent arguments).
	(satisfy_constraint_expression): Rename to ...
	(satisfy_nondeclaration_constraints): ... this.  Assert that
	'args' is empty when 't' is a concept-id.  Removing handling
	bare constraint-expressions, and handle REQUIRES_EXPRs
	specially.  Adjust comment accordingly.
	(satisfy_declaration_constraints): Assert in the two-parameter
	version that 't' is not a TEMPLATE_DECL.  Adjust following
	removal of normalize_(non)?template_requirements and
	satisfy_asociated_constraints.
	(constraint_satisfaction_value): Combine the two- and
	three-parameter versions in the natural way.
	(constraints_satisfied_p): Combine the one- and two-parameter
	versions in the natural way.  Improve documentation.
	(evaluate_requires_expr): Define.
	(evaluate_concept_check): Remove 'complain' parameter.  Use
	constraint_satisfaction_value instead of
	satisfy_constraint_expression.
	(diagnose_nested_requirement): Adjust following renaming of
	satisfy_constraint_expression.
	(diagnose_constraints): Handle REQUIRES_EXPR by going through
	diagnose_requires_expr directly instead of treating it as a
	constraint-expression.  Improve documentation.
	* cp-gimplify.c (cp_genericize_r) <case CALL_EXPR>: Adjust call
	to evaluate_concept_check.
	<case REQUIRES_EXPR>: Use evaluate_requires_expr instead of
	constraints_satisfied_p.
	<case TEMPLATE_ID_EXPR>: Adjust call to evaluate_concept_check.
	* cp-tree.h (evaluate_requires_expr): Declare.
	(evaluate_concept_check): Remove tsubst_flag_t parameter.
	(satisfy_constraint_expression): Remove declaration.
	(constraints_satisfied_p): Remove one-parameter declaration.
	Add a default argument to the two-parameter declaration.
	* cvt.c (convert_to_void): Adjust call to
	evaluate_concept_check.
---
 gcc/cp/constexpr.c   |   6 +-
 gcc/cp/constraint.cc | 234 ++++++++++++++++++-------------------------
 gcc/cp/cp-gimplify.c |   7 +-
 gcc/cp/cp-tree.h     |   7 +-
 gcc/cp/cvt.c         |   2 +-
 5 files changed, 110 insertions(+), 146 deletions(-)

diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c
index cd0a68e9fd6..7d96d577d84 100644
--- a/gcc/cp/constexpr.c
+++ b/gcc/cp/constexpr.c
@@ -2257,7 +2257,7 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
 {
   /* Handle concept checks separately.  */
   if (concept_check_p (t))
-    return evaluate_concept_check (t, tf_warning_or_error);
+    return evaluate_concept_check (t);
 
   location_t loc = cp_expr_loc_or_input_loc (t);
   tree fun = get_function_named_in_call (t);
@@ -6905,7 +6905,7 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
          '!requires (T t) { ... }' which is not transformed into
          a constraint.  */
       if (!processing_template_decl)
-        return satisfy_constraint_expression (t);
+	return evaluate_requires_expr (t);
       else
         *non_constant_p = true;
       return t;
@@ -6941,7 +6941,7 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
 
 	if (!processing_template_decl
 	    && !uid_sensitive_constexpr_evaluation_p ())
-	  r = evaluate_concept_check (t, tf_warning_or_error);
+	  r = evaluate_concept_check (t);
 	else
 	  *non_constant_p = true;
 
diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index 3332e6092a9..0949788aa29 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -107,10 +107,9 @@ struct subst_info
    a constraint is not satisfied.
 
    The entrypoints to satisfaction for which we set noisy+unsat are
-   diagnose_constraints and diagnose_nested_requirement.  The entrypoints for
-   which we set noisy-unsat are the replays inside constraint_satisfaction_value,
-   evaluate_concept_check and tsubst_nested_requirement.  In other entrypoints,
-   e.g. constraints_satisfied_p, we enter satisfaction quietly (both flags
+   diagnose_constraints and diagnose_nested_requirement.  The entrypoint for
+   which we set noisy-unsat is the replay inside constraint_satisfaction_value.
+   From constraints_satisfied_p, we enter satisfaction quietly (both flags
    cleared).  */
 
 struct sat_info : subst_info
@@ -133,7 +132,7 @@ struct sat_info : subst_info
   bool diagnose_unsatisfaction;
 };
 
-static tree satisfy_constraint_expression (tree, tree, sat_info);
+static tree constraint_satisfaction_value (tree, tree, sat_info);
 
 /* True if T is known to be some type other than bool. Note that this
    is false for dependent types and errors.  */
@@ -958,22 +957,6 @@ normalize_concept_definition (tree tmpl, bool diag = false)
   return norm;
 }
 
-/* Returns the normal form of TMPL's requirements.  */
-
-static tree
-normalize_template_requirements (tree tmpl, bool diag = false)
-{
-  return get_normalized_constraints_from_decl (tmpl, diag);
-}
-
-/* Returns the normal form of TMPL's requirements.  */
-
-static tree
-normalize_nontemplate_requirements (tree decl, bool diag = false)
-{
-  return get_normalized_constraints_from_decl (decl, diag);
-}
-
 /* Normalize an EXPR as a constraint.  */
 
 static tree
@@ -2068,15 +2051,8 @@ tsubst_compound_requirement (tree t, tree args, subst_info info)
 static tree
 tsubst_nested_requirement (tree t, tree args, subst_info info)
 {
-  /* Perform satisfaction quietly first.  */
   sat_info quiet (tf_none, info.in_decl);
-  tree result = satisfy_constraint_expression (t, args, quiet);
-  if (result == error_mark_node)
-    {
-      /* Replay the error.  */
-      sat_info noisy (tf_warning_or_error, info.in_decl);
-      satisfy_constraint_expression (t, args, noisy);
-    }
+  tree result = constraint_satisfaction_value (t, args, quiet);
   if (result != boolean_true_node)
     return error_mark_node;
   return boolean_true_node;
@@ -2459,7 +2435,7 @@ struct sat_hasher : ggc_ptr_hash<sat_entry>
 /* Cache the result of satisfy_atom.  */
 static GTY((deletable)) hash_table<sat_hasher> *sat_cache;
 
-/* Cache the result of constraint_satisfaction_value.  */
+/* Cache the result of satisfy_declaration_constraints.  */
 static GTY((deletable)) hash_map<tree, tree> *decl_satisfied_cache;
 
 /* A tool used by satisfy_atom to help manage satisfaction caching and to
@@ -2941,7 +2917,7 @@ satisfy_constraint_r (tree t, tree args, sat_info info)
 /* Check that the normalized constraint T is satisfied for ARGS.  */
 
 static tree
-satisfy_constraint (tree t, tree args, sat_info info)
+satisfy_normalized_constraints (tree t, tree args, sat_info info)
 {
   auto_timevar time (TV_CONSTRAINT_SAT);
 
@@ -2957,24 +2933,6 @@ satisfy_constraint (tree t, tree args, sat_info info)
   return satisfy_constraint_r (t, args, info);
 }
 
-/* Check the normalized constraints T against ARGS, returning a satisfaction
-   value (either true, false, or error).  */
-
-static tree
-satisfy_associated_constraints (tree t, tree args, sat_info info)
-{
-  /* If there are no constraints then this is trivially satisfied.  */
-  if (!t)
-    return boolean_true_node;
-
-  /* If any arguments depend on template parameters, we can't
-     check constraints. Pretend they're satisfied for now.  */
-  if (args && uses_template_parms (args))
-    return boolean_true_node;
-
-  return satisfy_constraint (t, args, info);
-}
-
 /* Return the normal form of the constraints on the placeholder 'auto'
    type T.  */
 
@@ -3005,19 +2963,34 @@ normalize_placeholder_type_constraints (tree t, bool diag)
   return normalize_constraint_expression (constr, info);
 }
 
-/* Evaluate EXPR as a constraint expression using ARGS, returning a
-   satisfaction value. */
+/* Evaluate the constraints of T using ARGS, returning a satisfaction value.
+   Here, T can be a concept-id, nested-requirement, placeholder 'auto', or
+   requires-expression.  */
 
 static tree
-satisfy_constraint_expression (tree t, tree args, sat_info info)
+satisfy_nondeclaration_constraints (tree t, tree args, sat_info info)
 {
   if (t == error_mark_node)
     return error_mark_node;
 
+  /* Handle REQUIRES_EXPR directly, bypassing satisfaction.  */
+  if (TREE_CODE (t) == REQUIRES_EXPR)
+    {
+      /* TODO: Remove this assert and the special casing of REQUIRES_EXPRs
+	 from diagnose_constraints once we merge tsubst_requires_expr and
+	 diagnose_requires_expr.  */
+      gcc_assert (!info.diagnose_unsatisfaction_p ());
+      auto ovr = make_temp_override (current_constraint_diagnosis_depth);
+      if (info.noisy ())
+	++current_constraint_diagnosis_depth;
+      return tsubst_requires_expr (t, args, info.complain, info.in_decl);
+    }
+
   /* Get the normalized constraints.  */
   tree norm;
-  if (args == NULL_TREE && concept_check_p (t))
+  if (concept_check_p (t))
     {
+      gcc_assert (!args);
       tree id = unpack_concept_check (t);
       args = TREE_OPERAND (id, 1);
       tree tmpl = get_concept_check_template (id);
@@ -3032,11 +3005,6 @@ satisfy_constraint_expression (tree t, tree args, sat_info info)
       ninfo.initial_parms = TREE_TYPE (t);
       norm = normalize_constraint_expression (TREE_OPERAND (t, 0), ninfo);
     }
-  else if (EXPR_P (t))
-    {
-      norm_info ninfo (info.noisy () ? tf_norm : tf_none);
-      norm = normalize_constraint_expression (t, ninfo);
-    }
   else if (is_auto (t))
     {
       norm = normalize_placeholder_type_constraints (t, info.noisy ());
@@ -3047,23 +3015,16 @@ satisfy_constraint_expression (tree t, tree args, sat_info info)
     gcc_unreachable ();
 
   /* Perform satisfaction.  */
-  return satisfy_constraint (norm, args, info);
+  return satisfy_normalized_constraints (norm, args, info);
 }
 
-/* Used only to evaluate requires-expressions during constant expression
-   evaluation.  */
-
-tree
-satisfy_constraint_expression (tree expr)
-{
-  sat_info info (tf_none, NULL_TREE);
-  return satisfy_constraint_expression (expr, NULL_TREE, info);
-}
+/* Evaluate the associated constraints of the template specialization T
+   according to INFO, returning a satisfaction value.  */
 
 static tree
 satisfy_declaration_constraints (tree t, sat_info info)
 {
-  gcc_assert (DECL_P (t));
+  gcc_assert (DECL_P (t) && TREE_CODE (t) != TEMPLATE_DECL);
   const tree saved_t = t;
 
   /* For inherited constructors, consider the original declaration;
@@ -3083,26 +3044,24 @@ satisfy_declaration_constraints (tree t, sat_info info)
     if (tree *result = hash_map_safe_get (decl_satisfied_cache, saved_t))
       return *result;
 
-  /* Get the normalized constraints.  */
-  tree norm = NULL_TREE;
   tree args = NULL_TREE;
   if (tree ti = DECL_TEMPLATE_INFO (t))
     {
-      tree tmpl = TI_TEMPLATE (ti);
-      norm = normalize_template_requirements (tmpl, info.noisy ());
-
       /* The initial parameter mapping is the complete set of
 	 template arguments substituted into the declaration.  */
       args = TI_ARGS (ti);
       if (inh_ctor_targs)
 	args = add_outermost_template_args (args, inh_ctor_targs);
-    }
-  else
-    {
-      /* These should be empty until we allow constraints on non-templates.  */
-      norm = normalize_nontemplate_requirements (t, info.noisy ());
+
+      /* If any arguments depend on template parameters, we can't
+	 check constraints. Pretend they're satisfied for now.  */
+      if (uses_template_parms (args))
+	return boolean_true_node;
     }
 
+  /* Get the normalized constraints.  */
+  tree norm = get_normalized_constraints_from_decl (t, info.noisy ());
+
   unsigned ftc_count = vec_safe_length (failed_type_completions);
 
   tree result = boolean_true_node;
@@ -3111,7 +3070,7 @@ satisfy_declaration_constraints (tree t, sat_info info)
       if (!push_tinst_level (t))
 	return result;
       push_access_scope (t);
-      result = satisfy_associated_constraints (norm, args, info);
+      result = satisfy_normalized_constraints (norm, args, info);
       pop_access_scope (t);
       pop_tinst_level ();
     }
@@ -3134,6 +3093,10 @@ satisfy_declaration_constraints (tree t, sat_info info)
   return result;
 }
 
+/* Evaluate the associated constraints of the template T using ARGS as the
+   innermost set of template arguments and according to INFO, returning a
+   satisfaction value.  */
+
 static tree
 satisfy_declaration_constraints (tree t, tree args, sat_info info)
 {
@@ -3144,14 +3107,19 @@ satisfy_declaration_constraints (tree t, tree args, sat_info info)
 
   args = add_outermost_template_args (t, args);
 
+  /* If any arguments depend on template parameters, we can't
+     check constraints. Pretend they're satisfied for now.  */
+  if (uses_template_parms (args))
+    return boolean_true_node;
+
   tree result = boolean_true_node;
-  if (tree norm = normalize_template_requirements (t, info.noisy ()))
+  if (tree norm = get_normalized_constraints_from_decl (t, info.noisy ()))
     {
       if (!push_tinst_level (t, args))
 	return result;
       tree pattern = DECL_TEMPLATE_RESULT (t);
       push_access_scope (pattern);
-      result = satisfy_associated_constraints (norm, args, info);
+      result = satisfy_normalized_constraints (norm, args, info);
       pop_access_scope (pattern);
       pop_tinst_level ();
     }
@@ -3159,62 +3127,50 @@ satisfy_declaration_constraints (tree t, tree args, sat_info info)
   return result;
 }
 
+/* A wrapper around satisfy_declaration_constraints and
+   satisfy_nondeclaration_constraints which additionally replays
+   quiet ill-formed satisfaction noisily, so that ill-formed
+   satisfaction always gets diagnosed.  */
+
 static tree
-constraint_satisfaction_value (tree t, sat_info info)
+constraint_satisfaction_value (tree t, tree args, sat_info info)
 {
   tree r;
   if (DECL_P (t))
-    r = satisfy_declaration_constraints (t, info);
+    {
+      if (args)
+	r = satisfy_declaration_constraints (t, args, info);
+      else
+	r = satisfy_declaration_constraints (t, info);
+    }
   else
-    r = satisfy_constraint_expression (t, NULL_TREE, info);
+    r = satisfy_nondeclaration_constraints (t, args, info);
   if (r == error_mark_node && info.quiet ()
       && !(DECL_P (t) && TREE_NO_WARNING (t)))
     {
-      /* Replay the error with re-normalized requirements.  */
+      /* Replay the error noisily.  */
       sat_info noisy (tf_warning_or_error, info.in_decl);
-      constraint_satisfaction_value (t, noisy);
-      if (DECL_P (t))
+      constraint_satisfaction_value (t, args, noisy);
+      if (DECL_P (t) && !args)
 	/* Avoid giving these errors again.  */
 	TREE_NO_WARNING (t) = true;
     }
   return r;
 }
 
-static tree
-constraint_satisfaction_value (tree t, tree args, sat_info info)
-{
-  tree r;
-  if (DECL_P (t))
-    r = satisfy_declaration_constraints (t, args, info);
-  else
-    r = satisfy_constraint_expression (t, args, info);
-  if (r == error_mark_node && info.quiet ())
-    {
-      /* Replay the error with re-normalized requirements.  */
-      sat_info noisy (tf_warning_or_error, info.in_decl);
-      constraint_satisfaction_value (t, args, noisy);
-    }
-  return r;
-}
-
-/* True iff the result of satisfying T is BOOLEAN_TRUE_NODE and false
-   otherwise, even in the case of errors.  */
-
-bool
-constraints_satisfied_p (tree t)
-{
-  if (!flag_concepts)
-    return true;
-
-  sat_info quiet (tf_none, NULL_TREE);
-  return constraint_satisfaction_value (t, quiet) == boolean_true_node;
-}
+/* True iff the result of satisfying T using ARGS is BOOLEAN_TRUE_NODE
+   and false otherwise, even in the case of errors.
 
-/* True iff the result of satisfying T with ARGS is BOOLEAN_TRUE_NODE
-    and false otherwise, even in the case of errors.  */
+   Here, T can be:
+     - a template declaration
+     - a template specialization (in which case ARGS must be empty)
+     - a concept-id (in which case ARGS must be empty)
+     - a nested-requirement
+     - a placeholder 'auto'
+     - a requires-expression.  */
 
 bool
-constraints_satisfied_p (tree t, tree args)
+constraints_satisfied_p (tree t, tree args/*= NULL_TREE */)
 {
   if (!flag_concepts)
     return true;
@@ -3227,7 +3183,7 @@ constraints_satisfied_p (tree t, tree args)
    evaluation of template-ids as id-expressions.  */
 
 tree
-evaluate_concept_check (tree check, tsubst_flags_t complain)
+evaluate_concept_check (tree check)
 {
   if (check == error_mark_node)
     return error_mark_node;
@@ -3236,14 +3192,19 @@ evaluate_concept_check (tree check, tsubst_flags_t complain)
 
   /* Check for satisfaction without diagnostics.  */
   sat_info quiet (tf_none, NULL_TREE);
-  tree result = satisfy_constraint_expression (check, NULL_TREE, quiet);
-  if (result == error_mark_node && (complain & tf_error))
-    {
-      /* Replay the error with re-normalized requirements.  */
-      sat_info noisy (tf_warning_or_error, NULL_TREE);
-      satisfy_constraint_expression (check, NULL_TREE, noisy);
-    }
-  return result;
+  return constraint_satisfaction_value (check, /*args=*/NULL_TREE, quiet);
+}
+
+/* Evaluate the requires-expression T, returning either boolean_true_node
+   or boolean_false_node.  This is used during gimplification and constexpr
+   evaluation.  */
+
+tree
+evaluate_requires_expr (tree t)
+{
+  gcc_assert (TREE_CODE (t) == REQUIRES_EXPR);
+  sat_info quiet (tf_none, NULL_TREE);
+  return constraint_satisfaction_value (t, /*args=*/NULL_TREE, quiet);
 }
 
 /*---------------------------------------------------------------------------
@@ -3709,7 +3670,7 @@ diagnose_nested_requirement (tree req, tree args)
 {
   /* Quietly check for satisfaction first.  */
   sat_info quiet (tf_none, NULL_TREE);
-  tree result = satisfy_constraint_expression (req, args, quiet);
+  tree result = satisfy_nondeclaration_constraints (req, args, quiet);
   if (result == boolean_true_node)
     return;
 
@@ -3721,7 +3682,7 @@ diagnose_nested_requirement (tree req, tree args)
       inform (loc, "nested requirement %qE is not satisfied, because", expr);
 
       sat_info noisy (tf_warning_or_error, NULL_TREE, /*diag_unsat=*/true);
-      satisfy_constraint_expression (req, args, noisy);
+      satisfy_nondeclaration_constraints (req, args, noisy);
     }
   else
     inform (loc, "nested requirement %qE is not satisfied", expr);
@@ -3854,7 +3815,7 @@ diagnosing_failed_constraint::replay_errors_p ()
 }
 
 /* Emit diagnostics detailing the failure ARGS to satisfy the constraints
-   of T. Here, T can be either a constraint or a declaration.  */
+   of T.  Here, T and ARGS are as in constraints_satisfied_p.  */
 
 void
 diagnose_constraints (location_t loc, tree t, tree args)
@@ -3866,8 +3827,13 @@ diagnose_constraints (location_t loc, tree t, tree args)
 
   /* Replay satisfaction, but diagnose unsatisfaction.  */
   sat_info noisy (tf_warning_or_error, NULL_TREE, /*diag_unsat=*/true);
-  if (!args)
-    constraint_satisfaction_value (t, noisy);
+  if (TREE_CODE (t) == REQUIRES_EXPR)
+    {
+      gcc_assert (!args);
+      ++current_constraint_diagnosis_depth;
+      diagnose_requires_expr (t, /*map=*/NULL_TREE, /*in_decl=*/NULL_TREE);
+      --current_constraint_diagnosis_depth;
+    }
   else
     constraint_satisfaction_value (t, args, noisy);
 
diff --git a/gcc/cp/cp-gimplify.c b/gcc/cp/cp-gimplify.c
index abb8a6ef078..df89ff3815b 100644
--- a/gcc/cp/cp-gimplify.c
+++ b/gcc/cp/cp-gimplify.c
@@ -1381,7 +1381,7 @@ cp_genericize_r (tree *stmt_p, int *walk_subtrees, void *data)
 	 normal functions.  */
       if (concept_check_p (stmt))
 	{
-	  *stmt_p = evaluate_concept_check (stmt, tf_warning_or_error);
+	  *stmt_p = evaluate_concept_check (stmt);
 	  * walk_subtrees = 0;
 	  break;
 	}
@@ -1453,15 +1453,14 @@ cp_genericize_r (tree *stmt_p, int *walk_subtrees, void *data)
 
     case REQUIRES_EXPR:
       /* Emit the value of the requires-expression.  */
-      *stmt_p = constant_boolean_node (constraints_satisfied_p (stmt),
-				       boolean_type_node);
+      *stmt_p = evaluate_requires_expr (stmt);
       *walk_subtrees = 0;
       break;
 
     case TEMPLATE_ID_EXPR:
       gcc_assert (concept_check_p (stmt));
       /* Emit the value of the concept check.  */
-      *stmt_p = evaluate_concept_check (stmt, tf_warning_or_error);
+      *stmt_p = evaluate_concept_check (stmt);
       walk_subtrees = 0;
       break;
 
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 544e99538a4..f06ac3c73bf 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -8109,6 +8109,7 @@ extern tree finish_compound_requirement         (location_t, tree, tree, bool);
 extern tree finish_nested_requirement           (location_t, tree);
 extern void check_constrained_friend            (tree, tree);
 extern tree tsubst_requires_expr                (tree, tree, tsubst_flags_t, tree);
+extern tree evaluate_requires_expr		(tree);
 extern tree tsubst_constraint                   (tree, tree, tsubst_flags_t, tree);
 extern tree tsubst_constraint_info              (tree, tree, tsubst_flags_t, tree);
 extern tree tsubst_parameter_mapping		(tree, tree, tsubst_flags_t, tree);
@@ -8123,10 +8124,8 @@ struct processing_constraint_expression_sentinel
 extern bool processing_constraint_expression_p	();
 
 extern tree unpack_concept_check		(tree);
-extern tree evaluate_concept_check              (tree, tsubst_flags_t);
-extern tree satisfy_constraint_expression	(tree);
-extern bool constraints_satisfied_p		(tree);
-extern bool constraints_satisfied_p		(tree, tree);
+extern tree evaluate_concept_check              (tree);
+extern bool constraints_satisfied_p		(tree, tree = NULL_TREE);
 extern bool* lookup_subsumption_result          (tree, tree);
 extern bool save_subsumption_result             (tree, tree, bool);
 extern tree find_template_parameters		(tree, tree);
diff --git a/gcc/cp/cvt.c b/gcc/cp/cvt.c
index e809f0e4068..3f5467c8283 100644
--- a/gcc/cp/cvt.c
+++ b/gcc/cp/cvt.c
@@ -1170,7 +1170,7 @@ convert_to_void (tree expr, impl_conv_void implicit, tsubst_flags_t complain)
   /* Explicitly evaluate void-converted concept checks since their
      satisfaction may produce ill-formed programs.  */
    if (concept_check_p (expr))
-     expr = evaluate_concept_check (expr, tf_warning_or_error);
+     expr = evaluate_concept_check (expr);
 
   if (VOID_TYPE_P (TREE_TYPE (expr)))
     return expr;
-- 
2.31.0.rc0.75.gec125d1bc1


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 6/6] c++: Consolidate REQUIRES_EXPR evaluation/diagnostic routines
  2021-03-02  2:18   ` Jason Merrill
@ 2021-03-02 16:45     ` Patrick Palka
  2021-03-02 23:04       ` Jason Merrill
  0 siblings, 1 reply; 29+ messages in thread
From: Patrick Palka @ 2021-03-02 16:45 UTC (permalink / raw)
  To: Jason Merrill; +Cc: Patrick Palka, gcc-patches

On Mon, 1 Mar 2021, Jason Merrill wrote:

> On 2/28/21 12:59 PM, Patrick Palka wrote:
> > This folds the diagnose_requires_expr routines into the corresponding
> > tsubst_requires_expr ones.  This is achieved by making the latter
> > routines take a sat_info instead of a subst_info, and assigning the
> > appropriate meanings to the flags sat_info::noisy and
> > sat_info::diagnose_unsatisfaction_p during tsubst_requires_expr:
> > info.noisy() controls whether to diagnose invalid types and expressions
> > inside the requires-expression, and info.diagnose_unsatisfaction_p()
> > controls whether to diagnose why the requires-expression evaluates to
> > false.
> > 
> > gcc/cp/ChangeLog:
> > 
> > 	* constraint.cc (struct sat_info): Document the different
> > 	meanings of noisy() and diagnose_unsatisfaction_p() during
> > 	satisfaction and requires-expression evaluation.
> > 	(tsubst_valid_expression_requirement): Take a sat_info instead
> > 	of a subst_info.  Perform the substitution quietly first.  Fold
> > 	in error-replaying code from diagnose_valid_expression.
> > 	(tsubst_simple_requirement): Take a sat_info instead of a
> > 	subst_info.
> > 	(tsubst_type_requirement_1): New.  Fold in error-replaying code
> > 	from diagnose_valid_type.
> > 	(tsubst_type_requirement): Use the above.  Take a sat_info
> > 	instead of a subst_info.
> > 	(tsubst_compound_requirement): Likewise.  Fold in
> > 	error-replaying code from diagnose_compound_requirement.
> > 	(tsubst_nested_requirement): Take a sat_info instead of a
> > 	subst_info.  Fold in error-replaying code from
> > 	diagnose_nested_requirement.
> > 	(tsubst_requirement): Take a sat_info instead of a subst_info.
> > 	(tsubst_requires_expr): Split into two versions, one that takes
> > 	a sat_info argument and another that takes a complain and
> > 	in_decl argument.  Remove outdated documentation.  Document the
> > 	effects of the sat_info argument.
> > 	(diagnose_trait_expr): Make static.  Take a template argument
> > 	vector instead of a parameter mapping.
> > 	(diagnose_valid_expression): Remove.
> > 	(diagnose_valid_type): Remove.
> > 	(diagnose_simple_requirement): Remove.
> > 	(diagnose_compound_requirement): Remove.
> > 	(diagnose_type_requirement): Remove.
> > 	(diagnose_nested_requirement): Remove.
> > 	(diagnose_requirement): Remove.
> > 	(diagnose_requires_expr): Remove.
> > 	(diagnose_atomic_constraint): Take a sat_info instead of a
> > 	subst_info.  Adjust call to diagnose_trait_expr.  Call
> > 	tsubst_requires_expr instead of diagnose_requires_expr.
> > 	(diagnose_constraints): Call tsubst_requires_expr instead of
> > 	diagnose_requires_expr.
> > 
> > gcc/testsuite/ChangeLog:
> > 
> > 	* g++.dg/concepts/diagnostic1.C: Adjust expected diagnostics
> > 	now that we diagnose only the first failed requirement of a
> > 	requires-expression.
> > ---
> >   gcc/cp/constraint.cc                        | 416 +++++++++-----------
> >   gcc/testsuite/g++.dg/concepts/diagnostic1.C |   2 +-
> >   2 files changed, 179 insertions(+), 239 deletions(-)
> > 
> > diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> > index cf319b34da0..31f32c25dfe 100644
> > --- a/gcc/cp/constraint.cc
> > +++ b/gcc/cp/constraint.cc
> > @@ -100,17 +100,30 @@ struct subst_info
> >     /* Provides additional context for satisfaction.
> >   -   The flag noisy() controls whether to diagnose ill-formed satisfaction,
> > -   such as the satisfaction value of an atom being non-bool or
> > non-constant.
> > -
> > -   The flag diagnose_unsatisfaction_p() controls whether to explain why
> > -   a constraint is not satisfied.
> > -
> > -   The entrypoints to satisfaction for which we set noisy+unsat are
> > -   diagnose_constraints and diagnose_nested_requirement.  The entrypoint
> > for
> > -   which we set noisy-unsat is the replay inside
> > constraint_satisfaction_value.
> > -   From constraints_satisfied_p, we enter satisfaction quietly (both flags
> > -   cleared).  */
> > +   During satisfaction:
> > +    - The flag noisy() controls whether to diagnose ill-formed
> > satisfaction,
> > +      such as the satisfaction value of an atom being non-bool or
> > non-constant.
> > +    - The flag diagnose_unsatisfaction_p() controls whether to explain why
> > +      a constraint is not satisfied.
> > +    - We enter satisfaction with noisy+unsat from diagnose_constraints.
> > +    - We enter satisfaction with noisy-unsat from the replay inside
> > +      constraint_satisfaction_value.
> > +    - We enter satisfaction quietly (both flags cleared) from
> > +      constraints_satisfied_p.
> > +
> > +   During evaluation of a requires-expression:
> > +    - The flag noisy() controls whether to diagnose ill-formed types and
> > +      expressions inside its requirements.
> > +    - The flag diagnose_unsatisfaction_p() controls whether to explain why
> > +      the requires-expression evaluates to false.
> > +    - We enter tsubst_requires_expr with noisy+unsat from
> > diagnose_constraints
> > +      and from diagnose_atomic_constraint.
> > +    - We enter tsubst_requires_expr with noisy-unsat from
> > +      cp_parser_requires_expression when processing a requires-expression
> > that
> > +      appears outside a template.
> > +    - We enter tsubst_requires_expr quietly (both flags cleared) when
> > +      substituting through a requires-expression as part of template
> > +      instantiation.  */
> >     struct sat_info : subst_info
> >   {
> > @@ -1926,22 +1939,44 @@ hash_placeholder_constraint (tree c)
> >     return val;
> >   }
> >   -/* Substitute through the simple requirement.  */
> > +/* Substitute through the expression of a simple requirement or
> > +   compound requirement.  */
> >     static tree
> > -tsubst_valid_expression_requirement (tree t, tree args, subst_info info)
> > +tsubst_valid_expression_requirement (tree t, tree args, sat_info info)
> >   {
> > -  tree r = tsubst_expr (t, args, info.complain, info.in_decl, false);
> > -  if (convert_to_void (r, ICV_STATEMENT, info.complain) == error_mark_node)
> > -    return error_mark_node;
> > -  return r;
> > +  tree r = tsubst_expr (t, args, tf_none, info.in_decl, false);
> > +  if (convert_to_void (r, ICV_STATEMENT, tf_none) != error_mark_node)
> > +    return r;
> > +
> > +  if (info.diagnose_unsatisfaction_p ())
> > +    {
> > +      location_t loc = cp_expr_loc_or_input_loc (t);
> > +      if (diagnosing_failed_constraint::replay_errors_p ())
> > +	{
> > +	  inform (loc, "the required expression %qE is invalid, because", t);
> > +	  if (r == error_mark_node)
> > +	    tsubst_expr (t, args, info.complain, info.in_decl, false);
> > +	  else
> > +	    convert_to_void (r, ICV_STATEMENT, info.complain);
> > +	}
> > +      else
> > +	inform (loc, "the required expression %qE is invalid", t);
> > +    }
> > +  else if (info.noisy ())
> > +    {
> > +      r = tsubst_expr (t, args, info.complain, info.in_decl, false);
> > +      convert_to_void (r, ICV_STATEMENT, info.complain);
> > +    }
> > +
> > +  return error_mark_node;
> >   }
> >       /* Substitute through the simple requirement.  */
> >     static tree
> > -tsubst_simple_requirement (tree t, tree args, subst_info info)
> > +tsubst_simple_requirement (tree t, tree args, sat_info info)
> >   {
> >     tree t0 = TREE_OPERAND (t, 0);
> >     tree expr = tsubst_valid_expression_requirement (t0, args, info);
> > @@ -1950,13 +1985,41 @@ tsubst_simple_requirement (tree t, tree args,
> > subst_info info)
> >     return boolean_true_node;
> >   }
> >   +/* Subroutine of tsubst_type_requirement that performs the actual
> > substitution
> > +   and diagnosing.  Also used by tsubst_compound_requirement.  */
> > +
> > +static tree
> > +tsubst_type_requirement_1 (tree t, tree args, sat_info info, location_t
> > loc)
> > +{
> > +  tree r = tsubst (t, args, tf_none, info.in_decl);
> > +  if (r != error_mark_node)
> > +    return r;
> > +
> > +  if (info.diagnose_unsatisfaction_p ())
> > +    {
> > +      if (diagnosing_failed_constraint::replay_errors_p ())
> > +	{
> > +	  /* Replay the substitution error.  */
> > +	  inform (loc, "the required type %qT is invalid, because", t);
> > +	  tsubst (t, args, info.complain, info.in_decl);
> > +	}
> > +      else
> > +	inform (loc, "the required type %qT is invalid", t);
> > +    }
> > +  else if (info.noisy ())
> > +    tsubst (t, args, info.complain, info.in_decl);
> > +
> > +  return error_mark_node;
> > +}
> > +
> > +
> >   /* Substitute through the type requirement.  */
> >     static tree
> > -tsubst_type_requirement (tree t, tree args, subst_info info)
> > +tsubst_type_requirement (tree t, tree args, sat_info info)
> >   {
> >     tree t0 = TREE_OPERAND (t, 0);
> > -  tree type = tsubst (t0, args, info.complain, info.in_decl);
> > +  tree type = tsubst_type_requirement_1 (t0, args, info, EXPR_LOCATION
> > (t));
> >     if (type == error_mark_node)
> >       return error_mark_node;
> >     return boolean_true_node;
> > @@ -2013,7 +2076,7 @@ expression_convertible_p (tree expr, tree type,
> > subst_info info)
> >   /* Substitute through the compound requirement.  */
> >     static tree
> > -tsubst_compound_requirement (tree t, tree args, subst_info info)
> > +tsubst_compound_requirement (tree t, tree args, sat_info info)
> >   {
> >     tree t0 = TREE_OPERAND (t, 0);
> >     tree t1 = TREE_OPERAND (t, 1);
> > @@ -2021,13 +2084,20 @@ tsubst_compound_requirement (tree t, tree args,
> > subst_info info)
> >     if (expr == error_mark_node)
> >       return error_mark_node;
> >   +  location_t loc = cp_expr_loc_or_input_loc (expr);
> > +
> >     /* Check the noexcept condition.  */
> >     bool noexcept_p = COMPOUND_REQ_NOEXCEPT_P (t);
> >     if (noexcept_p && !expr_noexcept_p (expr, tf_none))
> > -    return error_mark_node;
> > +    {
> > +      if (info.diagnose_unsatisfaction_p ())
> > +	inform (loc, "%qE is not %<noexcept%>", expr);
> > +      else
> > +	return error_mark_node;
> > +    }
> >       /* Substitute through the type expression, if any.  */
> > -  tree type = tsubst (t1, args, info.complain, info.in_decl);
> > +  tree type = tsubst_type_requirement_1 (t1, args, info, EXPR_LOCATION
> > (t));
> >     if (type == error_mark_node)
> >       return error_mark_node;
> >   @@ -2039,29 +2109,76 @@ tsubst_compound_requirement (tree t, tree args,
> > subst_info info)
> >         if (tree placeholder = type_uses_auto (type))
> >   	{
> >   	  if (!type_deducible_p (expr, type, placeholder, args, quiet))
> > -	    return error_mark_node;
> > +	    {
> > +	      if (info.diagnose_unsatisfaction_p ())
> > +		{
> > +		  if (diagnosing_failed_constraint::replay_errors_p ())
> > +		    {
> > +		      inform (loc,
> > +			      "%qE does not satisfy return-type-requirement, "
> > +			      "because", t0);
> > +		      /* Further explain the reason for the error.  */
> > +		      type_deducible_p (expr, type, placeholder, args, info);
> > +		    }
> > +		  else
> > +		    inform (loc,
> > +			    "%qE does not satisfy return-type-requirement",
> > t0);
> > +		}
> > +	      return error_mark_node;
> > +	    }
> >   	}
> >         else if (!expression_convertible_p (expr, type, quiet))
> > -	return error_mark_node;
> > +	{
> > +	  if (info.diagnose_unsatisfaction_p ())
> > +	    {
> > +	      if (diagnosing_failed_constraint::replay_errors_p ())
> > +		{
> > +		  inform (loc, "cannot convert %qE to %qT because", t0, type);
> > +		  /* Further explain the reason for the error.  */
> > +		  expression_convertible_p (expr, type, info);
> > +		}
> > +	      else
> > +		inform (loc, "cannot convert %qE to %qT", t0, type);
> > +	    }
> > +	  return error_mark_node;
> > +	}
> >       }
> >       return boolean_true_node;
> >   }
> >   +/* Substitute through the nested requirement.  */
> > +
> >   static tree
> > -tsubst_nested_requirement (tree t, tree args, subst_info info)
> > +tsubst_nested_requirement (tree t, tree args, sat_info info)
> >   {
> >     sat_info quiet (tf_none, info.in_decl);
> >     tree result = constraint_satisfaction_value (t, args, quiet);
> > -  if (result != boolean_true_node)
> > -    return error_mark_node;
> > -  return boolean_true_node;
> > +  if (result == boolean_true_node)
> > +    return boolean_true_node;
> > +
> > +  if (result == boolean_false_node
> > +      && info.diagnose_unsatisfaction_p ())
> > +    {
> > +      tree expr = TREE_OPERAND (t, 0);
> > +      location_t loc = cp_expr_location (t);
> > +      if (diagnosing_failed_constraint::replay_errors_p ())
> > +	{
> > +	  /* Replay the substitution error.  */
> > +	  inform (loc, "nested requirement %qE is not satisfied, because",
> > expr);
> > +	  constraint_satisfaction_value (t, args, info);
> > +	}
> > +      else
> > +	inform (loc, "nested requirement %qE is not satisfied", expr);
> > +    }
> > +
> > +  return error_mark_node;
> >   }
> >     /* Substitute ARGS into the requirement T.  */
> >     static tree
> > -tsubst_requirement (tree t, tree args, subst_info info)
> > +tsubst_requirement (tree t, tree args, sat_info info)
> >   {
> >     iloc_sentinel loc_s (cp_expr_location (t));
> >     switch (TREE_CODE (t))
> > @@ -2151,30 +2268,22 @@ tsubst_constraint_variables (tree t, tree args,
> > subst_info info)
> >      in its requirements ... In such cases, the expression evaluates
> >      to false; it does not cause the program to be ill-formed.
> >   -   However, there are cases where substitution must produce a
> > -   new requires-expression, that is not a template constraint.
> > -   For example:
> > +   When substituting through a REQUIRES_EXPR as part of template
> > +   instantiation, we call this routine with info.quiet() true.
> >   -        template<typename T>
> > -        class X {
> > -          template<typename U>
> > -          static constexpr bool var = requires (U u) { T::fn(u); };
> > -        };
> > +   When evaluating a REQUIRES_EXPR that appears outside a template in
> > +   cp_parser_requires_expression, we call this routine with
> > +   info.noisy() true.
> >   -   In the instantiation of X<Y> (assuming Y defines fn), then the
> > -   instantiated requires-expression would include Y::fn(u). If any
> > -   substitution in the requires-expression fails, we can immediately
> > -   fold the expression to false, as would be the case e.g., when
> > -   instantiation X<int>.  */
> > +   Finally, when diagnosing unsatisfaction from diagnose_atomic_constraint
> > +   and when diagnosing a false REQUIRES_EXPR via diagnose_constraints,
> > +   we call this routine with info.diagnose_unsatisfaction_p() true.  */
> >   -tree
> > -tsubst_requires_expr (tree t, tree args,
> > -		      tsubst_flags_t complain, tree in_decl)
> > +static tree
> > +tsubst_requires_expr (tree t, tree args, sat_info info)
> >   {
> >     local_specialization_stack stack (lss_copy);
> >   -  subst_info info (complain, in_decl);
> > -
> >     /* A requires-expression is an unevaluated context.  */
> >     cp_unevaluated u;
> >   @@ -2186,7 +2295,7 @@ tsubst_requires_expr (tree t, tree args,
> >   	 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);
> > +      REQUIRES_EXPR_EXTRA_ARGS (t) = build_extra_args (t, args,
> > info.complain);
> >         return t;
> >       }
> >   @@ -2207,6 +2316,16 @@ tsubst_requires_expr (tree t, tree args,
> >     return boolean_true_node;
> >   }
> >   +/* Public wrapper for the above.  */
> > +
> > +tree
> > +tsubst_requires_expr (tree t, tree args,
> > +		      tsubst_flags_t complain, tree in_decl)
> > +{
> > +  sat_info info (complain, in_decl);
> > +  return tsubst_requires_expr (t, args, info);
> > +}
> > +
> >   /* Substitute ARGS into the constraint information CI, producing a new
> >      constraint record.  */
> >   @@ -2790,7 +2909,7 @@ get_mapped_args (tree map)
> >     return args;
> >   }
> >   -static void diagnose_atomic_constraint (tree, tree, tree, subst_info);
> > +static void diagnose_atomic_constraint (tree, tree, tree, sat_info);
> >     /* Compute the satisfaction of an atomic constraint.  */
> >   @@ -3440,11 +3559,10 @@ get_constraint_error_location (tree t)
> >     /* Emit a diagnostic for a failed trait.  */
> >   -void
> > -diagnose_trait_expr (tree expr, tree map)
> > +static void
> > +diagnose_trait_expr (tree expr, tree args)
> >   {
> >     location_t loc = cp_expr_location (expr);
> > -  tree args = get_mapped_args (map);
> >       /* Build a "fake" version of the instantiated trait, so we can
> >        get the instantiated types from result.  */
> > @@ -3524,192 +3642,11 @@ diagnose_trait_expr (tree expr, tree map)
> >       }
> >   }
> >   -static tree
> > -diagnose_valid_expression (tree expr, tree args, tree in_decl)
> > -{
> > -  tree result = tsubst_expr (expr, args, tf_none, in_decl, false);
> > -  if (result != error_mark_node
> > -      && convert_to_void (result, ICV_STATEMENT, tf_none) !=
> > error_mark_node)
> > -    return result;
> > -
> > -  location_t loc = cp_expr_loc_or_input_loc (expr);
> > -  if (diagnosing_failed_constraint::replay_errors_p ())
> > -    {
> > -      /* Replay the substitution error.  */
> > -      inform (loc, "the required expression %qE is invalid, because",
> > expr);
> > -      if (result == error_mark_node)
> > -	tsubst_expr (expr, args, tf_error, in_decl, false);
> > -      else
> > -	convert_to_void (result, ICV_STATEMENT, tf_error);
> > -    }
> > -  else
> > -    inform (loc, "the required expression %qE is invalid", expr);
> > -
> > -  return error_mark_node;
> > -}
> > -
> > -static tree
> > -diagnose_valid_type (tree type, tree args, tree in_decl)
> > -{
> > -  tree result = tsubst (type, args, tf_none, in_decl);
> > -  if (result != error_mark_node)
> > -    return result;
> > -
> > -  location_t loc = cp_expr_loc_or_input_loc (type);
> > -  if (diagnosing_failed_constraint::replay_errors_p ())
> > -    {
> > -      /* Replay the substitution error.  */
> > -      inform (loc, "the required type %qT is invalid, because", type);
> > -      tsubst (type, args, tf_error, in_decl);
> > -    }
> > -  else
> > -    inform (loc, "the required type %qT is invalid", type);
> > -
> > -  return error_mark_node;
> > -}
> > -
> > -static void
> > -diagnose_simple_requirement (tree req, tree args, tree in_decl)
> > -{
> > -  diagnose_valid_expression (TREE_OPERAND (req, 0), args, in_decl);
> > -}
> > -
> > -static void
> > -diagnose_compound_requirement (tree req, tree args, tree in_decl)
> > -{
> > -  tree expr = TREE_OPERAND (req, 0);
> > -  expr = diagnose_valid_expression (expr, args, in_decl);
> > -  if (expr == error_mark_node)
> > -    return;
> > -
> > -  location_t loc = cp_expr_loc_or_input_loc (expr);
> > -
> > -  /* Check the noexcept condition.  */
> > -  if (COMPOUND_REQ_NOEXCEPT_P (req) && !expr_noexcept_p (expr, tf_none))
> > -    inform (loc, "%qE is not %<noexcept%>", expr);
> > -
> > -  tree type = TREE_OPERAND (req, 1);
> > -  type = diagnose_valid_type (type, args, in_decl);
> > -  if (type == error_mark_node)
> > -    return;
> > -
> > -  if (type)
> > -    {
> > -      subst_info quiet (tf_none, in_decl);
> > -      subst_info noisy (tf_error, in_decl);
> > -
> > -      /* Check the expression against the result type.  */
> > -      if (tree placeholder = type_uses_auto (type))
> > -	{
> > -	  if (!type_deducible_p (expr, type, placeholder, args, quiet))
> > -	    {
> > -	      tree orig_expr = TREE_OPERAND (req, 0);
> > -	      if (diagnosing_failed_constraint::replay_errors_p ())
> > -		{
> > -		  inform (loc,
> > -			  "%qE does not satisfy return-type-requirement, "
> > -			  "because", orig_expr);
> > -		  /* Further explain the reason for the error.  */
> > -		  type_deducible_p (expr, type, placeholder, args, noisy);
> > -		}
> > -	      else
> > -		inform (loc, "%qE does not satisfy return-type-requirement",
> > -			orig_expr);
> > -	    }
> > -	}
> > -      else if (!expression_convertible_p (expr, type, quiet))
> > -	{
> > -	  tree orig_expr = TREE_OPERAND (req, 0);
> > -	  if (diagnosing_failed_constraint::replay_errors_p ())
> > -	    {
> > -	      inform (loc, "cannot convert %qE to %qT because", orig_expr,
> > type);
> > -	      /* Further explain the reason for the error.  */
> > -	      expression_convertible_p (expr, type, noisy);
> > -	    }
> > -	  else
> > -	    inform (loc, "cannot convert %qE to %qT", orig_expr, type);
> > -	}
> > -    }
> > -}
> > -
> > -static void
> > -diagnose_type_requirement (tree req, tree args, tree in_decl)
> > -{
> > -  tree type = TREE_OPERAND (req, 0);
> > -  diagnose_valid_type (type, args, in_decl);
> > -}
> > -
> > -static void
> > -diagnose_nested_requirement (tree req, tree args)
> > -{
> > -  /* Quietly check for satisfaction first.  */
> > -  sat_info quiet (tf_none, NULL_TREE);
> > -  tree result = satisfy_nondeclaration_constraints (req, args, quiet);
> > -  if (result == boolean_true_node)
> > -    return;
> > -
> > -  tree expr = TREE_OPERAND (req, 0);
> > -  location_t loc = cp_expr_location (expr);
> > -  if (diagnosing_failed_constraint::replay_errors_p ())
> > -    {
> > -      /* Replay the substitution error with re-normalized requirements.  */
> > -      inform (loc, "nested requirement %qE is not satisfied, because",
> > expr);
> > -
> > -      sat_info noisy (tf_warning_or_error, NULL_TREE, /*diag_unsat=*/true);
> > -      satisfy_nondeclaration_constraints (req, args, noisy);
> > -    }
> > -  else
> > -    inform (loc, "nested requirement %qE is not satisfied", expr);
> > -
> > -}
> > -
> > -static void
> > -diagnose_requirement (tree req, tree args, tree in_decl)
> > -{
> > -  iloc_sentinel loc_s (cp_expr_location (req));
> > -  switch (TREE_CODE (req))
> > -    {
> > -    case SIMPLE_REQ:
> > -      return diagnose_simple_requirement (req, args, in_decl);
> > -    case COMPOUND_REQ:
> > -      return diagnose_compound_requirement (req, args, in_decl);
> > -    case TYPE_REQ:
> > -      return diagnose_type_requirement (req, args, in_decl);
> > -    case NESTED_REQ:
> > -      return diagnose_nested_requirement (req, args);
> > -    default:
> > -       gcc_unreachable ();
> > -    }
> > -}
> > -
> > -static void
> > -diagnose_requires_expr (tree expr, tree map, tree in_decl)
> > -{
> > -  local_specialization_stack stack (lss_copy);
> > -  tree parms = TREE_OPERAND (expr, 0);
> > -  tree body = TREE_OPERAND (expr, 1);
> > -  tree args = get_mapped_args (map);
> > -
> > -  cp_unevaluated u;
> > -  subst_info info (tf_warning_or_error, NULL_TREE);
> > -  tree vars = tsubst_constraint_variables (parms, args, info);
> > -  if (vars == error_mark_node)
> > -    return;
> > -
> > -  tree p = body;
> > -  while (p)
> > -    {
> > -      tree req = TREE_VALUE (p);
> > -      diagnose_requirement (req, args, in_decl);
> > -      p = TREE_CHAIN (p);
> > -    }
> > -}
> > -
> >   /* Diagnose a substitution failure in the atomic constraint T when applied
> >      with the instantiated parameter mapping MAP.  */
> >     static void
> > -diagnose_atomic_constraint (tree t, tree map, tree result, subst_info info)
> > +diagnose_atomic_constraint (tree t, tree map, tree result, sat_info info)
> >   {
> >     /* If the constraint is already ill-formed, we've previously diagnosed
> >        the reason. We should still say why the constraints aren't satisfied.
> > */
> > @@ -3730,13 +3667,16 @@ diagnose_atomic_constraint (tree t, tree map, tree
> > result, subst_info info)
> >     /* Generate better diagnostics for certain kinds of expressions.  */
> >     tree expr = ATOMIC_CONSTR_EXPR (t);
> >     STRIP_ANY_LOCATION_WRAPPER (expr);
> > +  tree args = get_mapped_args (map);
> >     switch (TREE_CODE (expr))
> >       {
> >       case TRAIT_EXPR:
> > -      diagnose_trait_expr (expr, map);
> > +      diagnose_trait_expr (expr, args);
> >         break;
> >       case REQUIRES_EXPR:
> > -      diagnose_requires_expr (expr, map, info.in_decl);
> > +      gcc_checking_assert (info.diagnose_unsatisfaction_p ());
> > +      info.in_decl = NULL_TREE;
> > +      tsubst_requires_expr (expr, args, info);
> >         break;
> >       default:
> >         if (!same_type_p (TREE_TYPE (result), boolean_type_node))
> > @@ -3807,7 +3747,7 @@ diagnose_constraints (location_t loc, tree t, tree
> > args)
> >       {
> >         gcc_assert (!args);
> >         ++current_constraint_diagnosis_depth;
> > -      diagnose_requires_expr (t, /*map=*/NULL_TREE, /*in_decl=*/NULL_TREE);
> > +      tsubst_requires_expr (t, /*args=*/NULL_TREE, noisy);
> >         --current_constraint_diagnosis_depth;
> >       }
> >     else
> > diff --git a/gcc/testsuite/g++.dg/concepts/diagnostic1.C
> > b/gcc/testsuite/g++.dg/concepts/diagnostic1.C
> > index 29c78c4c730..23bd592411e 100644
> > --- a/gcc/testsuite/g++.dg/concepts/diagnostic1.C
> > +++ b/gcc/testsuite/g++.dg/concepts/diagnostic1.C
> > @@ -8,7 +8,7 @@ concept bool SameAs = __is_same_as(T, U);
> >   template <class T>
> >   concept bool R1 = requires (T& t) { // { dg-message "in requirements" }
> >     { t.begin() } -> T;		// { dg-error "no match" }
> > -  { t.end() } -> SameAs<T*>;	// { dg-message "does not satisfy" }
> > +  { t.end() } -> SameAs<T*>;
> 
> Are we no longer giving a message for this line?  That seems like a diagnostic
> quality regression.

This happens because diagnose_requires_expr didn't short-circuit its
processing of requirements upon seeing a failed requirement, and this
behavior got lost when it was merged with tsubst_requires_expr, which
does short-circuit.  I wasn't sure if we wanted to keep this behavior or
not :)

The below restores the previous non-short-circuiting behavior of
diagnose_requires_expr inside tsubst_requires_expr, and addresses the
TODO added by v2 of patch5/6.

-- >8 --

Subject: [PATCH] c++: Unify REQUIRES_EXPR evaluation / diagnostic routines

This folds the diagnose_requires_expr routines into the corresponding
tsubst_requires_expr ones.  This is achieved by making the latter
routines take a sat_info instead of a subst_info, and assigning the
appropriate meanings to the flags sat_info::noisy and
sat_info::diagnose_unsatisfaction_p during tsubst_requires_expr:
info.noisy() controls whether to diagnose invalid types and expressions
inside the requirements, and info.diagnose_unsatisfaction_p() controls
whether to additionally diagnose why the requires-expression evaluates
to false.

gcc/cp/ChangeLog:

	* constraint.cc (struct sat_info): Document the different
	meanings of noisy() and diagnose_unsatisfaction_p() during
	satisfaction and requires-expression evaluation.
	(tsubst_valid_expression_requirement): Take a sat_info instead
	of a subst_info.  Perform the substitution quietly first.  Fold
	in error-replaying code from diagnose_valid_expression.
	(tsubst_simple_requirement): Take a sat_info instead of a
	subst_info.
	(tsubst_type_requirement_1): New.  Fold in error-replaying code
	from diagnose_valid_type.
	(tsubst_type_requirement): Use the above.  Take a sat_info
	instead of a subst_info.
	(tsubst_compound_requirement): Likewise.  Fold in
	error-replaying code from diagnose_compound_requirement.
	(tsubst_nested_requirement): Take a sat_info instead of a
	subst_info.  Fold in error-replaying code from
	diagnose_nested_requirement.
	(tsubst_requirement): Take a sat_info instead of a subst_info.
	(tsubst_requires_expr): Split into two versions, one that takes
	a sat_info argument and another that takes a complain and
	in_decl argument.  Remove outdated documentation.  Document the
	effects of the sat_info argument.  Don't short-circuit
	processing of requirements when diagnosing unsatisfaction,
	mirroring diagnose_requires_expr.
	(satisfy_nondeclaration_constraint) <case REQUIRES_EXPR>: Remove
	assert, and se the three-parameter version of tsubst_requires_expr.
	(diagnose_trait_expr): Make static.  Take a template argument
	vector instead of a parameter mapping.
	(diagnose_valid_expression): Remove.
	(diagnose_valid_type): Remove.
	(diagnose_simple_requirement): Remove.
	(diagnose_compound_requirement): Remove.
	(diagnose_type_requirement): Remove.
	(diagnose_nested_requirement): Remove.
	(diagnose_requirement): Remove.
	(diagnose_requires_expr): Remove.
	(diagnose_atomic_constraint): Take a sat_info instead of a
	subst_info.  Adjust call to diagnose_trait_expr.  Call
	tsubst_requires_expr instead of diagnose_requires_expr.
	(diagnose_constraints): Remove special casing of REQUIRES_EXPR
	and just always call constraint_satisfaction_value.
---
 gcc/cp/constraint.cc | 444 ++++++++++++++++++-------------------------
 1 file changed, 189 insertions(+), 255 deletions(-)

diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index 0949788aa29..b2d25f3c232 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -100,17 +100,30 @@ struct subst_info
 
 /* Provides additional context for satisfaction.
 
-   The flag noisy() controls whether to diagnose ill-formed satisfaction,
-   such as the satisfaction value of an atom being non-bool or non-constant.
-
-   The flag diagnose_unsatisfaction_p() controls whether to explain why
-   a constraint is not satisfied.
-
-   The entrypoints to satisfaction for which we set noisy+unsat are
-   diagnose_constraints and diagnose_nested_requirement.  The entrypoint for
-   which we set noisy-unsat is the replay inside constraint_satisfaction_value.
-   From constraints_satisfied_p, we enter satisfaction quietly (both flags
-   cleared).  */
+   During satisfaction:
+    - The flag noisy() controls whether to diagnose ill-formed satisfaction,
+      such as the satisfaction value of an atom being non-bool or non-constant.
+    - The flag diagnose_unsatisfaction_p() controls whether to additionally
+      explain why a constraint is not satisfied.
+    - We enter satisfaction with noisy+unsat from diagnose_constraints.
+    - We enter satisfaction with noisy-unsat from the replay inside
+      constraint_satisfaction_value.
+    - We enter satisfaction quietly (both flags cleared) from
+      constraints_satisfied_p.
+
+   During evaluation of a requires-expression:
+    - The flag noisy() controls whether to diagnose ill-formed types and
+      expressions inside its requirements.
+    - The flag diagnose_unsatisfaction_p() controls whether to additionally
+      explain why the requires-expression evaluates to false.
+    - We enter tsubst_requires_expr with noisy+unsat from diagnose_constraints
+      and from diagnose_atomic_constraint.
+    - We enter tsubst_requires_expr with noisy-unsat from
+      cp_parser_requires_expression when processing a requires-expression that
+      appears outside a template.
+    - We enter tsubst_requires_expr quietly (both flags cleared) when
+      substituting through a requires-expression as part of template
+      instantiation.  */
 
 struct sat_info : subst_info
 {
@@ -1926,22 +1939,44 @@ hash_placeholder_constraint (tree c)
   return val;
 }
 
-/* Substitute through the simple requirement.  */
+/* Substitute through the expression of a simple requirement or
+   compound requirement.  */
 
 static tree
-tsubst_valid_expression_requirement (tree t, tree args, subst_info info)
+tsubst_valid_expression_requirement (tree t, tree args, sat_info info)
 {
-  tree r = tsubst_expr (t, args, info.complain, info.in_decl, false);
-  if (convert_to_void (r, ICV_STATEMENT, info.complain) == error_mark_node)
-    return error_mark_node;
-  return r;
+  tree r = tsubst_expr (t, args, tf_none, info.in_decl, false);
+  if (convert_to_void (r, ICV_STATEMENT, tf_none) != error_mark_node)
+    return r;
+
+  if (info.diagnose_unsatisfaction_p ())
+    {
+      location_t loc = cp_expr_loc_or_input_loc (t);
+      if (diagnosing_failed_constraint::replay_errors_p ())
+	{
+	  inform (loc, "the required expression %qE is invalid, because", t);
+	  if (r == error_mark_node)
+	    tsubst_expr (t, args, info.complain, info.in_decl, false);
+	  else
+	    convert_to_void (r, ICV_STATEMENT, info.complain);
+	}
+      else
+	inform (loc, "the required expression %qE is invalid", t);
+    }
+  else if (info.noisy ())
+    {
+      r = tsubst_expr (t, args, info.complain, info.in_decl, false);
+      convert_to_void (r, ICV_STATEMENT, info.complain);
+    }
+
+  return error_mark_node;
 }
 
 
 /* Substitute through the simple requirement.  */
 
 static tree
-tsubst_simple_requirement (tree t, tree args, subst_info info)
+tsubst_simple_requirement (tree t, tree args, sat_info info)
 {
   tree t0 = TREE_OPERAND (t, 0);
   tree expr = tsubst_valid_expression_requirement (t0, args, info);
@@ -1950,13 +1985,41 @@ tsubst_simple_requirement (tree t, tree args, subst_info info)
   return boolean_true_node;
 }
 
+/* Subroutine of tsubst_type_requirement that performs the actual substitution
+   and diagnosing.  Also used by tsubst_compound_requirement.  */
+
+static tree
+tsubst_type_requirement_1 (tree t, tree args, sat_info info, location_t loc)
+{
+  tree r = tsubst (t, args, tf_none, info.in_decl);
+  if (r != error_mark_node)
+    return r;
+
+  if (info.diagnose_unsatisfaction_p ())
+    {
+      if (diagnosing_failed_constraint::replay_errors_p ())
+	{
+	  /* Replay the substitution error.  */
+	  inform (loc, "the required type %qT is invalid, because", t);
+	  tsubst (t, args, info.complain, info.in_decl);
+	}
+      else
+	inform (loc, "the required type %qT is invalid", t);
+    }
+  else if (info.noisy ())
+    tsubst (t, args, info.complain, info.in_decl);
+
+  return error_mark_node;
+}
+
+
 /* Substitute through the type requirement.  */
 
 static tree
-tsubst_type_requirement (tree t, tree args, subst_info info)
+tsubst_type_requirement (tree t, tree args, sat_info info)
 {
   tree t0 = TREE_OPERAND (t, 0);
-  tree type = tsubst (t0, args, info.complain, info.in_decl);
+  tree type = tsubst_type_requirement_1 (t0, args, info, EXPR_LOCATION (t));
   if (type == error_mark_node)
     return error_mark_node;
   return boolean_true_node;
@@ -2013,7 +2076,7 @@ expression_convertible_p (tree expr, tree type, subst_info info)
 /* Substitute through the compound requirement.  */
 
 static tree
-tsubst_compound_requirement (tree t, tree args, subst_info info)
+tsubst_compound_requirement (tree t, tree args, sat_info info)
 {
   tree t0 = TREE_OPERAND (t, 0);
   tree t1 = TREE_OPERAND (t, 1);
@@ -2021,13 +2084,20 @@ tsubst_compound_requirement (tree t, tree args, subst_info info)
   if (expr == error_mark_node)
     return error_mark_node;
 
+  location_t loc = cp_expr_loc_or_input_loc (expr);
+
   /* Check the noexcept condition.  */
   bool noexcept_p = COMPOUND_REQ_NOEXCEPT_P (t);
   if (noexcept_p && !expr_noexcept_p (expr, tf_none))
-    return error_mark_node;
+    {
+      if (info.diagnose_unsatisfaction_p ())
+	inform (loc, "%qE is not %<noexcept%>", expr);
+      else
+	return error_mark_node;
+    }
 
   /* Substitute through the type expression, if any.  */
-  tree type = tsubst (t1, args, info.complain, info.in_decl);
+  tree type = tsubst_type_requirement_1 (t1, args, info, EXPR_LOCATION (t));
   if (type == error_mark_node)
     return error_mark_node;
 
@@ -2039,29 +2109,76 @@ tsubst_compound_requirement (tree t, tree args, subst_info info)
       if (tree placeholder = type_uses_auto (type))
 	{
 	  if (!type_deducible_p (expr, type, placeholder, args, quiet))
-	    return error_mark_node;
+	    {
+	      if (info.diagnose_unsatisfaction_p ())
+		{
+		  if (diagnosing_failed_constraint::replay_errors_p ())
+		    {
+		      inform (loc,
+			      "%qE does not satisfy return-type-requirement, "
+			      "because", t0);
+		      /* Further explain the reason for the error.  */
+		      type_deducible_p (expr, type, placeholder, args, info);
+		    }
+		  else
+		    inform (loc,
+			    "%qE does not satisfy return-type-requirement", t0);
+		}
+	      return error_mark_node;
+	    }
 	}
       else if (!expression_convertible_p (expr, type, quiet))
-	return error_mark_node;
+	{
+	  if (info.diagnose_unsatisfaction_p ())
+	    {
+	      if (diagnosing_failed_constraint::replay_errors_p ())
+		{
+		  inform (loc, "cannot convert %qE to %qT because", t0, type);
+		  /* Further explain the reason for the error.  */
+		  expression_convertible_p (expr, type, info);
+		}
+	      else
+		inform (loc, "cannot convert %qE to %qT", t0, type);
+	    }
+	  return error_mark_node;
+	}
     }
 
   return boolean_true_node;
 }
 
+/* Substitute through the nested requirement.  */
+
 static tree
-tsubst_nested_requirement (tree t, tree args, subst_info info)
+tsubst_nested_requirement (tree t, tree args, sat_info info)
 {
   sat_info quiet (tf_none, info.in_decl);
   tree result = constraint_satisfaction_value (t, args, quiet);
-  if (result != boolean_true_node)
-    return error_mark_node;
-  return boolean_true_node;
+  if (result == boolean_true_node)
+    return boolean_true_node;
+
+  if (result == boolean_false_node
+      && info.diagnose_unsatisfaction_p ())
+    {
+      tree expr = TREE_OPERAND (t, 0);
+      location_t loc = cp_expr_location (t);
+      if (diagnosing_failed_constraint::replay_errors_p ())
+	{
+	  /* Replay the substitution error.  */
+	  inform (loc, "nested requirement %qE is not satisfied, because", expr);
+	  constraint_satisfaction_value (t, args, info);
+	}
+      else
+	inform (loc, "nested requirement %qE is not satisfied", expr);
+    }
+
+  return error_mark_node;
 }
 
 /* Substitute ARGS into the requirement T.  */
 
 static tree
-tsubst_requirement (tree t, tree args, subst_info info)
+tsubst_requirement (tree t, tree args, sat_info info)
 {
   iloc_sentinel loc_s (cp_expr_location (t));
   switch (TREE_CODE (t))
@@ -2151,30 +2268,22 @@ tsubst_constraint_variables (tree t, tree args, subst_info info)
    in its requirements ... In such cases, the expression evaluates
    to false; it does not cause the program to be ill-formed.
 
-   However, there are cases where substitution must produce a
-   new requires-expression, that is not a template constraint.
-   For example:
+   When substituting through a REQUIRES_EXPR as part of template
+   instantiation, we call this routine with info.quiet() true.
 
-        template<typename T>
-        class X {
-          template<typename U>
-          static constexpr bool var = requires (U u) { T::fn(u); };
-        };
+   When evaluating a REQUIRES_EXPR that appears outside a template in
+   cp_parser_requires_expression, we call this routine with
+   info.noisy() true.
 
-   In the instantiation of X<Y> (assuming Y defines fn), then the
-   instantiated requires-expression would include Y::fn(u). If any
-   substitution in the requires-expression fails, we can immediately
-   fold the expression to false, as would be the case e.g., when
-   instantiation X<int>.  */
+   Finally, when diagnosing unsatisfaction from diagnose_atomic_constraint
+   and when diagnosing a false REQUIRES_EXPR via diagnose_constraints,
+   we call this routine with info.diagnose_unsatisfaction_p() true.  */
 
-tree
-tsubst_requires_expr (tree t, tree args,
-		      tsubst_flags_t complain, tree in_decl)
+static tree
+tsubst_requires_expr (tree t, tree args, sat_info info)
 {
   local_specialization_stack stack (lss_copy);
 
-  subst_info info (complain, in_decl);
-
   /* A requires-expression is an unevaluated context.  */
   cp_unevaluated u;
 
@@ -2186,7 +2295,7 @@ tsubst_requires_expr (tree t, tree args,
 	 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);
+      REQUIRES_EXPR_EXTRA_ARGS (t) = build_extra_args (t, args, info.complain);
       return t;
     }
 
@@ -2197,14 +2306,30 @@ tsubst_requires_expr (tree t, tree args,
 	return boolean_false_node;
     }
 
+  tree result = boolean_true_node;
   for (tree reqs = REQUIRES_EXPR_REQS (t); reqs; reqs = TREE_CHAIN (reqs))
     {
       tree req = TREE_VALUE (reqs);
-      tree result = tsubst_requirement (req, args, info);
-      if (result == error_mark_node)
-	return boolean_false_node;
+      if (tsubst_requirement (req, args, info) == error_mark_node)
+	{
+	  result = boolean_false_node;
+	  if (info.diagnose_unsatisfaction_p ())
+	    /* Keep going so that we diagnose all failed requirements.  */;
+	  else
+	    break;
+	}
     }
-  return boolean_true_node;
+  return result;
+}
+
+/* Public wrapper for the above.  */
+
+tree
+tsubst_requires_expr (tree t, tree args,
+		      tsubst_flags_t complain, tree in_decl)
+{
+  sat_info info (complain, in_decl);
+  return tsubst_requires_expr (t, args, info);
 }
 
 /* Substitute ARGS into the constraint information CI, producing a new
@@ -2790,7 +2915,7 @@ get_mapped_args (tree map)
   return args;
 }
 
-static void diagnose_atomic_constraint (tree, tree, tree, subst_info);
+static void diagnose_atomic_constraint (tree, tree, tree, sat_info);
 
 /* Compute the satisfaction of an atomic constraint.  */
 
@@ -2976,14 +3101,10 @@ satisfy_nondeclaration_constraints (tree t, tree args, sat_info info)
   /* Handle REQUIRES_EXPR directly, bypassing satisfaction.  */
   if (TREE_CODE (t) == REQUIRES_EXPR)
     {
-      /* TODO: Remove this assert and the special casing of REQUIRES_EXPRs
-	 from diagnose_constraints once we merge tsubst_requires_expr and
-	 diagnose_requires_expr.  */
-      gcc_assert (!info.diagnose_unsatisfaction_p ());
       auto ovr = make_temp_override (current_constraint_diagnosis_depth);
       if (info.noisy ())
 	++current_constraint_diagnosis_depth;
-      return tsubst_requires_expr (t, args, info.complain, info.in_decl);
+      return tsubst_requires_expr (t, args, info);
     }
 
   /* Get the normalized constraints.  */
@@ -3466,11 +3587,10 @@ get_constraint_error_location (tree t)
 
 /* Emit a diagnostic for a failed trait.  */
 
-void
-diagnose_trait_expr (tree expr, tree map)
+static void
+diagnose_trait_expr (tree expr, tree args)
 {
   location_t loc = cp_expr_location (expr);
-  tree args = get_mapped_args (map);
 
   /* Build a "fake" version of the instantiated trait, so we can
      get the instantiated types from result.  */
@@ -3550,192 +3670,11 @@ diagnose_trait_expr (tree expr, tree map)
     }
 }
 
-static tree
-diagnose_valid_expression (tree expr, tree args, tree in_decl)
-{
-  tree result = tsubst_expr (expr, args, tf_none, in_decl, false);
-  if (result != error_mark_node
-      && convert_to_void (result, ICV_STATEMENT, tf_none) != error_mark_node)
-    return result;
-
-  location_t loc = cp_expr_loc_or_input_loc (expr);
-  if (diagnosing_failed_constraint::replay_errors_p ())
-    {
-      /* Replay the substitution error.  */
-      inform (loc, "the required expression %qE is invalid, because", expr);
-      if (result == error_mark_node)
-	tsubst_expr (expr, args, tf_error, in_decl, false);
-      else
-	convert_to_void (result, ICV_STATEMENT, tf_error);
-    }
-  else
-    inform (loc, "the required expression %qE is invalid", expr);
-
-  return error_mark_node;
-}
-
-static tree
-diagnose_valid_type (tree type, tree args, tree in_decl)
-{
-  tree result = tsubst (type, args, tf_none, in_decl);
-  if (result != error_mark_node)
-    return result;
-
-  location_t loc = cp_expr_loc_or_input_loc (type);
-  if (diagnosing_failed_constraint::replay_errors_p ())
-    {
-      /* Replay the substitution error.  */
-      inform (loc, "the required type %qT is invalid, because", type);
-      tsubst (type, args, tf_error, in_decl);
-    }
-  else
-    inform (loc, "the required type %qT is invalid", type);
-
-  return error_mark_node;
-}
-
-static void
-diagnose_simple_requirement (tree req, tree args, tree in_decl)
-{
-  diagnose_valid_expression (TREE_OPERAND (req, 0), args, in_decl);
-}
-
-static void
-diagnose_compound_requirement (tree req, tree args, tree in_decl)
-{
-  tree expr = TREE_OPERAND (req, 0);
-  expr = diagnose_valid_expression (expr, args, in_decl);
-  if (expr == error_mark_node)
-    return;
-
-  location_t loc = cp_expr_loc_or_input_loc (expr);
-
-  /* Check the noexcept condition.  */
-  if (COMPOUND_REQ_NOEXCEPT_P (req) && !expr_noexcept_p (expr, tf_none))
-    inform (loc, "%qE is not %<noexcept%>", expr);
-
-  tree type = TREE_OPERAND (req, 1);
-  type = diagnose_valid_type (type, args, in_decl);
-  if (type == error_mark_node)
-    return;
-
-  if (type)
-    {
-      subst_info quiet (tf_none, in_decl);
-      subst_info noisy (tf_error, in_decl);
-
-      /* Check the expression against the result type.  */
-      if (tree placeholder = type_uses_auto (type))
-	{
-	  if (!type_deducible_p (expr, type, placeholder, args, quiet))
-	    {
-	      tree orig_expr = TREE_OPERAND (req, 0);
-	      if (diagnosing_failed_constraint::replay_errors_p ())
-		{
-		  inform (loc,
-			  "%qE does not satisfy return-type-requirement, "
-			  "because", orig_expr);
-		  /* Further explain the reason for the error.  */
-		  type_deducible_p (expr, type, placeholder, args, noisy);
-		}
-	      else
-		inform (loc, "%qE does not satisfy return-type-requirement",
-			orig_expr);
-	    }
-	}
-      else if (!expression_convertible_p (expr, type, quiet))
-	{
-	  tree orig_expr = TREE_OPERAND (req, 0);
-	  if (diagnosing_failed_constraint::replay_errors_p ())
-	    {
-	      inform (loc, "cannot convert %qE to %qT because", orig_expr, type);
-	      /* Further explain the reason for the error.  */
-	      expression_convertible_p (expr, type, noisy);
-	    }
-	  else
-	    inform (loc, "cannot convert %qE to %qT", orig_expr, type);
-	}
-    }
-}
-
-static void
-diagnose_type_requirement (tree req, tree args, tree in_decl)
-{
-  tree type = TREE_OPERAND (req, 0);
-  diagnose_valid_type (type, args, in_decl);
-}
-
-static void
-diagnose_nested_requirement (tree req, tree args)
-{
-  /* Quietly check for satisfaction first.  */
-  sat_info quiet (tf_none, NULL_TREE);
-  tree result = satisfy_nondeclaration_constraints (req, args, quiet);
-  if (result == boolean_true_node)
-    return;
-
-  tree expr = TREE_OPERAND (req, 0);
-  location_t loc = cp_expr_location (expr);
-  if (diagnosing_failed_constraint::replay_errors_p ())
-    {
-      /* Replay the substitution error with re-normalized requirements.  */
-      inform (loc, "nested requirement %qE is not satisfied, because", expr);
-
-      sat_info noisy (tf_warning_or_error, NULL_TREE, /*diag_unsat=*/true);
-      satisfy_nondeclaration_constraints (req, args, noisy);
-    }
-  else
-    inform (loc, "nested requirement %qE is not satisfied", expr);
-
-}
-
-static void
-diagnose_requirement (tree req, tree args, tree in_decl)
-{
-  iloc_sentinel loc_s (cp_expr_location (req));
-  switch (TREE_CODE (req))
-    {
-    case SIMPLE_REQ:
-      return diagnose_simple_requirement (req, args, in_decl);
-    case COMPOUND_REQ:
-      return diagnose_compound_requirement (req, args, in_decl);
-    case TYPE_REQ:
-      return diagnose_type_requirement (req, args, in_decl);
-    case NESTED_REQ:
-      return diagnose_nested_requirement (req, args);
-    default:
-       gcc_unreachable ();
-    }
-}
-
-static void
-diagnose_requires_expr (tree expr, tree map, tree in_decl)
-{
-  local_specialization_stack stack (lss_copy);
-  tree parms = TREE_OPERAND (expr, 0);
-  tree body = TREE_OPERAND (expr, 1);
-  tree args = get_mapped_args (map);
-
-  cp_unevaluated u;
-  subst_info info (tf_warning_or_error, NULL_TREE);
-  tree vars = tsubst_constraint_variables (parms, args, info);
-  if (vars == error_mark_node)
-    return;
-
-  tree p = body;
-  while (p)
-    {
-      tree req = TREE_VALUE (p);
-      diagnose_requirement (req, args, in_decl);
-      p = TREE_CHAIN (p);
-    }
-}
-
 /* Diagnose a substitution failure in the atomic constraint T when applied
    with the instantiated parameter mapping MAP.  */
 
 static void
-diagnose_atomic_constraint (tree t, tree map, tree result, subst_info info)
+diagnose_atomic_constraint (tree t, tree map, tree result, sat_info info)
 {
   /* If the constraint is already ill-formed, we've previously diagnosed
      the reason. We should still say why the constraints aren't satisfied.  */
@@ -3756,13 +3695,16 @@ diagnose_atomic_constraint (tree t, tree map, tree result, subst_info info)
   /* Generate better diagnostics for certain kinds of expressions.  */
   tree expr = ATOMIC_CONSTR_EXPR (t);
   STRIP_ANY_LOCATION_WRAPPER (expr);
+  tree args = get_mapped_args (map);
   switch (TREE_CODE (expr))
     {
     case TRAIT_EXPR:
-      diagnose_trait_expr (expr, map);
+      diagnose_trait_expr (expr, args);
       break;
     case REQUIRES_EXPR:
-      diagnose_requires_expr (expr, map, info.in_decl);
+      gcc_checking_assert (info.diagnose_unsatisfaction_p ());
+      info.in_decl = NULL_TREE;
+      tsubst_requires_expr (expr, args, info);
       break;
     default:
       if (!same_type_p (TREE_TYPE (result), boolean_type_node))
@@ -3827,15 +3769,7 @@ diagnose_constraints (location_t loc, tree t, tree args)
 
   /* Replay satisfaction, but diagnose unsatisfaction.  */
   sat_info noisy (tf_warning_or_error, NULL_TREE, /*diag_unsat=*/true);
-  if (TREE_CODE (t) == REQUIRES_EXPR)
-    {
-      gcc_assert (!args);
-      ++current_constraint_diagnosis_depth;
-      diagnose_requires_expr (t, /*map=*/NULL_TREE, /*in_decl=*/NULL_TREE);
-      --current_constraint_diagnosis_depth;
-    }
-  else
-    constraint_satisfaction_value (t, args, noisy);
+  constraint_satisfaction_value (t, args, noisy);
 
   static bool suggested_p;
   if (concepts_diagnostics_max_depth_exceeded_p
-- 
2.31.0.rc0.75.gec125d1bc1


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 5/6] c++: Clean up normalization / satisfaction routines
  2021-03-02 16:25     ` Patrick Palka
@ 2021-03-02 22:58       ` Jason Merrill
  0 siblings, 0 replies; 29+ messages in thread
From: Jason Merrill @ 2021-03-02 22:58 UTC (permalink / raw)
  To: Patrick Palka; +Cc: gcc-patches

On 3/2/21 11:25 AM, Patrick Palka wrote:
> On Mon, 1 Mar 2021, Jason Merrill wrote:
> 
>> On 2/28/21 12:58 PM, Patrick Palka wrote:
>>> This patch mostly performs some straightforward refactoring:
>>>
>>>     - Renamed satisfy_constraint to satisfy_normalized_constraints
>>>     - Renamed the three-parameter version of satisfy_constraint_expression
>>>       to satisfy_nondeclaration_constraints
>>>     - Removed normalize_(non)?template_requirements
>>>     - Removed satisfy_associated_constraints (and made its callers
>>>       check for dependent template args sooner, before normalization)
>>>     - Removed the tsubst_flags_t parameter of evaluate_concept_check
>>>     - Combined the two versions of constraint_satisfaction_value
>>>     - Combined the two versions of constraint_satisfied_p
>>>
>>> Additionally, this patch removes the handling of bare
>>> constraint-expressions from satisfy_nondeclaration_constraints, and
>>> hence constraints_satisfied_p and constraint_satisfaction_value now only
>>> take things that carry their own template information needed for
>>> normalization.  In practice, this only means it's no longer possible to
>>> evaluate bare REQUIRES_EXPRs via the satisfaction routines, and so this
>>> patch adjusts the affected callers to instead use tsubst_requires_expr.
>>
>> It's probably better to have a different entry point than tsubst_requires_expr
>> for callers that have nothing to do with templates.  Whether that's a one-line
>> wrapper for the call to tsubst_requires_expr ("evaluate_requires_expr"?) or
>> calling tsubst_requires_expr from satisfy_nondeclaration_constraints, I don't
>> have an opinion either way.
>>
>>> For convenience, the function diagnose_constraints continues to accept
>>> REQUIRES_EXPRs, but it now handles them by calling diagnose_require_expr
>>> directly.
>>
>> This might argue for the latter choice above.
> 
> Ah, I didn't consider the option of continuing to handle REQUIRES_EXPRs
> from satisfy_nondeclaration_constraints by evaluating them directly.
> 
> Here's a patch that implements this.  I opted to add a
> evaluate_requires_expr wrapper as well, since
> 
>    evaluate_requires_expr (t)
> 
> is much more readable than
> 
>    constant_boolean_node (constraints_satisfied_p (t),
>                           boolean_type_node)
> 
> (Note the added TODO in satisfy_nondeclaration_constraints, which'll be
> fixed in a new version of the REQUIRES_EXPR evaluation/diagnostic
> unification patch.)

OK.

> -- >8 --
> 
> Subject: [PATCH] c++: Clean up normalization / satisfaction routines
> 
> This patch mostly performs some straightforward refactoring:
> 
>    - Renamed satisfy_constraint to satisfy_normalized_constraints
>    - Renamed the three-parameter version of satisfy_constraint_expression
>      to satisfy_nondeclaration_constraints
>    - Removed normalize_(non)?template_requirements
>    - Removed satisfy_associated_constraints (and made its callers
>      check for dependent template args sooner, before normalization)
>    - Removed the tsubst_flags_t parameter of evaluate_concept_check
>    - Combined the two versions of constraint_satisfaction_value
>    - Combined the two versions of constraint_satisfied_p
> 
> Additionally, this patch removes the handling of general
> constraint-expressions from satisfy_nondeclaration_constraints, and
> hence constraints_satisfied_p and constraint_satisfaction_value now take
> only things that carry their own template information needed for
> normalization, and, as a special case, REQUIRES_EXPRs.  But the latter
> now get evaluated directly via tsubst_requires_expr rather than going
> through satisfaction.
> 
> (That we used to evaluate REQUIRES_EXPR via satisfaction might even be a
> correctness issue: since we cache satisfaction in special ways that don't
> apply to regular evaluation, going through satisfaction could in theory
> cause us to reuse a cached value for a REQUIRES_EXPR when we shouldn't
> have.)
> 
> gcc/cp/ChangeLog:
> 
> 	* constexpr.c (cxx_eval_call_expression): Adjust call to
> 	evaluate_concept_check.
> 	(cxx_eval_constant_expression) <case REQUIRES_EXPR>: Use
> 	evaluate_requires_expression instead of
> 	satisfy_constraint_expression.
> 	<case TEMPLATE_ID_EXPR>: Adjust call to evaluate_concept_check.
> 	* constraint.cc (struct sat_info): Adjust comment about which
> 	satisfaction entrypoints use noisy-unsat.
> 	(normalize_template_requirements): Remove (and adjust callers
> 	appropriately).
> 	(normalize_nontemplate_requirements): Likewise.
> 	(tsubst_nested_requirement): Use constraint_satisfaction_value
> 	instead of satisfy_constraint_expression, which'll do the
> 	noisy replaying of ill-formed quiet satisfaction for us.
> 	(decl_satisfied_cache): Adjust comment.
> 	(satisfy_constraint): Rename to ...
> 	(satisfy_normalized_constraints): ... this.
> 	(satisfy_associated_constraints): Remove (and make its
> 	callers check for dependent arguments).
> 	(satisfy_constraint_expression): Rename to ...
> 	(satisfy_nondeclaration_constraints): ... this.  Assert that
> 	'args' is empty when 't' is a concept-id.  Removing handling
> 	bare constraint-expressions, and handle REQUIRES_EXPRs
> 	specially.  Adjust comment accordingly.
> 	(satisfy_declaration_constraints): Assert in the two-parameter
> 	version that 't' is not a TEMPLATE_DECL.  Adjust following
> 	removal of normalize_(non)?template_requirements and
> 	satisfy_asociated_constraints.
> 	(constraint_satisfaction_value): Combine the two- and
> 	three-parameter versions in the natural way.
> 	(constraints_satisfied_p): Combine the one- and two-parameter
> 	versions in the natural way.  Improve documentation.
> 	(evaluate_requires_expr): Define.
> 	(evaluate_concept_check): Remove 'complain' parameter.  Use
> 	constraint_satisfaction_value instead of
> 	satisfy_constraint_expression.
> 	(diagnose_nested_requirement): Adjust following renaming of
> 	satisfy_constraint_expression.
> 	(diagnose_constraints): Handle REQUIRES_EXPR by going through
> 	diagnose_requires_expr directly instead of treating it as a
> 	constraint-expression.  Improve documentation.
> 	* cp-gimplify.c (cp_genericize_r) <case CALL_EXPR>: Adjust call
> 	to evaluate_concept_check.
> 	<case REQUIRES_EXPR>: Use evaluate_requires_expr instead of
> 	constraints_satisfied_p.
> 	<case TEMPLATE_ID_EXPR>: Adjust call to evaluate_concept_check.
> 	* cp-tree.h (evaluate_requires_expr): Declare.
> 	(evaluate_concept_check): Remove tsubst_flag_t parameter.
> 	(satisfy_constraint_expression): Remove declaration.
> 	(constraints_satisfied_p): Remove one-parameter declaration.
> 	Add a default argument to the two-parameter declaration.
> 	* cvt.c (convert_to_void): Adjust call to
> 	evaluate_concept_check.
> ---
>   gcc/cp/constexpr.c   |   6 +-
>   gcc/cp/constraint.cc | 234 ++++++++++++++++++-------------------------
>   gcc/cp/cp-gimplify.c |   7 +-
>   gcc/cp/cp-tree.h     |   7 +-
>   gcc/cp/cvt.c         |   2 +-
>   5 files changed, 110 insertions(+), 146 deletions(-)
> 
> diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c
> index cd0a68e9fd6..7d96d577d84 100644
> --- a/gcc/cp/constexpr.c
> +++ b/gcc/cp/constexpr.c
> @@ -2257,7 +2257,7 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
>   {
>     /* Handle concept checks separately.  */
>     if (concept_check_p (t))
> -    return evaluate_concept_check (t, tf_warning_or_error);
> +    return evaluate_concept_check (t);
>   
>     location_t loc = cp_expr_loc_or_input_loc (t);
>     tree fun = get_function_named_in_call (t);
> @@ -6905,7 +6905,7 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
>            '!requires (T t) { ... }' which is not transformed into
>            a constraint.  */
>         if (!processing_template_decl)
> -        return satisfy_constraint_expression (t);
> +	return evaluate_requires_expr (t);
>         else
>           *non_constant_p = true;
>         return t;
> @@ -6941,7 +6941,7 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
>   
>   	if (!processing_template_decl
>   	    && !uid_sensitive_constexpr_evaluation_p ())
> -	  r = evaluate_concept_check (t, tf_warning_or_error);
> +	  r = evaluate_concept_check (t);
>   	else
>   	  *non_constant_p = true;
>   
> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> index 3332e6092a9..0949788aa29 100644
> --- a/gcc/cp/constraint.cc
> +++ b/gcc/cp/constraint.cc
> @@ -107,10 +107,9 @@ struct subst_info
>      a constraint is not satisfied.
>   
>      The entrypoints to satisfaction for which we set noisy+unsat are
> -   diagnose_constraints and diagnose_nested_requirement.  The entrypoints for
> -   which we set noisy-unsat are the replays inside constraint_satisfaction_value,
> -   evaluate_concept_check and tsubst_nested_requirement.  In other entrypoints,
> -   e.g. constraints_satisfied_p, we enter satisfaction quietly (both flags
> +   diagnose_constraints and diagnose_nested_requirement.  The entrypoint for
> +   which we set noisy-unsat is the replay inside constraint_satisfaction_value.
> +   From constraints_satisfied_p, we enter satisfaction quietly (both flags
>      cleared).  */
>   
>   struct sat_info : subst_info
> @@ -133,7 +132,7 @@ struct sat_info : subst_info
>     bool diagnose_unsatisfaction;
>   };
>   
> -static tree satisfy_constraint_expression (tree, tree, sat_info);
> +static tree constraint_satisfaction_value (tree, tree, sat_info);
>   
>   /* True if T is known to be some type other than bool. Note that this
>      is false for dependent types and errors.  */
> @@ -958,22 +957,6 @@ normalize_concept_definition (tree tmpl, bool diag = false)
>     return norm;
>   }
>   
> -/* Returns the normal form of TMPL's requirements.  */
> -
> -static tree
> -normalize_template_requirements (tree tmpl, bool diag = false)
> -{
> -  return get_normalized_constraints_from_decl (tmpl, diag);
> -}
> -
> -/* Returns the normal form of TMPL's requirements.  */
> -
> -static tree
> -normalize_nontemplate_requirements (tree decl, bool diag = false)
> -{
> -  return get_normalized_constraints_from_decl (decl, diag);
> -}
> -
>   /* Normalize an EXPR as a constraint.  */
>   
>   static tree
> @@ -2068,15 +2051,8 @@ tsubst_compound_requirement (tree t, tree args, subst_info info)
>   static tree
>   tsubst_nested_requirement (tree t, tree args, subst_info info)
>   {
> -  /* Perform satisfaction quietly first.  */
>     sat_info quiet (tf_none, info.in_decl);
> -  tree result = satisfy_constraint_expression (t, args, quiet);
> -  if (result == error_mark_node)
> -    {
> -      /* Replay the error.  */
> -      sat_info noisy (tf_warning_or_error, info.in_decl);
> -      satisfy_constraint_expression (t, args, noisy);
> -    }
> +  tree result = constraint_satisfaction_value (t, args, quiet);
>     if (result != boolean_true_node)
>       return error_mark_node;
>     return boolean_true_node;
> @@ -2459,7 +2435,7 @@ struct sat_hasher : ggc_ptr_hash<sat_entry>
>   /* Cache the result of satisfy_atom.  */
>   static GTY((deletable)) hash_table<sat_hasher> *sat_cache;
>   
> -/* Cache the result of constraint_satisfaction_value.  */
> +/* Cache the result of satisfy_declaration_constraints.  */
>   static GTY((deletable)) hash_map<tree, tree> *decl_satisfied_cache;
>   
>   /* A tool used by satisfy_atom to help manage satisfaction caching and to
> @@ -2941,7 +2917,7 @@ satisfy_constraint_r (tree t, tree args, sat_info info)
>   /* Check that the normalized constraint T is satisfied for ARGS.  */
>   
>   static tree
> -satisfy_constraint (tree t, tree args, sat_info info)
> +satisfy_normalized_constraints (tree t, tree args, sat_info info)
>   {
>     auto_timevar time (TV_CONSTRAINT_SAT);
>   
> @@ -2957,24 +2933,6 @@ satisfy_constraint (tree t, tree args, sat_info info)
>     return satisfy_constraint_r (t, args, info);
>   }
>   
> -/* Check the normalized constraints T against ARGS, returning a satisfaction
> -   value (either true, false, or error).  */
> -
> -static tree
> -satisfy_associated_constraints (tree t, tree args, sat_info info)
> -{
> -  /* If there are no constraints then this is trivially satisfied.  */
> -  if (!t)
> -    return boolean_true_node;
> -
> -  /* If any arguments depend on template parameters, we can't
> -     check constraints. Pretend they're satisfied for now.  */
> -  if (args && uses_template_parms (args))
> -    return boolean_true_node;
> -
> -  return satisfy_constraint (t, args, info);
> -}
> -
>   /* Return the normal form of the constraints on the placeholder 'auto'
>      type T.  */
>   
> @@ -3005,19 +2963,34 @@ normalize_placeholder_type_constraints (tree t, bool diag)
>     return normalize_constraint_expression (constr, info);
>   }
>   
> -/* Evaluate EXPR as a constraint expression using ARGS, returning a
> -   satisfaction value. */
> +/* Evaluate the constraints of T using ARGS, returning a satisfaction value.
> +   Here, T can be a concept-id, nested-requirement, placeholder 'auto', or
> +   requires-expression.  */
>   
>   static tree
> -satisfy_constraint_expression (tree t, tree args, sat_info info)
> +satisfy_nondeclaration_constraints (tree t, tree args, sat_info info)
>   {
>     if (t == error_mark_node)
>       return error_mark_node;
>   
> +  /* Handle REQUIRES_EXPR directly, bypassing satisfaction.  */
> +  if (TREE_CODE (t) == REQUIRES_EXPR)
> +    {
> +      /* TODO: Remove this assert and the special casing of REQUIRES_EXPRs
> +	 from diagnose_constraints once we merge tsubst_requires_expr and
> +	 diagnose_requires_expr.  */
> +      gcc_assert (!info.diagnose_unsatisfaction_p ());
> +      auto ovr = make_temp_override (current_constraint_diagnosis_depth);
> +      if (info.noisy ())
> +	++current_constraint_diagnosis_depth;
> +      return tsubst_requires_expr (t, args, info.complain, info.in_decl);
> +    }
> +
>     /* Get the normalized constraints.  */
>     tree norm;
> -  if (args == NULL_TREE && concept_check_p (t))
> +  if (concept_check_p (t))
>       {
> +      gcc_assert (!args);
>         tree id = unpack_concept_check (t);
>         args = TREE_OPERAND (id, 1);
>         tree tmpl = get_concept_check_template (id);
> @@ -3032,11 +3005,6 @@ satisfy_constraint_expression (tree t, tree args, sat_info info)
>         ninfo.initial_parms = TREE_TYPE (t);
>         norm = normalize_constraint_expression (TREE_OPERAND (t, 0), ninfo);
>       }
> -  else if (EXPR_P (t))
> -    {
> -      norm_info ninfo (info.noisy () ? tf_norm : tf_none);
> -      norm = normalize_constraint_expression (t, ninfo);
> -    }
>     else if (is_auto (t))
>       {
>         norm = normalize_placeholder_type_constraints (t, info.noisy ());
> @@ -3047,23 +3015,16 @@ satisfy_constraint_expression (tree t, tree args, sat_info info)
>       gcc_unreachable ();
>   
>     /* Perform satisfaction.  */
> -  return satisfy_constraint (norm, args, info);
> +  return satisfy_normalized_constraints (norm, args, info);
>   }
>   
> -/* Used only to evaluate requires-expressions during constant expression
> -   evaluation.  */
> -
> -tree
> -satisfy_constraint_expression (tree expr)
> -{
> -  sat_info info (tf_none, NULL_TREE);
> -  return satisfy_constraint_expression (expr, NULL_TREE, info);
> -}
> +/* Evaluate the associated constraints of the template specialization T
> +   according to INFO, returning a satisfaction value.  */
>   
>   static tree
>   satisfy_declaration_constraints (tree t, sat_info info)
>   {
> -  gcc_assert (DECL_P (t));
> +  gcc_assert (DECL_P (t) && TREE_CODE (t) != TEMPLATE_DECL);
>     const tree saved_t = t;
>   
>     /* For inherited constructors, consider the original declaration;
> @@ -3083,26 +3044,24 @@ satisfy_declaration_constraints (tree t, sat_info info)
>       if (tree *result = hash_map_safe_get (decl_satisfied_cache, saved_t))
>         return *result;
>   
> -  /* Get the normalized constraints.  */
> -  tree norm = NULL_TREE;
>     tree args = NULL_TREE;
>     if (tree ti = DECL_TEMPLATE_INFO (t))
>       {
> -      tree tmpl = TI_TEMPLATE (ti);
> -      norm = normalize_template_requirements (tmpl, info.noisy ());
> -
>         /* The initial parameter mapping is the complete set of
>   	 template arguments substituted into the declaration.  */
>         args = TI_ARGS (ti);
>         if (inh_ctor_targs)
>   	args = add_outermost_template_args (args, inh_ctor_targs);
> -    }
> -  else
> -    {
> -      /* These should be empty until we allow constraints on non-templates.  */
> -      norm = normalize_nontemplate_requirements (t, info.noisy ());
> +
> +      /* If any arguments depend on template parameters, we can't
> +	 check constraints. Pretend they're satisfied for now.  */
> +      if (uses_template_parms (args))
> +	return boolean_true_node;
>       }
>   
> +  /* Get the normalized constraints.  */
> +  tree norm = get_normalized_constraints_from_decl (t, info.noisy ());
> +
>     unsigned ftc_count = vec_safe_length (failed_type_completions);
>   
>     tree result = boolean_true_node;
> @@ -3111,7 +3070,7 @@ satisfy_declaration_constraints (tree t, sat_info info)
>         if (!push_tinst_level (t))
>   	return result;
>         push_access_scope (t);
> -      result = satisfy_associated_constraints (norm, args, info);
> +      result = satisfy_normalized_constraints (norm, args, info);
>         pop_access_scope (t);
>         pop_tinst_level ();
>       }
> @@ -3134,6 +3093,10 @@ satisfy_declaration_constraints (tree t, sat_info info)
>     return result;
>   }
>   
> +/* Evaluate the associated constraints of the template T using ARGS as the
> +   innermost set of template arguments and according to INFO, returning a
> +   satisfaction value.  */
> +
>   static tree
>   satisfy_declaration_constraints (tree t, tree args, sat_info info)
>   {
> @@ -3144,14 +3107,19 @@ satisfy_declaration_constraints (tree t, tree args, sat_info info)
>   
>     args = add_outermost_template_args (t, args);
>   
> +  /* If any arguments depend on template parameters, we can't
> +     check constraints. Pretend they're satisfied for now.  */
> +  if (uses_template_parms (args))
> +    return boolean_true_node;
> +
>     tree result = boolean_true_node;
> -  if (tree norm = normalize_template_requirements (t, info.noisy ()))
> +  if (tree norm = get_normalized_constraints_from_decl (t, info.noisy ()))
>       {
>         if (!push_tinst_level (t, args))
>   	return result;
>         tree pattern = DECL_TEMPLATE_RESULT (t);
>         push_access_scope (pattern);
> -      result = satisfy_associated_constraints (norm, args, info);
> +      result = satisfy_normalized_constraints (norm, args, info);
>         pop_access_scope (pattern);
>         pop_tinst_level ();
>       }
> @@ -3159,62 +3127,50 @@ satisfy_declaration_constraints (tree t, tree args, sat_info info)
>     return result;
>   }
>   
> +/* A wrapper around satisfy_declaration_constraints and
> +   satisfy_nondeclaration_constraints which additionally replays
> +   quiet ill-formed satisfaction noisily, so that ill-formed
> +   satisfaction always gets diagnosed.  */
> +
>   static tree
> -constraint_satisfaction_value (tree t, sat_info info)
> +constraint_satisfaction_value (tree t, tree args, sat_info info)
>   {
>     tree r;
>     if (DECL_P (t))
> -    r = satisfy_declaration_constraints (t, info);
> +    {
> +      if (args)
> +	r = satisfy_declaration_constraints (t, args, info);
> +      else
> +	r = satisfy_declaration_constraints (t, info);
> +    }
>     else
> -    r = satisfy_constraint_expression (t, NULL_TREE, info);
> +    r = satisfy_nondeclaration_constraints (t, args, info);
>     if (r == error_mark_node && info.quiet ()
>         && !(DECL_P (t) && TREE_NO_WARNING (t)))
>       {
> -      /* Replay the error with re-normalized requirements.  */
> +      /* Replay the error noisily.  */
>         sat_info noisy (tf_warning_or_error, info.in_decl);
> -      constraint_satisfaction_value (t, noisy);
> -      if (DECL_P (t))
> +      constraint_satisfaction_value (t, args, noisy);
> +      if (DECL_P (t) && !args)
>   	/* Avoid giving these errors again.  */
>   	TREE_NO_WARNING (t) = true;
>       }
>     return r;
>   }
>   
> -static tree
> -constraint_satisfaction_value (tree t, tree args, sat_info info)
> -{
> -  tree r;
> -  if (DECL_P (t))
> -    r = satisfy_declaration_constraints (t, args, info);
> -  else
> -    r = satisfy_constraint_expression (t, args, info);
> -  if (r == error_mark_node && info.quiet ())
> -    {
> -      /* Replay the error with re-normalized requirements.  */
> -      sat_info noisy (tf_warning_or_error, info.in_decl);
> -      constraint_satisfaction_value (t, args, noisy);
> -    }
> -  return r;
> -}
> -
> -/* True iff the result of satisfying T is BOOLEAN_TRUE_NODE and false
> -   otherwise, even in the case of errors.  */
> -
> -bool
> -constraints_satisfied_p (tree t)
> -{
> -  if (!flag_concepts)
> -    return true;
> -
> -  sat_info quiet (tf_none, NULL_TREE);
> -  return constraint_satisfaction_value (t, quiet) == boolean_true_node;
> -}
> +/* True iff the result of satisfying T using ARGS is BOOLEAN_TRUE_NODE
> +   and false otherwise, even in the case of errors.
>   
> -/* True iff the result of satisfying T with ARGS is BOOLEAN_TRUE_NODE
> -    and false otherwise, even in the case of errors.  */
> +   Here, T can be:
> +     - a template declaration
> +     - a template specialization (in which case ARGS must be empty)
> +     - a concept-id (in which case ARGS must be empty)
> +     - a nested-requirement
> +     - a placeholder 'auto'
> +     - a requires-expression.  */
>   
>   bool
> -constraints_satisfied_p (tree t, tree args)
> +constraints_satisfied_p (tree t, tree args/*= NULL_TREE */)
>   {
>     if (!flag_concepts)
>       return true;
> @@ -3227,7 +3183,7 @@ constraints_satisfied_p (tree t, tree args)
>      evaluation of template-ids as id-expressions.  */
>   
>   tree
> -evaluate_concept_check (tree check, tsubst_flags_t complain)
> +evaluate_concept_check (tree check)
>   {
>     if (check == error_mark_node)
>       return error_mark_node;
> @@ -3236,14 +3192,19 @@ evaluate_concept_check (tree check, tsubst_flags_t complain)
>   
>     /* Check for satisfaction without diagnostics.  */
>     sat_info quiet (tf_none, NULL_TREE);
> -  tree result = satisfy_constraint_expression (check, NULL_TREE, quiet);
> -  if (result == error_mark_node && (complain & tf_error))
> -    {
> -      /* Replay the error with re-normalized requirements.  */
> -      sat_info noisy (tf_warning_or_error, NULL_TREE);
> -      satisfy_constraint_expression (check, NULL_TREE, noisy);
> -    }
> -  return result;
> +  return constraint_satisfaction_value (check, /*args=*/NULL_TREE, quiet);
> +}
> +
> +/* Evaluate the requires-expression T, returning either boolean_true_node
> +   or boolean_false_node.  This is used during gimplification and constexpr
> +   evaluation.  */
> +
> +tree
> +evaluate_requires_expr (tree t)
> +{
> +  gcc_assert (TREE_CODE (t) == REQUIRES_EXPR);
> +  sat_info quiet (tf_none, NULL_TREE);
> +  return constraint_satisfaction_value (t, /*args=*/NULL_TREE, quiet);
>   }
>   
>   /*---------------------------------------------------------------------------
> @@ -3709,7 +3670,7 @@ diagnose_nested_requirement (tree req, tree args)
>   {
>     /* Quietly check for satisfaction first.  */
>     sat_info quiet (tf_none, NULL_TREE);
> -  tree result = satisfy_constraint_expression (req, args, quiet);
> +  tree result = satisfy_nondeclaration_constraints (req, args, quiet);
>     if (result == boolean_true_node)
>       return;
>   
> @@ -3721,7 +3682,7 @@ diagnose_nested_requirement (tree req, tree args)
>         inform (loc, "nested requirement %qE is not satisfied, because", expr);
>   
>         sat_info noisy (tf_warning_or_error, NULL_TREE, /*diag_unsat=*/true);
> -      satisfy_constraint_expression (req, args, noisy);
> +      satisfy_nondeclaration_constraints (req, args, noisy);
>       }
>     else
>       inform (loc, "nested requirement %qE is not satisfied", expr);
> @@ -3854,7 +3815,7 @@ diagnosing_failed_constraint::replay_errors_p ()
>   }
>   
>   /* Emit diagnostics detailing the failure ARGS to satisfy the constraints
> -   of T. Here, T can be either a constraint or a declaration.  */
> +   of T.  Here, T and ARGS are as in constraints_satisfied_p.  */
>   
>   void
>   diagnose_constraints (location_t loc, tree t, tree args)
> @@ -3866,8 +3827,13 @@ diagnose_constraints (location_t loc, tree t, tree args)
>   
>     /* Replay satisfaction, but diagnose unsatisfaction.  */
>     sat_info noisy (tf_warning_or_error, NULL_TREE, /*diag_unsat=*/true);
> -  if (!args)
> -    constraint_satisfaction_value (t, noisy);
> +  if (TREE_CODE (t) == REQUIRES_EXPR)
> +    {
> +      gcc_assert (!args);
> +      ++current_constraint_diagnosis_depth;
> +      diagnose_requires_expr (t, /*map=*/NULL_TREE, /*in_decl=*/NULL_TREE);
> +      --current_constraint_diagnosis_depth;
> +    }
>     else
>       constraint_satisfaction_value (t, args, noisy);
>   
> diff --git a/gcc/cp/cp-gimplify.c b/gcc/cp/cp-gimplify.c
> index abb8a6ef078..df89ff3815b 100644
> --- a/gcc/cp/cp-gimplify.c
> +++ b/gcc/cp/cp-gimplify.c
> @@ -1381,7 +1381,7 @@ cp_genericize_r (tree *stmt_p, int *walk_subtrees, void *data)
>   	 normal functions.  */
>         if (concept_check_p (stmt))
>   	{
> -	  *stmt_p = evaluate_concept_check (stmt, tf_warning_or_error);
> +	  *stmt_p = evaluate_concept_check (stmt);
>   	  * walk_subtrees = 0;
>   	  break;
>   	}
> @@ -1453,15 +1453,14 @@ cp_genericize_r (tree *stmt_p, int *walk_subtrees, void *data)
>   
>       case REQUIRES_EXPR:
>         /* Emit the value of the requires-expression.  */
> -      *stmt_p = constant_boolean_node (constraints_satisfied_p (stmt),
> -				       boolean_type_node);
> +      *stmt_p = evaluate_requires_expr (stmt);
>         *walk_subtrees = 0;
>         break;
>   
>       case TEMPLATE_ID_EXPR:
>         gcc_assert (concept_check_p (stmt));
>         /* Emit the value of the concept check.  */
> -      *stmt_p = evaluate_concept_check (stmt, tf_warning_or_error);
> +      *stmt_p = evaluate_concept_check (stmt);
>         walk_subtrees = 0;
>         break;
>   
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index 544e99538a4..f06ac3c73bf 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -8109,6 +8109,7 @@ extern tree finish_compound_requirement         (location_t, tree, tree, bool);
>   extern tree finish_nested_requirement           (location_t, tree);
>   extern void check_constrained_friend            (tree, tree);
>   extern tree tsubst_requires_expr                (tree, tree, tsubst_flags_t, tree);
> +extern tree evaluate_requires_expr		(tree);
>   extern tree tsubst_constraint                   (tree, tree, tsubst_flags_t, tree);
>   extern tree tsubst_constraint_info              (tree, tree, tsubst_flags_t, tree);
>   extern tree tsubst_parameter_mapping		(tree, tree, tsubst_flags_t, tree);
> @@ -8123,10 +8124,8 @@ struct processing_constraint_expression_sentinel
>   extern bool processing_constraint_expression_p	();
>   
>   extern tree unpack_concept_check		(tree);
> -extern tree evaluate_concept_check              (tree, tsubst_flags_t);
> -extern tree satisfy_constraint_expression	(tree);
> -extern bool constraints_satisfied_p		(tree);
> -extern bool constraints_satisfied_p		(tree, tree);
> +extern tree evaluate_concept_check              (tree);
> +extern bool constraints_satisfied_p		(tree, tree = NULL_TREE);
>   extern bool* lookup_subsumption_result          (tree, tree);
>   extern bool save_subsumption_result             (tree, tree, bool);
>   extern tree find_template_parameters		(tree, tree);
> diff --git a/gcc/cp/cvt.c b/gcc/cp/cvt.c
> index e809f0e4068..3f5467c8283 100644
> --- a/gcc/cp/cvt.c
> +++ b/gcc/cp/cvt.c
> @@ -1170,7 +1170,7 @@ convert_to_void (tree expr, impl_conv_void implicit, tsubst_flags_t complain)
>     /* Explicitly evaluate void-converted concept checks since their
>        satisfaction may produce ill-formed programs.  */
>      if (concept_check_p (expr))
> -     expr = evaluate_concept_check (expr, tf_warning_or_error);
> +     expr = evaluate_concept_check (expr);
>   
>     if (VOID_TYPE_P (TREE_TYPE (expr)))
>       return expr;
> 


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 6/6] c++: Consolidate REQUIRES_EXPR evaluation/diagnostic routines
  2021-03-02 16:45     ` Patrick Palka
@ 2021-03-02 23:04       ` Jason Merrill
  2021-03-03 17:10         ` Patrick Palka
  0 siblings, 1 reply; 29+ messages in thread
From: Jason Merrill @ 2021-03-02 23:04 UTC (permalink / raw)
  To: Patrick Palka; +Cc: gcc-patches

On 3/2/21 11:45 AM, Patrick Palka wrote:
> On Mon, 1 Mar 2021, Jason Merrill wrote:
> 
>> On 2/28/21 12:59 PM, Patrick Palka wrote:
>>> This folds the diagnose_requires_expr routines into the corresponding
>>> tsubst_requires_expr ones.  This is achieved by making the latter
>>> routines take a sat_info instead of a subst_info, and assigning the
>>> appropriate meanings to the flags sat_info::noisy and
>>> sat_info::diagnose_unsatisfaction_p during tsubst_requires_expr:
>>> info.noisy() controls whether to diagnose invalid types and expressions
>>> inside the requires-expression, and info.diagnose_unsatisfaction_p()
>>> controls whether to diagnose why the requires-expression evaluates to
>>> false.
>>>
>>> gcc/cp/ChangeLog:
>>>
>>> 	* constraint.cc (struct sat_info): Document the different
>>> 	meanings of noisy() and diagnose_unsatisfaction_p() during
>>> 	satisfaction and requires-expression evaluation.
>>> 	(tsubst_valid_expression_requirement): Take a sat_info instead
>>> 	of a subst_info.  Perform the substitution quietly first.  Fold
>>> 	in error-replaying code from diagnose_valid_expression.
>>> 	(tsubst_simple_requirement): Take a sat_info instead of a
>>> 	subst_info.
>>> 	(tsubst_type_requirement_1): New.  Fold in error-replaying code
>>> 	from diagnose_valid_type.
>>> 	(tsubst_type_requirement): Use the above.  Take a sat_info
>>> 	instead of a subst_info.
>>> 	(tsubst_compound_requirement): Likewise.  Fold in
>>> 	error-replaying code from diagnose_compound_requirement.
>>> 	(tsubst_nested_requirement): Take a sat_info instead of a
>>> 	subst_info.  Fold in error-replaying code from
>>> 	diagnose_nested_requirement.
>>> 	(tsubst_requirement): Take a sat_info instead of a subst_info.
>>> 	(tsubst_requires_expr): Split into two versions, one that takes
>>> 	a sat_info argument and another that takes a complain and
>>> 	in_decl argument.  Remove outdated documentation.  Document the
>>> 	effects of the sat_info argument.
>>> 	(diagnose_trait_expr): Make static.  Take a template argument
>>> 	vector instead of a parameter mapping.
>>> 	(diagnose_valid_expression): Remove.
>>> 	(diagnose_valid_type): Remove.
>>> 	(diagnose_simple_requirement): Remove.
>>> 	(diagnose_compound_requirement): Remove.
>>> 	(diagnose_type_requirement): Remove.
>>> 	(diagnose_nested_requirement): Remove.
>>> 	(diagnose_requirement): Remove.
>>> 	(diagnose_requires_expr): Remove.
>>> 	(diagnose_atomic_constraint): Take a sat_info instead of a
>>> 	subst_info.  Adjust call to diagnose_trait_expr.  Call
>>> 	tsubst_requires_expr instead of diagnose_requires_expr.
>>> 	(diagnose_constraints): Call tsubst_requires_expr instead of
>>> 	diagnose_requires_expr.
>>>
>>> gcc/testsuite/ChangeLog:
>>>
>>> 	* g++.dg/concepts/diagnostic1.C: Adjust expected diagnostics
>>> 	now that we diagnose only the first failed requirement of a
>>> 	requires-expression.
>>> ---
>>>    gcc/cp/constraint.cc                        | 416 +++++++++-----------
>>>    gcc/testsuite/g++.dg/concepts/diagnostic1.C |   2 +-
>>>    2 files changed, 179 insertions(+), 239 deletions(-)
>>>
>>> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
>>> index cf319b34da0..31f32c25dfe 100644
>>> --- a/gcc/cp/constraint.cc
>>> +++ b/gcc/cp/constraint.cc
>>> @@ -100,17 +100,30 @@ struct subst_info
>>>      /* Provides additional context for satisfaction.
>>>    -   The flag noisy() controls whether to diagnose ill-formed satisfaction,
>>> -   such as the satisfaction value of an atom being non-bool or
>>> non-constant.
>>> -
>>> -   The flag diagnose_unsatisfaction_p() controls whether to explain why
>>> -   a constraint is not satisfied.
>>> -
>>> -   The entrypoints to satisfaction for which we set noisy+unsat are
>>> -   diagnose_constraints and diagnose_nested_requirement.  The entrypoint
>>> for
>>> -   which we set noisy-unsat is the replay inside
>>> constraint_satisfaction_value.
>>> -   From constraints_satisfied_p, we enter satisfaction quietly (both flags
>>> -   cleared).  */
>>> +   During satisfaction:
>>> +    - The flag noisy() controls whether to diagnose ill-formed
>>> satisfaction,
>>> +      such as the satisfaction value of an atom being non-bool or
>>> non-constant.
>>> +    - The flag diagnose_unsatisfaction_p() controls whether to explain why
>>> +      a constraint is not satisfied.
>>> +    - We enter satisfaction with noisy+unsat from diagnose_constraints.
>>> +    - We enter satisfaction with noisy-unsat from the replay inside
>>> +      constraint_satisfaction_value.
>>> +    - We enter satisfaction quietly (both flags cleared) from
>>> +      constraints_satisfied_p.
>>> +
>>> +   During evaluation of a requires-expression:
>>> +    - The flag noisy() controls whether to diagnose ill-formed types and
>>> +      expressions inside its requirements.
>>> +    - The flag diagnose_unsatisfaction_p() controls whether to explain why
>>> +      the requires-expression evaluates to false.
>>> +    - We enter tsubst_requires_expr with noisy+unsat from
>>> diagnose_constraints
>>> +      and from diagnose_atomic_constraint.
>>> +    - We enter tsubst_requires_expr with noisy-unsat from
>>> +      cp_parser_requires_expression when processing a requires-expression
>>> that
>>> +      appears outside a template.
>>> +    - We enter tsubst_requires_expr quietly (both flags cleared) when
>>> +      substituting through a requires-expression as part of template
>>> +      instantiation.  */
>>>      struct sat_info : subst_info
>>>    {
>>> @@ -1926,22 +1939,44 @@ hash_placeholder_constraint (tree c)
>>>      return val;
>>>    }
>>>    -/* Substitute through the simple requirement.  */
>>> +/* Substitute through the expression of a simple requirement or
>>> +   compound requirement.  */
>>>      static tree
>>> -tsubst_valid_expression_requirement (tree t, tree args, subst_info info)
>>> +tsubst_valid_expression_requirement (tree t, tree args, sat_info info)
>>>    {
>>> -  tree r = tsubst_expr (t, args, info.complain, info.in_decl, false);
>>> -  if (convert_to_void (r, ICV_STATEMENT, info.complain) == error_mark_node)
>>> -    return error_mark_node;
>>> -  return r;
>>> +  tree r = tsubst_expr (t, args, tf_none, info.in_decl, false);
>>> +  if (convert_to_void (r, ICV_STATEMENT, tf_none) != error_mark_node)
>>> +    return r;
>>> +
>>> +  if (info.diagnose_unsatisfaction_p ())
>>> +    {
>>> +      location_t loc = cp_expr_loc_or_input_loc (t);
>>> +      if (diagnosing_failed_constraint::replay_errors_p ())
>>> +	{
>>> +	  inform (loc, "the required expression %qE is invalid, because", t);
>>> +	  if (r == error_mark_node)
>>> +	    tsubst_expr (t, args, info.complain, info.in_decl, false);
>>> +	  else
>>> +	    convert_to_void (r, ICV_STATEMENT, info.complain);
>>> +	}
>>> +      else
>>> +	inform (loc, "the required expression %qE is invalid", t);
>>> +    }
>>> +  else if (info.noisy ())
>>> +    {
>>> +      r = tsubst_expr (t, args, info.complain, info.in_decl, false);
>>> +      convert_to_void (r, ICV_STATEMENT, info.complain);
>>> +    }
>>> +
>>> +  return error_mark_node;
>>>    }
>>>        /* Substitute through the simple requirement.  */
>>>      static tree
>>> -tsubst_simple_requirement (tree t, tree args, subst_info info)
>>> +tsubst_simple_requirement (tree t, tree args, sat_info info)
>>>    {
>>>      tree t0 = TREE_OPERAND (t, 0);
>>>      tree expr = tsubst_valid_expression_requirement (t0, args, info);
>>> @@ -1950,13 +1985,41 @@ tsubst_simple_requirement (tree t, tree args,
>>> subst_info info)
>>>      return boolean_true_node;
>>>    }
>>>    +/* Subroutine of tsubst_type_requirement that performs the actual
>>> substitution
>>> +   and diagnosing.  Also used by tsubst_compound_requirement.  */
>>> +
>>> +static tree
>>> +tsubst_type_requirement_1 (tree t, tree args, sat_info info, location_t
>>> loc)
>>> +{
>>> +  tree r = tsubst (t, args, tf_none, info.in_decl);
>>> +  if (r != error_mark_node)
>>> +    return r;
>>> +
>>> +  if (info.diagnose_unsatisfaction_p ())
>>> +    {
>>> +      if (diagnosing_failed_constraint::replay_errors_p ())
>>> +	{
>>> +	  /* Replay the substitution error.  */
>>> +	  inform (loc, "the required type %qT is invalid, because", t);
>>> +	  tsubst (t, args, info.complain, info.in_decl);
>>> +	}
>>> +      else
>>> +	inform (loc, "the required type %qT is invalid", t);
>>> +    }
>>> +  else if (info.noisy ())
>>> +    tsubst (t, args, info.complain, info.in_decl);
>>> +
>>> +  return error_mark_node;
>>> +}
>>> +
>>> +
>>>    /* Substitute through the type requirement.  */
>>>      static tree
>>> -tsubst_type_requirement (tree t, tree args, subst_info info)
>>> +tsubst_type_requirement (tree t, tree args, sat_info info)
>>>    {
>>>      tree t0 = TREE_OPERAND (t, 0);
>>> -  tree type = tsubst (t0, args, info.complain, info.in_decl);
>>> +  tree type = tsubst_type_requirement_1 (t0, args, info, EXPR_LOCATION
>>> (t));
>>>      if (type == error_mark_node)
>>>        return error_mark_node;
>>>      return boolean_true_node;
>>> @@ -2013,7 +2076,7 @@ expression_convertible_p (tree expr, tree type,
>>> subst_info info)
>>>    /* Substitute through the compound requirement.  */
>>>      static tree
>>> -tsubst_compound_requirement (tree t, tree args, subst_info info)
>>> +tsubst_compound_requirement (tree t, tree args, sat_info info)
>>>    {
>>>      tree t0 = TREE_OPERAND (t, 0);
>>>      tree t1 = TREE_OPERAND (t, 1);
>>> @@ -2021,13 +2084,20 @@ tsubst_compound_requirement (tree t, tree args,
>>> subst_info info)
>>>      if (expr == error_mark_node)
>>>        return error_mark_node;
>>>    +  location_t loc = cp_expr_loc_or_input_loc (expr);
>>> +
>>>      /* Check the noexcept condition.  */
>>>      bool noexcept_p = COMPOUND_REQ_NOEXCEPT_P (t);
>>>      if (noexcept_p && !expr_noexcept_p (expr, tf_none))
>>> -    return error_mark_node;
>>> +    {
>>> +      if (info.diagnose_unsatisfaction_p ())
>>> +	inform (loc, "%qE is not %<noexcept%>", expr);
>>> +      else
>>> +	return error_mark_node;
>>> +    }
>>>        /* Substitute through the type expression, if any.  */
>>> -  tree type = tsubst (t1, args, info.complain, info.in_decl);
>>> +  tree type = tsubst_type_requirement_1 (t1, args, info, EXPR_LOCATION
>>> (t));
>>>      if (type == error_mark_node)
>>>        return error_mark_node;
>>>    @@ -2039,29 +2109,76 @@ tsubst_compound_requirement (tree t, tree args,
>>> subst_info info)
>>>          if (tree placeholder = type_uses_auto (type))
>>>    	{
>>>    	  if (!type_deducible_p (expr, type, placeholder, args, quiet))
>>> -	    return error_mark_node;
>>> +	    {
>>> +	      if (info.diagnose_unsatisfaction_p ())
>>> +		{
>>> +		  if (diagnosing_failed_constraint::replay_errors_p ())
>>> +		    {
>>> +		      inform (loc,
>>> +			      "%qE does not satisfy return-type-requirement, "
>>> +			      "because", t0);
>>> +		      /* Further explain the reason for the error.  */
>>> +		      type_deducible_p (expr, type, placeholder, args, info);
>>> +		    }
>>> +		  else
>>> +		    inform (loc,
>>> +			    "%qE does not satisfy return-type-requirement",
>>> t0);
>>> +		}
>>> +	      return error_mark_node;
>>> +	    }
>>>    	}
>>>          else if (!expression_convertible_p (expr, type, quiet))
>>> -	return error_mark_node;
>>> +	{
>>> +	  if (info.diagnose_unsatisfaction_p ())
>>> +	    {
>>> +	      if (diagnosing_failed_constraint::replay_errors_p ())
>>> +		{
>>> +		  inform (loc, "cannot convert %qE to %qT because", t0, type);
>>> +		  /* Further explain the reason for the error.  */
>>> +		  expression_convertible_p (expr, type, info);
>>> +		}
>>> +	      else
>>> +		inform (loc, "cannot convert %qE to %qT", t0, type);
>>> +	    }
>>> +	  return error_mark_node;
>>> +	}
>>>        }
>>>        return boolean_true_node;
>>>    }
>>>    +/* Substitute through the nested requirement.  */
>>> +
>>>    static tree
>>> -tsubst_nested_requirement (tree t, tree args, subst_info info)
>>> +tsubst_nested_requirement (tree t, tree args, sat_info info)
>>>    {
>>>      sat_info quiet (tf_none, info.in_decl);
>>>      tree result = constraint_satisfaction_value (t, args, quiet);
>>> -  if (result != boolean_true_node)
>>> -    return error_mark_node;
>>> -  return boolean_true_node;
>>> +  if (result == boolean_true_node)
>>> +    return boolean_true_node;
>>> +
>>> +  if (result == boolean_false_node
>>> +      && info.diagnose_unsatisfaction_p ())
>>> +    {
>>> +      tree expr = TREE_OPERAND (t, 0);
>>> +      location_t loc = cp_expr_location (t);
>>> +      if (diagnosing_failed_constraint::replay_errors_p ())
>>> +	{
>>> +	  /* Replay the substitution error.  */
>>> +	  inform (loc, "nested requirement %qE is not satisfied, because",
>>> expr);
>>> +	  constraint_satisfaction_value (t, args, info);
>>> +	}
>>> +      else
>>> +	inform (loc, "nested requirement %qE is not satisfied", expr);
>>> +    }
>>> +
>>> +  return error_mark_node;
>>>    }
>>>      /* Substitute ARGS into the requirement T.  */
>>>      static tree
>>> -tsubst_requirement (tree t, tree args, subst_info info)
>>> +tsubst_requirement (tree t, tree args, sat_info info)
>>>    {
>>>      iloc_sentinel loc_s (cp_expr_location (t));
>>>      switch (TREE_CODE (t))
>>> @@ -2151,30 +2268,22 @@ tsubst_constraint_variables (tree t, tree args,
>>> subst_info info)
>>>       in its requirements ... In such cases, the expression evaluates
>>>       to false; it does not cause the program to be ill-formed.
>>>    -   However, there are cases where substitution must produce a
>>> -   new requires-expression, that is not a template constraint.
>>> -   For example:
>>> +   When substituting through a REQUIRES_EXPR as part of template
>>> +   instantiation, we call this routine with info.quiet() true.
>>>    -        template<typename T>
>>> -        class X {
>>> -          template<typename U>
>>> -          static constexpr bool var = requires (U u) { T::fn(u); };
>>> -        };
>>> +   When evaluating a REQUIRES_EXPR that appears outside a template in
>>> +   cp_parser_requires_expression, we call this routine with
>>> +   info.noisy() true.
>>>    -   In the instantiation of X<Y> (assuming Y defines fn), then the
>>> -   instantiated requires-expression would include Y::fn(u). If any
>>> -   substitution in the requires-expression fails, we can immediately
>>> -   fold the expression to false, as would be the case e.g., when
>>> -   instantiation X<int>.  */
>>> +   Finally, when diagnosing unsatisfaction from diagnose_atomic_constraint
>>> +   and when diagnosing a false REQUIRES_EXPR via diagnose_constraints,
>>> +   we call this routine with info.diagnose_unsatisfaction_p() true.  */
>>>    -tree
>>> -tsubst_requires_expr (tree t, tree args,
>>> -		      tsubst_flags_t complain, tree in_decl)
>>> +static tree
>>> +tsubst_requires_expr (tree t, tree args, sat_info info)
>>>    {
>>>      local_specialization_stack stack (lss_copy);
>>>    -  subst_info info (complain, in_decl);
>>> -
>>>      /* A requires-expression is an unevaluated context.  */
>>>      cp_unevaluated u;
>>>    @@ -2186,7 +2295,7 @@ tsubst_requires_expr (tree t, tree args,
>>>    	 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);
>>> +      REQUIRES_EXPR_EXTRA_ARGS (t) = build_extra_args (t, args,
>>> info.complain);
>>>          return t;
>>>        }
>>>    @@ -2207,6 +2316,16 @@ tsubst_requires_expr (tree t, tree args,
>>>      return boolean_true_node;
>>>    }
>>>    +/* Public wrapper for the above.  */
>>> +
>>> +tree
>>> +tsubst_requires_expr (tree t, tree args,
>>> +		      tsubst_flags_t complain, tree in_decl)
>>> +{
>>> +  sat_info info (complain, in_decl);
>>> +  return tsubst_requires_expr (t, args, info);
>>> +}
>>> +
>>>    /* Substitute ARGS into the constraint information CI, producing a new
>>>       constraint record.  */
>>>    @@ -2790,7 +2909,7 @@ get_mapped_args (tree map)
>>>      return args;
>>>    }
>>>    -static void diagnose_atomic_constraint (tree, tree, tree, subst_info);
>>> +static void diagnose_atomic_constraint (tree, tree, tree, sat_info);
>>>      /* Compute the satisfaction of an atomic constraint.  */
>>>    @@ -3440,11 +3559,10 @@ get_constraint_error_location (tree t)
>>>      /* Emit a diagnostic for a failed trait.  */
>>>    -void
>>> -diagnose_trait_expr (tree expr, tree map)
>>> +static void
>>> +diagnose_trait_expr (tree expr, tree args)
>>>    {
>>>      location_t loc = cp_expr_location (expr);
>>> -  tree args = get_mapped_args (map);
>>>        /* Build a "fake" version of the instantiated trait, so we can
>>>         get the instantiated types from result.  */
>>> @@ -3524,192 +3642,11 @@ diagnose_trait_expr (tree expr, tree map)
>>>        }
>>>    }
>>>    -static tree
>>> -diagnose_valid_expression (tree expr, tree args, tree in_decl)
>>> -{
>>> -  tree result = tsubst_expr (expr, args, tf_none, in_decl, false);
>>> -  if (result != error_mark_node
>>> -      && convert_to_void (result, ICV_STATEMENT, tf_none) !=
>>> error_mark_node)
>>> -    return result;
>>> -
>>> -  location_t loc = cp_expr_loc_or_input_loc (expr);
>>> -  if (diagnosing_failed_constraint::replay_errors_p ())
>>> -    {
>>> -      /* Replay the substitution error.  */
>>> -      inform (loc, "the required expression %qE is invalid, because",
>>> expr);
>>> -      if (result == error_mark_node)
>>> -	tsubst_expr (expr, args, tf_error, in_decl, false);
>>> -      else
>>> -	convert_to_void (result, ICV_STATEMENT, tf_error);
>>> -    }
>>> -  else
>>> -    inform (loc, "the required expression %qE is invalid", expr);
>>> -
>>> -  return error_mark_node;
>>> -}
>>> -
>>> -static tree
>>> -diagnose_valid_type (tree type, tree args, tree in_decl)
>>> -{
>>> -  tree result = tsubst (type, args, tf_none, in_decl);
>>> -  if (result != error_mark_node)
>>> -    return result;
>>> -
>>> -  location_t loc = cp_expr_loc_or_input_loc (type);
>>> -  if (diagnosing_failed_constraint::replay_errors_p ())
>>> -    {
>>> -      /* Replay the substitution error.  */
>>> -      inform (loc, "the required type %qT is invalid, because", type);
>>> -      tsubst (type, args, tf_error, in_decl);
>>> -    }
>>> -  else
>>> -    inform (loc, "the required type %qT is invalid", type);
>>> -
>>> -  return error_mark_node;
>>> -}
>>> -
>>> -static void
>>> -diagnose_simple_requirement (tree req, tree args, tree in_decl)
>>> -{
>>> -  diagnose_valid_expression (TREE_OPERAND (req, 0), args, in_decl);
>>> -}
>>> -
>>> -static void
>>> -diagnose_compound_requirement (tree req, tree args, tree in_decl)
>>> -{
>>> -  tree expr = TREE_OPERAND (req, 0);
>>> -  expr = diagnose_valid_expression (expr, args, in_decl);
>>> -  if (expr == error_mark_node)
>>> -    return;
>>> -
>>> -  location_t loc = cp_expr_loc_or_input_loc (expr);
>>> -
>>> -  /* Check the noexcept condition.  */
>>> -  if (COMPOUND_REQ_NOEXCEPT_P (req) && !expr_noexcept_p (expr, tf_none))
>>> -    inform (loc, "%qE is not %<noexcept%>", expr);
>>> -
>>> -  tree type = TREE_OPERAND (req, 1);
>>> -  type = diagnose_valid_type (type, args, in_decl);
>>> -  if (type == error_mark_node)
>>> -    return;
>>> -
>>> -  if (type)
>>> -    {
>>> -      subst_info quiet (tf_none, in_decl);
>>> -      subst_info noisy (tf_error, in_decl);
>>> -
>>> -      /* Check the expression against the result type.  */
>>> -      if (tree placeholder = type_uses_auto (type))
>>> -	{
>>> -	  if (!type_deducible_p (expr, type, placeholder, args, quiet))
>>> -	    {
>>> -	      tree orig_expr = TREE_OPERAND (req, 0);
>>> -	      if (diagnosing_failed_constraint::replay_errors_p ())
>>> -		{
>>> -		  inform (loc,
>>> -			  "%qE does not satisfy return-type-requirement, "
>>> -			  "because", orig_expr);
>>> -		  /* Further explain the reason for the error.  */
>>> -		  type_deducible_p (expr, type, placeholder, args, noisy);
>>> -		}
>>> -	      else
>>> -		inform (loc, "%qE does not satisfy return-type-requirement",
>>> -			orig_expr);
>>> -	    }
>>> -	}
>>> -      else if (!expression_convertible_p (expr, type, quiet))
>>> -	{
>>> -	  tree orig_expr = TREE_OPERAND (req, 0);
>>> -	  if (diagnosing_failed_constraint::replay_errors_p ())
>>> -	    {
>>> -	      inform (loc, "cannot convert %qE to %qT because", orig_expr,
>>> type);
>>> -	      /* Further explain the reason for the error.  */
>>> -	      expression_convertible_p (expr, type, noisy);
>>> -	    }
>>> -	  else
>>> -	    inform (loc, "cannot convert %qE to %qT", orig_expr, type);
>>> -	}
>>> -    }
>>> -}
>>> -
>>> -static void
>>> -diagnose_type_requirement (tree req, tree args, tree in_decl)
>>> -{
>>> -  tree type = TREE_OPERAND (req, 0);
>>> -  diagnose_valid_type (type, args, in_decl);
>>> -}
>>> -
>>> -static void
>>> -diagnose_nested_requirement (tree req, tree args)
>>> -{
>>> -  /* Quietly check for satisfaction first.  */
>>> -  sat_info quiet (tf_none, NULL_TREE);
>>> -  tree result = satisfy_nondeclaration_constraints (req, args, quiet);
>>> -  if (result == boolean_true_node)
>>> -    return;
>>> -
>>> -  tree expr = TREE_OPERAND (req, 0);
>>> -  location_t loc = cp_expr_location (expr);
>>> -  if (diagnosing_failed_constraint::replay_errors_p ())
>>> -    {
>>> -      /* Replay the substitution error with re-normalized requirements.  */
>>> -      inform (loc, "nested requirement %qE is not satisfied, because",
>>> expr);
>>> -
>>> -      sat_info noisy (tf_warning_or_error, NULL_TREE, /*diag_unsat=*/true);
>>> -      satisfy_nondeclaration_constraints (req, args, noisy);
>>> -    }
>>> -  else
>>> -    inform (loc, "nested requirement %qE is not satisfied", expr);
>>> -
>>> -}
>>> -
>>> -static void
>>> -diagnose_requirement (tree req, tree args, tree in_decl)
>>> -{
>>> -  iloc_sentinel loc_s (cp_expr_location (req));
>>> -  switch (TREE_CODE (req))
>>> -    {
>>> -    case SIMPLE_REQ:
>>> -      return diagnose_simple_requirement (req, args, in_decl);
>>> -    case COMPOUND_REQ:
>>> -      return diagnose_compound_requirement (req, args, in_decl);
>>> -    case TYPE_REQ:
>>> -      return diagnose_type_requirement (req, args, in_decl);
>>> -    case NESTED_REQ:
>>> -      return diagnose_nested_requirement (req, args);
>>> -    default:
>>> -       gcc_unreachable ();
>>> -    }
>>> -}
>>> -
>>> -static void
>>> -diagnose_requires_expr (tree expr, tree map, tree in_decl)
>>> -{
>>> -  local_specialization_stack stack (lss_copy);
>>> -  tree parms = TREE_OPERAND (expr, 0);
>>> -  tree body = TREE_OPERAND (expr, 1);
>>> -  tree args = get_mapped_args (map);
>>> -
>>> -  cp_unevaluated u;
>>> -  subst_info info (tf_warning_or_error, NULL_TREE);
>>> -  tree vars = tsubst_constraint_variables (parms, args, info);
>>> -  if (vars == error_mark_node)
>>> -    return;
>>> -
>>> -  tree p = body;
>>> -  while (p)
>>> -    {
>>> -      tree req = TREE_VALUE (p);
>>> -      diagnose_requirement (req, args, in_decl);
>>> -      p = TREE_CHAIN (p);
>>> -    }
>>> -}
>>> -
>>>    /* Diagnose a substitution failure in the atomic constraint T when applied
>>>       with the instantiated parameter mapping MAP.  */
>>>      static void
>>> -diagnose_atomic_constraint (tree t, tree map, tree result, subst_info info)
>>> +diagnose_atomic_constraint (tree t, tree map, tree result, sat_info info)
>>>    {
>>>      /* If the constraint is already ill-formed, we've previously diagnosed
>>>         the reason. We should still say why the constraints aren't satisfied.
>>> */
>>> @@ -3730,13 +3667,16 @@ diagnose_atomic_constraint (tree t, tree map, tree
>>> result, subst_info info)
>>>      /* Generate better diagnostics for certain kinds of expressions.  */
>>>      tree expr = ATOMIC_CONSTR_EXPR (t);
>>>      STRIP_ANY_LOCATION_WRAPPER (expr);
>>> +  tree args = get_mapped_args (map);
>>>      switch (TREE_CODE (expr))
>>>        {
>>>        case TRAIT_EXPR:
>>> -      diagnose_trait_expr (expr, map);
>>> +      diagnose_trait_expr (expr, args);
>>>          break;
>>>        case REQUIRES_EXPR:
>>> -      diagnose_requires_expr (expr, map, info.in_decl);
>>> +      gcc_checking_assert (info.diagnose_unsatisfaction_p ());
>>> +      info.in_decl = NULL_TREE;
>>> +      tsubst_requires_expr (expr, args, info);
>>>          break;
>>>        default:
>>>          if (!same_type_p (TREE_TYPE (result), boolean_type_node))
>>> @@ -3807,7 +3747,7 @@ diagnose_constraints (location_t loc, tree t, tree
>>> args)
>>>        {
>>>          gcc_assert (!args);
>>>          ++current_constraint_diagnosis_depth;
>>> -      diagnose_requires_expr (t, /*map=*/NULL_TREE, /*in_decl=*/NULL_TREE);
>>> +      tsubst_requires_expr (t, /*args=*/NULL_TREE, noisy);
>>>          --current_constraint_diagnosis_depth;
>>>        }
>>>      else
>>> diff --git a/gcc/testsuite/g++.dg/concepts/diagnostic1.C
>>> b/gcc/testsuite/g++.dg/concepts/diagnostic1.C
>>> index 29c78c4c730..23bd592411e 100644
>>> --- a/gcc/testsuite/g++.dg/concepts/diagnostic1.C
>>> +++ b/gcc/testsuite/g++.dg/concepts/diagnostic1.C
>>> @@ -8,7 +8,7 @@ concept bool SameAs = __is_same_as(T, U);
>>>    template <class T>
>>>    concept bool R1 = requires (T& t) { // { dg-message "in requirements" }
>>>      { t.begin() } -> T;		// { dg-error "no match" }
>>> -  { t.end() } -> SameAs<T*>;	// { dg-message "does not satisfy" }
>>> +  { t.end() } -> SameAs<T*>;
>>
>> Are we no longer giving a message for this line?  That seems like a diagnostic
>> quality regression.
> 
> This happens because diagnose_requires_expr didn't short-circuit its
> processing of requirements upon seeing a failed requirement, and this
> behavior got lost when it was merged with tsubst_requires_expr, which
> does short-circuit.  I wasn't sure if we wanted to keep this behavior or
> not :)
> 
> The below restores the previous non-short-circuiting behavior of
> diagnose_requires_expr inside tsubst_requires_expr, and addresses the
> TODO added by v2 of patch5/6.
> 
> -- >8 --
> 
> Subject: [PATCH] c++: Unify REQUIRES_EXPR evaluation / diagnostic routines
> 
> This folds the diagnose_requires_expr routines into the corresponding
> tsubst_requires_expr ones.  This is achieved by making the latter
> routines take a sat_info instead of a subst_info, and assigning the
> appropriate meanings to the flags sat_info::noisy and
> sat_info::diagnose_unsatisfaction_p during tsubst_requires_expr:
> info.noisy() controls whether to diagnose invalid types and expressions
> inside the requirements, and info.diagnose_unsatisfaction_p() controls
> whether to additionally diagnose why the requires-expression evaluates
> to false.
> 
> gcc/cp/ChangeLog:
> 
> 	* constraint.cc (struct sat_info): Document the different
> 	meanings of noisy() and diagnose_unsatisfaction_p() during
> 	satisfaction and requires-expression evaluation.
> 	(tsubst_valid_expression_requirement): Take a sat_info instead
> 	of a subst_info.  Perform the substitution quietly first.  Fold
> 	in error-replaying code from diagnose_valid_expression.
> 	(tsubst_simple_requirement): Take a sat_info instead of a
> 	subst_info.
> 	(tsubst_type_requirement_1): New.  Fold in error-replaying code
> 	from diagnose_valid_type.
> 	(tsubst_type_requirement): Use the above.  Take a sat_info
> 	instead of a subst_info.
> 	(tsubst_compound_requirement): Likewise.  Fold in
> 	error-replaying code from diagnose_compound_requirement.
> 	(tsubst_nested_requirement): Take a sat_info instead of a
> 	subst_info.  Fold in error-replaying code from
> 	diagnose_nested_requirement.
> 	(tsubst_requirement): Take a sat_info instead of a subst_info.
> 	(tsubst_requires_expr): Split into two versions, one that takes
> 	a sat_info argument and another that takes a complain and
> 	in_decl argument.  Remove outdated documentation.  Document the
> 	effects of the sat_info argument.  Don't short-circuit
> 	processing of requirements when diagnosing unsatisfaction,
> 	mirroring diagnose_requires_expr.
> 	(satisfy_nondeclaration_constraint) <case REQUIRES_EXPR>: Remove
> 	assert, and se the three-parameter version of tsubst_requires_expr.
> 	(diagnose_trait_expr): Make static.  Take a template argument
> 	vector instead of a parameter mapping.
> 	(diagnose_valid_expression): Remove.
> 	(diagnose_valid_type): Remove.
> 	(diagnose_simple_requirement): Remove.
> 	(diagnose_compound_requirement): Remove.
> 	(diagnose_type_requirement): Remove.
> 	(diagnose_nested_requirement): Remove.
> 	(diagnose_requirement): Remove.
> 	(diagnose_requires_expr): Remove.
> 	(diagnose_atomic_constraint): Take a sat_info instead of a
> 	subst_info.  Adjust call to diagnose_trait_expr.  Call
> 	tsubst_requires_expr instead of diagnose_requires_expr.
> 	(diagnose_constraints): Remove special casing of REQUIRES_EXPR
> 	and just always call constraint_satisfaction_value.
> ---
>   gcc/cp/constraint.cc | 444 ++++++++++++++++++-------------------------
>   1 file changed, 189 insertions(+), 255 deletions(-)
> 
> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> index 0949788aa29..b2d25f3c232 100644
> --- a/gcc/cp/constraint.cc
> +++ b/gcc/cp/constraint.cc
> @@ -100,17 +100,30 @@ struct subst_info
>   
>   /* Provides additional context for satisfaction.
>   
> -   The flag noisy() controls whether to diagnose ill-formed satisfaction,
> -   such as the satisfaction value of an atom being non-bool or non-constant.
> -
> -   The flag diagnose_unsatisfaction_p() controls whether to explain why
> -   a constraint is not satisfied.
> -
> -   The entrypoints to satisfaction for which we set noisy+unsat are
> -   diagnose_constraints and diagnose_nested_requirement.  The entrypoint for
> -   which we set noisy-unsat is the replay inside constraint_satisfaction_value.
> -   From constraints_satisfied_p, we enter satisfaction quietly (both flags
> -   cleared).  */
> +   During satisfaction:
> +    - The flag noisy() controls whether to diagnose ill-formed satisfaction,
> +      such as the satisfaction value of an atom being non-bool or non-constant.
> +    - The flag diagnose_unsatisfaction_p() controls whether to additionally
> +      explain why a constraint is not satisfied.
> +    - We enter satisfaction with noisy+unsat from diagnose_constraints.
> +    - We enter satisfaction with noisy-unsat from the replay inside
> +      constraint_satisfaction_value.
> +    - We enter satisfaction quietly (both flags cleared) from
> +      constraints_satisfied_p.
> +
> +   During evaluation of a requires-expression:
> +    - The flag noisy() controls whether to diagnose ill-formed types and
> +      expressions inside its requirements.
> +    - The flag diagnose_unsatisfaction_p() controls whether to additionally
> +      explain why the requires-expression evaluates to false.
> +    - We enter tsubst_requires_expr with noisy+unsat from diagnose_constraints
> +      and from diagnose_atomic_constraint.
> +    - We enter tsubst_requires_expr with noisy-unsat from
> +      cp_parser_requires_expression when processing a requires-expression that
> +      appears outside a template.
> +    - We enter tsubst_requires_expr quietly (both flags cleared) when
> +      substituting through a requires-expression as part of template
> +      instantiation.  */
>   
>   struct sat_info : subst_info
>   {
> @@ -1926,22 +1939,44 @@ hash_placeholder_constraint (tree c)
>     return val;
>   }
>   
> -/* Substitute through the simple requirement.  */
> +/* Substitute through the expression of a simple requirement or
> +   compound requirement.  */
>   
>   static tree
> -tsubst_valid_expression_requirement (tree t, tree args, subst_info info)
> +tsubst_valid_expression_requirement (tree t, tree args, sat_info info)
>   {
> -  tree r = tsubst_expr (t, args, info.complain, info.in_decl, false);
> -  if (convert_to_void (r, ICV_STATEMENT, info.complain) == error_mark_node)
> -    return error_mark_node;
> -  return r;
> +  tree r = tsubst_expr (t, args, tf_none, info.in_decl, false);
> +  if (convert_to_void (r, ICV_STATEMENT, tf_none) != error_mark_node)
> +    return r;
> +
> +  if (info.diagnose_unsatisfaction_p ())
> +    {
> +      location_t loc = cp_expr_loc_or_input_loc (t);
> +      if (diagnosing_failed_constraint::replay_errors_p ())
> +	{
> +	  inform (loc, "the required expression %qE is invalid, because", t);
> +	  if (r == error_mark_node)
> +	    tsubst_expr (t, args, info.complain, info.in_decl, false);
> +	  else
> +	    convert_to_void (r, ICV_STATEMENT, info.complain);
> +	}
> +      else
> +	inform (loc, "the required expression %qE is invalid", t);
> +    }
> +  else if (info.noisy ())
> +    {
> +      r = tsubst_expr (t, args, info.complain, info.in_decl, false);
> +      convert_to_void (r, ICV_STATEMENT, info.complain);
> +    }
> +
> +  return error_mark_node;
>   }
>   
>   
>   /* Substitute through the simple requirement.  */
>   
>   static tree
> -tsubst_simple_requirement (tree t, tree args, subst_info info)
> +tsubst_simple_requirement (tree t, tree args, sat_info info)
>   {
>     tree t0 = TREE_OPERAND (t, 0);
>     tree expr = tsubst_valid_expression_requirement (t0, args, info);
> @@ -1950,13 +1985,41 @@ tsubst_simple_requirement (tree t, tree args, subst_info info)
>     return boolean_true_node;
>   }
>   
> +/* Subroutine of tsubst_type_requirement that performs the actual substitution
> +   and diagnosing.  Also used by tsubst_compound_requirement.  */
> +
> +static tree
> +tsubst_type_requirement_1 (tree t, tree args, sat_info info, location_t loc)
> +{
> +  tree r = tsubst (t, args, tf_none, info.in_decl);
> +  if (r != error_mark_node)
> +    return r;
> +
> +  if (info.diagnose_unsatisfaction_p ())
> +    {
> +      if (diagnosing_failed_constraint::replay_errors_p ())
> +	{
> +	  /* Replay the substitution error.  */
> +	  inform (loc, "the required type %qT is invalid, because", t);
> +	  tsubst (t, args, info.complain, info.in_decl);
> +	}
> +      else
> +	inform (loc, "the required type %qT is invalid", t);
> +    }
> +  else if (info.noisy ())
> +    tsubst (t, args, info.complain, info.in_decl);
> +
> +  return error_mark_node;
> +}
> +
> +
>   /* Substitute through the type requirement.  */
>   
>   static tree
> -tsubst_type_requirement (tree t, tree args, subst_info info)
> +tsubst_type_requirement (tree t, tree args, sat_info info)
>   {
>     tree t0 = TREE_OPERAND (t, 0);
> -  tree type = tsubst (t0, args, info.complain, info.in_decl);
> +  tree type = tsubst_type_requirement_1 (t0, args, info, EXPR_LOCATION (t));
>     if (type == error_mark_node)
>       return error_mark_node;
>     return boolean_true_node;
> @@ -2013,7 +2076,7 @@ expression_convertible_p (tree expr, tree type, subst_info info)
>   /* Substitute through the compound requirement.  */
>   
>   static tree
> -tsubst_compound_requirement (tree t, tree args, subst_info info)
> +tsubst_compound_requirement (tree t, tree args, sat_info info)
>   {
>     tree t0 = TREE_OPERAND (t, 0);
>     tree t1 = TREE_OPERAND (t, 1);
> @@ -2021,13 +2084,20 @@ tsubst_compound_requirement (tree t, tree args, subst_info info)
>     if (expr == error_mark_node)
>       return error_mark_node;
>   
> +  location_t loc = cp_expr_loc_or_input_loc (expr);
> +
>     /* Check the noexcept condition.  */
>     bool noexcept_p = COMPOUND_REQ_NOEXCEPT_P (t);
>     if (noexcept_p && !expr_noexcept_p (expr, tf_none))
> -    return error_mark_node;
> +    {
> +      if (info.diagnose_unsatisfaction_p ())
> +	inform (loc, "%qE is not %<noexcept%>", expr);
> +      else
> +	return error_mark_node;
> +    }
>   
>     /* Substitute through the type expression, if any.  */
> -  tree type = tsubst (t1, args, info.complain, info.in_decl);
> +  tree type = tsubst_type_requirement_1 (t1, args, info, EXPR_LOCATION (t));
>     if (type == error_mark_node)
>       return error_mark_node;
>   
> @@ -2039,29 +2109,76 @@ tsubst_compound_requirement (tree t, tree args, subst_info info)
>         if (tree placeholder = type_uses_auto (type))
>   	{
>   	  if (!type_deducible_p (expr, type, placeholder, args, quiet))
> -	    return error_mark_node;
> +	    {
> +	      if (info.diagnose_unsatisfaction_p ())
> +		{
> +		  if (diagnosing_failed_constraint::replay_errors_p ())
> +		    {
> +		      inform (loc,
> +			      "%qE does not satisfy return-type-requirement, "
> +			      "because", t0);
> +		      /* Further explain the reason for the error.  */
> +		      type_deducible_p (expr, type, placeholder, args, info);
> +		    }
> +		  else
> +		    inform (loc,
> +			    "%qE does not satisfy return-type-requirement", t0);
> +		}
> +	      return error_mark_node;
> +	    }
>   	}
>         else if (!expression_convertible_p (expr, type, quiet))
> -	return error_mark_node;
> +	{
> +	  if (info.diagnose_unsatisfaction_p ())
> +	    {
> +	      if (diagnosing_failed_constraint::replay_errors_p ())
> +		{
> +		  inform (loc, "cannot convert %qE to %qT because", t0, type);
> +		  /* Further explain the reason for the error.  */
> +		  expression_convertible_p (expr, type, info);
> +		}
> +	      else
> +		inform (loc, "cannot convert %qE to %qT", t0, type);
> +	    }
> +	  return error_mark_node;
> +	}
>       }
>   
>     return boolean_true_node;
>   }
>   
> +/* Substitute through the nested requirement.  */
> +
>   static tree
> -tsubst_nested_requirement (tree t, tree args, subst_info info)
> +tsubst_nested_requirement (tree t, tree args, sat_info info)
>   {
>     sat_info quiet (tf_none, info.in_decl);
>     tree result = constraint_satisfaction_value (t, args, quiet);
> -  if (result != boolean_true_node)
> -    return error_mark_node;
> -  return boolean_true_node;
> +  if (result == boolean_true_node)
> +    return boolean_true_node;
> +
> +  if (result == boolean_false_node
> +      && info.diagnose_unsatisfaction_p ())
> +    {
> +      tree expr = TREE_OPERAND (t, 0);
> +      location_t loc = cp_expr_location (t);
> +      if (diagnosing_failed_constraint::replay_errors_p ())
> +	{
> +	  /* Replay the substitution error.  */
> +	  inform (loc, "nested requirement %qE is not satisfied, because", expr);
> +	  constraint_satisfaction_value (t, args, info);
> +	}
> +      else
> +	inform (loc, "nested requirement %qE is not satisfied", expr);
> +    }
> +
> +  return error_mark_node;
>   }
>   
>   /* Substitute ARGS into the requirement T.  */
>   
>   static tree
> -tsubst_requirement (tree t, tree args, subst_info info)
> +tsubst_requirement (tree t, tree args, sat_info info)
>   {
>     iloc_sentinel loc_s (cp_expr_location (t));
>     switch (TREE_CODE (t))
> @@ -2151,30 +2268,22 @@ tsubst_constraint_variables (tree t, tree args, subst_info info)
>      in its requirements ... In such cases, the expression evaluates
>      to false; it does not cause the program to be ill-formed.
>   
> -   However, there are cases where substitution must produce a
> -   new requires-expression, that is not a template constraint.
> -   For example:
> +   When substituting through a REQUIRES_EXPR as part of template
> +   instantiation, we call this routine with info.quiet() true.
>   
> -        template<typename T>
> -        class X {
> -          template<typename U>
> -          static constexpr bool var = requires (U u) { T::fn(u); };
> -        };
> +   When evaluating a REQUIRES_EXPR that appears outside a template in
> +   cp_parser_requires_expression, we call this routine with
> +   info.noisy() true.
>   
> -   In the instantiation of X<Y> (assuming Y defines fn), then the
> -   instantiated requires-expression would include Y::fn(u). If any
> -   substitution in the requires-expression fails, we can immediately
> -   fold the expression to false, as would be the case e.g., when
> -   instantiation X<int>.  */
> +   Finally, when diagnosing unsatisfaction from diagnose_atomic_constraint
> +   and when diagnosing a false REQUIRES_EXPR via diagnose_constraints,
> +   we call this routine with info.diagnose_unsatisfaction_p() true.  */
>   
> -tree
> -tsubst_requires_expr (tree t, tree args,
> -		      tsubst_flags_t complain, tree in_decl)
> +static tree
> +tsubst_requires_expr (tree t, tree args, sat_info info)
>   {
>     local_specialization_stack stack (lss_copy);
>   
> -  subst_info info (complain, in_decl);
> -
>     /* A requires-expression is an unevaluated context.  */
>     cp_unevaluated u;
>   
> @@ -2186,7 +2295,7 @@ tsubst_requires_expr (tree t, tree args,
>   	 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);
> +      REQUIRES_EXPR_EXTRA_ARGS (t) = build_extra_args (t, args, info.complain);
>         return t;
>       }
>   
> @@ -2197,14 +2306,30 @@ tsubst_requires_expr (tree t, tree args,
>   	return boolean_false_node;
>       }
>   
> +  tree result = boolean_true_node;
>     for (tree reqs = REQUIRES_EXPR_REQS (t); reqs; reqs = TREE_CHAIN (reqs))
>       {
>         tree req = TREE_VALUE (reqs);
> -      tree result = tsubst_requirement (req, args, info);
> -      if (result == error_mark_node)
> -	return boolean_false_node;
> +      if (tsubst_requirement (req, args, info) == error_mark_node)
> +	{
> +	  result = boolean_false_node;
> +	  if (info.diagnose_unsatisfaction_p ())
> +	    /* Keep going so that we diagnose all failed requirements.  */;
> +	  else
> +	    break;
> +	}
>       }
> -  return boolean_true_node;
> +  return result;
> +}
> +
> +/* Public wrapper for the above.  */
> +
> +tree
> +tsubst_requires_expr (tree t, tree args,
> +		      tsubst_flags_t complain, tree in_decl)
> +{
> +  sat_info info (complain, in_decl);
> +  return tsubst_requires_expr (t, args, info);
>   }
>   
>   /* Substitute ARGS into the constraint information CI, producing a new
> @@ -2790,7 +2915,7 @@ get_mapped_args (tree map)
>     return args;
>   }
>   
> -static void diagnose_atomic_constraint (tree, tree, tree, subst_info);
> +static void diagnose_atomic_constraint (tree, tree, tree, sat_info);
>   
>   /* Compute the satisfaction of an atomic constraint.  */
>   
> @@ -2976,14 +3101,10 @@ satisfy_nondeclaration_constraints (tree t, tree args, sat_info info)
>     /* Handle REQUIRES_EXPR directly, bypassing satisfaction.  */
>     if (TREE_CODE (t) == REQUIRES_EXPR)
>       {
> -      /* TODO: Remove this assert and the special casing of REQUIRES_EXPRs
> -	 from diagnose_constraints once we merge tsubst_requires_expr and
> -	 diagnose_requires_expr.  */
> -      gcc_assert (!info.diagnose_unsatisfaction_p ());
>         auto ovr = make_temp_override (current_constraint_diagnosis_depth);
>         if (info.noisy ())
>   	++current_constraint_diagnosis_depth;
> -      return tsubst_requires_expr (t, args, info.complain, info.in_decl);
> +      return tsubst_requires_expr (t, args, info);
>       }
>   
>     /* Get the normalized constraints.  */
> @@ -3466,11 +3587,10 @@ get_constraint_error_location (tree t)
>   
>   /* Emit a diagnostic for a failed trait.  */
>   
> -void
> -diagnose_trait_expr (tree expr, tree map)
> +static void
> +diagnose_trait_expr (tree expr, tree args)
>   {
>     location_t loc = cp_expr_location (expr);
> -  tree args = get_mapped_args (map);
>   
>     /* Build a "fake" version of the instantiated trait, so we can
>        get the instantiated types from result.  */
> @@ -3550,192 +3670,11 @@ diagnose_trait_expr (tree expr, tree map)
>       }
>   }
>   
> -static tree
> -diagnose_valid_expression (tree expr, tree args, tree in_decl)
> -{
> -  tree result = tsubst_expr (expr, args, tf_none, in_decl, false);
> -  if (result != error_mark_node
> -      && convert_to_void (result, ICV_STATEMENT, tf_none) != error_mark_node)
> -    return result;
> -
> -  location_t loc = cp_expr_loc_or_input_loc (expr);
> -  if (diagnosing_failed_constraint::replay_errors_p ())
> -    {
> -      /* Replay the substitution error.  */
> -      inform (loc, "the required expression %qE is invalid, because", expr);
> -      if (result == error_mark_node)
> -	tsubst_expr (expr, args, tf_error, in_decl, false);
> -      else
> -	convert_to_void (result, ICV_STATEMENT, tf_error);
> -    }
> -  else
> -    inform (loc, "the required expression %qE is invalid", expr);
> -
> -  return error_mark_node;
> -}
> -
> -static tree
> -diagnose_valid_type (tree type, tree args, tree in_decl)
> -{
> -  tree result = tsubst (type, args, tf_none, in_decl);
> -  if (result != error_mark_node)
> -    return result;
> -
> -  location_t loc = cp_expr_loc_or_input_loc (type);
> -  if (diagnosing_failed_constraint::replay_errors_p ())
> -    {
> -      /* Replay the substitution error.  */
> -      inform (loc, "the required type %qT is invalid, because", type);
> -      tsubst (type, args, tf_error, in_decl);
> -    }
> -  else
> -    inform (loc, "the required type %qT is invalid", type);
> -
> -  return error_mark_node;
> -}
> -
> -static void
> -diagnose_simple_requirement (tree req, tree args, tree in_decl)
> -{
> -  diagnose_valid_expression (TREE_OPERAND (req, 0), args, in_decl);
> -}
> -
> -static void
> -diagnose_compound_requirement (tree req, tree args, tree in_decl)
> -{
> -  tree expr = TREE_OPERAND (req, 0);
> -  expr = diagnose_valid_expression (expr, args, in_decl);
> -  if (expr == error_mark_node)
> -    return;
> -
> -  location_t loc = cp_expr_loc_or_input_loc (expr);
> -
> -  /* Check the noexcept condition.  */
> -  if (COMPOUND_REQ_NOEXCEPT_P (req) && !expr_noexcept_p (expr, tf_none))
> -    inform (loc, "%qE is not %<noexcept%>", expr);
> -
> -  tree type = TREE_OPERAND (req, 1);
> -  type = diagnose_valid_type (type, args, in_decl);
> -  if (type == error_mark_node)
> -    return;
> -
> -  if (type)
> -    {
> -      subst_info quiet (tf_none, in_decl);
> -      subst_info noisy (tf_error, in_decl);
> -
> -      /* Check the expression against the result type.  */
> -      if (tree placeholder = type_uses_auto (type))
> -	{
> -	  if (!type_deducible_p (expr, type, placeholder, args, quiet))
> -	    {
> -	      tree orig_expr = TREE_OPERAND (req, 0);
> -	      if (diagnosing_failed_constraint::replay_errors_p ())
> -		{
> -		  inform (loc,
> -			  "%qE does not satisfy return-type-requirement, "
> -			  "because", orig_expr);
> -		  /* Further explain the reason for the error.  */
> -		  type_deducible_p (expr, type, placeholder, args, noisy);
> -		}
> -	      else
> -		inform (loc, "%qE does not satisfy return-type-requirement",
> -			orig_expr);
> -	    }
> -	}
> -      else if (!expression_convertible_p (expr, type, quiet))
> -	{
> -	  tree orig_expr = TREE_OPERAND (req, 0);
> -	  if (diagnosing_failed_constraint::replay_errors_p ())
> -	    {
> -	      inform (loc, "cannot convert %qE to %qT because", orig_expr, type);
> -	      /* Further explain the reason for the error.  */
> -	      expression_convertible_p (expr, type, noisy);
> -	    }
> -	  else
> -	    inform (loc, "cannot convert %qE to %qT", orig_expr, type);
> -	}
> -    }
> -}
> -
> -static void
> -diagnose_type_requirement (tree req, tree args, tree in_decl)
> -{
> -  tree type = TREE_OPERAND (req, 0);
> -  diagnose_valid_type (type, args, in_decl);
> -}
> -
> -static void
> -diagnose_nested_requirement (tree req, tree args)
> -{
> -  /* Quietly check for satisfaction first.  */
> -  sat_info quiet (tf_none, NULL_TREE);
> -  tree result = satisfy_nondeclaration_constraints (req, args, quiet);
> -  if (result == boolean_true_node)
> -    return;
> -
> -  tree expr = TREE_OPERAND (req, 0);
> -  location_t loc = cp_expr_location (expr);
> -  if (diagnosing_failed_constraint::replay_errors_p ())
> -    {
> -      /* Replay the substitution error with re-normalized requirements.  */
> -      inform (loc, "nested requirement %qE is not satisfied, because", expr);
> -
> -      sat_info noisy (tf_warning_or_error, NULL_TREE, /*diag_unsat=*/true);
> -      satisfy_nondeclaration_constraints (req, args, noisy);
> -    }
> -  else
> -    inform (loc, "nested requirement %qE is not satisfied", expr);
> -
> -}
> -
> -static void
> -diagnose_requirement (tree req, tree args, tree in_decl)
> -{
> -  iloc_sentinel loc_s (cp_expr_location (req));
> -  switch (TREE_CODE (req))
> -    {
> -    case SIMPLE_REQ:
> -      return diagnose_simple_requirement (req, args, in_decl);
> -    case COMPOUND_REQ:
> -      return diagnose_compound_requirement (req, args, in_decl);
> -    case TYPE_REQ:
> -      return diagnose_type_requirement (req, args, in_decl);
> -    case NESTED_REQ:
> -      return diagnose_nested_requirement (req, args);
> -    default:
> -       gcc_unreachable ();
> -    }
> -}
> -
> -static void
> -diagnose_requires_expr (tree expr, tree map, tree in_decl)
> -{
> -  local_specialization_stack stack (lss_copy);
> -  tree parms = TREE_OPERAND (expr, 0);
> -  tree body = TREE_OPERAND (expr, 1);
> -  tree args = get_mapped_args (map);
> -
> -  cp_unevaluated u;
> -  subst_info info (tf_warning_or_error, NULL_TREE);
> -  tree vars = tsubst_constraint_variables (parms, args, info);
> -  if (vars == error_mark_node)
> -    return;
> -
> -  tree p = body;
> -  while (p)
> -    {
> -      tree req = TREE_VALUE (p);
> -      diagnose_requirement (req, args, in_decl);
> -      p = TREE_CHAIN (p);
> -    }
> -}
> -
>   /* Diagnose a substitution failure in the atomic constraint T when applied
>      with the instantiated parameter mapping MAP.  */
>   
>   static void
> -diagnose_atomic_constraint (tree t, tree map, tree result, subst_info info)
> +diagnose_atomic_constraint (tree t, tree map, tree result, sat_info info)
>   {
>     /* If the constraint is already ill-formed, we've previously diagnosed
>        the reason. We should still say why the constraints aren't satisfied.  */
> @@ -3756,13 +3695,16 @@ diagnose_atomic_constraint (tree t, tree map, tree result, subst_info info)
>     /* Generate better diagnostics for certain kinds of expressions.  */
>     tree expr = ATOMIC_CONSTR_EXPR (t);
>     STRIP_ANY_LOCATION_WRAPPER (expr);
> +  tree args = get_mapped_args (map);
>     switch (TREE_CODE (expr))
>       {
>       case TRAIT_EXPR:
> -      diagnose_trait_expr (expr, map);
> +      diagnose_trait_expr (expr, args);
>         break;
>       case REQUIRES_EXPR:
> -      diagnose_requires_expr (expr, map, info.in_decl);
> +      gcc_checking_assert (info.diagnose_unsatisfaction_p ());
> +      info.in_decl = NULL_TREE;

This line needs a comment.  OK with that change.

> +      tsubst_requires_expr (expr, args, info);
>         break;
>       default:
>         if (!same_type_p (TREE_TYPE (result), boolean_type_node))
> @@ -3827,15 +3769,7 @@ diagnose_constraints (location_t loc, tree t, tree args)
>   
>     /* Replay satisfaction, but diagnose unsatisfaction.  */
>     sat_info noisy (tf_warning_or_error, NULL_TREE, /*diag_unsat=*/true);
> -  if (TREE_CODE (t) == REQUIRES_EXPR)
> -    {
> -      gcc_assert (!args);
> -      ++current_constraint_diagnosis_depth;
> -      diagnose_requires_expr (t, /*map=*/NULL_TREE, /*in_decl=*/NULL_TREE);
> -      --current_constraint_diagnosis_depth;
> -    }
> -  else
> -    constraint_satisfaction_value (t, args, noisy);
> +  constraint_satisfaction_value (t, args, noisy);
>   
>     static bool suggested_p;
>     if (concepts_diagnostics_max_depth_exceeded_p
> 


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 6/6] c++: Consolidate REQUIRES_EXPR evaluation/diagnostic routines
  2021-03-02 23:04       ` Jason Merrill
@ 2021-03-03 17:10         ` Patrick Palka
  0 siblings, 0 replies; 29+ messages in thread
From: Patrick Palka @ 2021-03-03 17:10 UTC (permalink / raw)
  To: Jason Merrill; +Cc: Patrick Palka, gcc-patches



On Tue, 2 Mar 2021, Jason Merrill wrote:

> On 3/2/21 11:45 AM, Patrick Palka wrote:
> > On Mon, 1 Mar 2021, Jason Merrill wrote:
> > 
> > > On 2/28/21 12:59 PM, Patrick Palka wrote:
> > > > This folds the diagnose_requires_expr routines into the corresponding
> > > > tsubst_requires_expr ones.  This is achieved by making the latter
> > > > routines take a sat_info instead of a subst_info, and assigning the
> > > > appropriate meanings to the flags sat_info::noisy and
> > > > sat_info::diagnose_unsatisfaction_p during tsubst_requires_expr:
> > > > info.noisy() controls whether to diagnose invalid types and expressions
> > > > inside the requires-expression, and info.diagnose_unsatisfaction_p()
> > > > controls whether to diagnose why the requires-expression evaluates to
> > > > false.
> > > > 
> > > > gcc/cp/ChangeLog:
> > > > 
> > > > 	* constraint.cc (struct sat_info): Document the different
> > > > 	meanings of noisy() and diagnose_unsatisfaction_p() during
> > > > 	satisfaction and requires-expression evaluation.
> > > > 	(tsubst_valid_expression_requirement): Take a sat_info instead
> > > > 	of a subst_info.  Perform the substitution quietly first.  Fold
> > > > 	in error-replaying code from diagnose_valid_expression.
> > > > 	(tsubst_simple_requirement): Take a sat_info instead of a
> > > > 	subst_info.
> > > > 	(tsubst_type_requirement_1): New.  Fold in error-replaying code
> > > > 	from diagnose_valid_type.
> > > > 	(tsubst_type_requirement): Use the above.  Take a sat_info
> > > > 	instead of a subst_info.
> > > > 	(tsubst_compound_requirement): Likewise.  Fold in
> > > > 	error-replaying code from diagnose_compound_requirement.
> > > > 	(tsubst_nested_requirement): Take a sat_info instead of a
> > > > 	subst_info.  Fold in error-replaying code from
> > > > 	diagnose_nested_requirement.
> > > > 	(tsubst_requirement): Take a sat_info instead of a subst_info.
> > > > 	(tsubst_requires_expr): Split into two versions, one that takes
> > > > 	a sat_info argument and another that takes a complain and
> > > > 	in_decl argument.  Remove outdated documentation.  Document the
> > > > 	effects of the sat_info argument.
> > > > 	(diagnose_trait_expr): Make static.  Take a template argument
> > > > 	vector instead of a parameter mapping.
> > > > 	(diagnose_valid_expression): Remove.
> > > > 	(diagnose_valid_type): Remove.
> > > > 	(diagnose_simple_requirement): Remove.
> > > > 	(diagnose_compound_requirement): Remove.
> > > > 	(diagnose_type_requirement): Remove.
> > > > 	(diagnose_nested_requirement): Remove.
> > > > 	(diagnose_requirement): Remove.
> > > > 	(diagnose_requires_expr): Remove.
> > > > 	(diagnose_atomic_constraint): Take a sat_info instead of a
> > > > 	subst_info.  Adjust call to diagnose_trait_expr.  Call
> > > > 	tsubst_requires_expr instead of diagnose_requires_expr.
> > > > 	(diagnose_constraints): Call tsubst_requires_expr instead of
> > > > 	diagnose_requires_expr.
> > > > 
> > > > gcc/testsuite/ChangeLog:
> > > > 
> > > > 	* g++.dg/concepts/diagnostic1.C: Adjust expected diagnostics
> > > > 	now that we diagnose only the first failed requirement of a
> > > > 	requires-expression.
> > > > ---
> > > >    gcc/cp/constraint.cc                        | 416
> > > > +++++++++-----------
> > > >    gcc/testsuite/g++.dg/concepts/diagnostic1.C |   2 +-
> > > >    2 files changed, 179 insertions(+), 239 deletions(-)
> > > > 
> > > > diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> > > > index cf319b34da0..31f32c25dfe 100644
> > > > --- a/gcc/cp/constraint.cc
> > > > +++ b/gcc/cp/constraint.cc
> > > > @@ -100,17 +100,30 @@ struct subst_info
> > > >      /* Provides additional context for satisfaction.
> > > >    -   The flag noisy() controls whether to diagnose ill-formed
> > > > satisfaction,
> > > > -   such as the satisfaction value of an atom being non-bool or
> > > > non-constant.
> > > > -
> > > > -   The flag diagnose_unsatisfaction_p() controls whether to explain why
> > > > -   a constraint is not satisfied.
> > > > -
> > > > -   The entrypoints to satisfaction for which we set noisy+unsat are
> > > > -   diagnose_constraints and diagnose_nested_requirement.  The
> > > > entrypoint
> > > > for
> > > > -   which we set noisy-unsat is the replay inside
> > > > constraint_satisfaction_value.
> > > > -   From constraints_satisfied_p, we enter satisfaction quietly (both
> > > > flags
> > > > -   cleared).  */
> > > > +   During satisfaction:
> > > > +    - The flag noisy() controls whether to diagnose ill-formed
> > > > satisfaction,
> > > > +      such as the satisfaction value of an atom being non-bool or
> > > > non-constant.
> > > > +    - The flag diagnose_unsatisfaction_p() controls whether to explain
> > > > why
> > > > +      a constraint is not satisfied.
> > > > +    - We enter satisfaction with noisy+unsat from diagnose_constraints.
> > > > +    - We enter satisfaction with noisy-unsat from the replay inside
> > > > +      constraint_satisfaction_value.
> > > > +    - We enter satisfaction quietly (both flags cleared) from
> > > > +      constraints_satisfied_p.
> > > > +
> > > > +   During evaluation of a requires-expression:
> > > > +    - The flag noisy() controls whether to diagnose ill-formed types
> > > > and
> > > > +      expressions inside its requirements.
> > > > +    - The flag diagnose_unsatisfaction_p() controls whether to explain
> > > > why
> > > > +      the requires-expression evaluates to false.
> > > > +    - We enter tsubst_requires_expr with noisy+unsat from
> > > > diagnose_constraints
> > > > +      and from diagnose_atomic_constraint.
> > > > +    - We enter tsubst_requires_expr with noisy-unsat from
> > > > +      cp_parser_requires_expression when processing a
> > > > requires-expression
> > > > that
> > > > +      appears outside a template.
> > > > +    - We enter tsubst_requires_expr quietly (both flags cleared) when
> > > > +      substituting through a requires-expression as part of template
> > > > +      instantiation.  */
> > > >      struct sat_info : subst_info
> > > >    {
> > > > @@ -1926,22 +1939,44 @@ hash_placeholder_constraint (tree c)
> > > >      return val;
> > > >    }
> > > >    -/* Substitute through the simple requirement.  */
> > > > +/* Substitute through the expression of a simple requirement or
> > > > +   compound requirement.  */
> > > >      static tree
> > > > -tsubst_valid_expression_requirement (tree t, tree args, subst_info
> > > > info)
> > > > +tsubst_valid_expression_requirement (tree t, tree args, sat_info info)
> > > >    {
> > > > -  tree r = tsubst_expr (t, args, info.complain, info.in_decl, false);
> > > > -  if (convert_to_void (r, ICV_STATEMENT, info.complain) ==
> > > > error_mark_node)
> > > > -    return error_mark_node;
> > > > -  return r;
> > > > +  tree r = tsubst_expr (t, args, tf_none, info.in_decl, false);
> > > > +  if (convert_to_void (r, ICV_STATEMENT, tf_none) != error_mark_node)
> > > > +    return r;
> > > > +
> > > > +  if (info.diagnose_unsatisfaction_p ())
> > > > +    {
> > > > +      location_t loc = cp_expr_loc_or_input_loc (t);
> > > > +      if (diagnosing_failed_constraint::replay_errors_p ())
> > > > +	{
> > > > +	  inform (loc, "the required expression %qE is invalid, because", t);
> > > > +	  if (r == error_mark_node)
> > > > +	    tsubst_expr (t, args, info.complain, info.in_decl, false);
> > > > +	  else
> > > > +	    convert_to_void (r, ICV_STATEMENT, info.complain);
> > > > +	}
> > > > +      else
> > > > +	inform (loc, "the required expression %qE is invalid", t);
> > > > +    }
> > > > +  else if (info.noisy ())
> > > > +    {
> > > > +      r = tsubst_expr (t, args, info.complain, info.in_decl, false);
> > > > +      convert_to_void (r, ICV_STATEMENT, info.complain);
> > > > +    }
> > > > +
> > > > +  return error_mark_node;
> > > >    }
> > > >        /* Substitute through the simple requirement.  */
> > > >      static tree
> > > > -tsubst_simple_requirement (tree t, tree args, subst_info info)
> > > > +tsubst_simple_requirement (tree t, tree args, sat_info info)
> > > >    {
> > > >      tree t0 = TREE_OPERAND (t, 0);
> > > >      tree expr = tsubst_valid_expression_requirement (t0, args, info);
> > > > @@ -1950,13 +1985,41 @@ tsubst_simple_requirement (tree t, tree args,
> > > > subst_info info)
> > > >      return boolean_true_node;
> > > >    }
> > > >    +/* Subroutine of tsubst_type_requirement that performs the actual
> > > > substitution
> > > > +   and diagnosing.  Also used by tsubst_compound_requirement.  */
> > > > +
> > > > +static tree
> > > > +tsubst_type_requirement_1 (tree t, tree args, sat_info info, location_t
> > > > loc)
> > > > +{
> > > > +  tree r = tsubst (t, args, tf_none, info.in_decl);
> > > > +  if (r != error_mark_node)
> > > > +    return r;
> > > > +
> > > > +  if (info.diagnose_unsatisfaction_p ())
> > > > +    {
> > > > +      if (diagnosing_failed_constraint::replay_errors_p ())
> > > > +	{
> > > > +	  /* Replay the substitution error.  */
> > > > +	  inform (loc, "the required type %qT is invalid, because", t);
> > > > +	  tsubst (t, args, info.complain, info.in_decl);
> > > > +	}
> > > > +      else
> > > > +	inform (loc, "the required type %qT is invalid", t);
> > > > +    }
> > > > +  else if (info.noisy ())
> > > > +    tsubst (t, args, info.complain, info.in_decl);
> > > > +
> > > > +  return error_mark_node;
> > > > +}
> > > > +
> > > > +
> > > >    /* Substitute through the type requirement.  */
> > > >      static tree
> > > > -tsubst_type_requirement (tree t, tree args, subst_info info)
> > > > +tsubst_type_requirement (tree t, tree args, sat_info info)
> > > >    {
> > > >      tree t0 = TREE_OPERAND (t, 0);
> > > > -  tree type = tsubst (t0, args, info.complain, info.in_decl);
> > > > +  tree type = tsubst_type_requirement_1 (t0, args, info, EXPR_LOCATION
> > > > (t));
> > > >      if (type == error_mark_node)
> > > >        return error_mark_node;
> > > >      return boolean_true_node;
> > > > @@ -2013,7 +2076,7 @@ expression_convertible_p (tree expr, tree type,
> > > > subst_info info)
> > > >    /* Substitute through the compound requirement.  */
> > > >      static tree
> > > > -tsubst_compound_requirement (tree t, tree args, subst_info info)
> > > > +tsubst_compound_requirement (tree t, tree args, sat_info info)
> > > >    {
> > > >      tree t0 = TREE_OPERAND (t, 0);
> > > >      tree t1 = TREE_OPERAND (t, 1);
> > > > @@ -2021,13 +2084,20 @@ tsubst_compound_requirement (tree t, tree args,
> > > > subst_info info)
> > > >      if (expr == error_mark_node)
> > > >        return error_mark_node;
> > > >    +  location_t loc = cp_expr_loc_or_input_loc (expr);
> > > > +
> > > >      /* Check the noexcept condition.  */
> > > >      bool noexcept_p = COMPOUND_REQ_NOEXCEPT_P (t);
> > > >      if (noexcept_p && !expr_noexcept_p (expr, tf_none))
> > > > -    return error_mark_node;
> > > > +    {
> > > > +      if (info.diagnose_unsatisfaction_p ())
> > > > +	inform (loc, "%qE is not %<noexcept%>", expr);
> > > > +      else
> > > > +	return error_mark_node;
> > > > +    }
> > > >        /* Substitute through the type expression, if any.  */
> > > > -  tree type = tsubst (t1, args, info.complain, info.in_decl);
> > > > +  tree type = tsubst_type_requirement_1 (t1, args, info, EXPR_LOCATION
> > > > (t));
> > > >      if (type == error_mark_node)
> > > >        return error_mark_node;
> > > >    @@ -2039,29 +2109,76 @@ tsubst_compound_requirement (tree t, tree
> > > > args,
> > > > subst_info info)
> > > >          if (tree placeholder = type_uses_auto (type))
> > > >    	{
> > > >    	  if (!type_deducible_p (expr, type, placeholder, args,
> > > > quiet))
> > > > -	    return error_mark_node;
> > > > +	    {
> > > > +	      if (info.diagnose_unsatisfaction_p ())
> > > > +		{
> > > > +		  if (diagnosing_failed_constraint::replay_errors_p ())
> > > > +		    {
> > > > +		      inform (loc,
> > > > +			      "%qE does not satisfy return-type-requirement, "
> > > > +			      "because", t0);
> > > > +		      /* Further explain the reason for the error.  */
> > > > +		      type_deducible_p (expr, type, placeholder, args, info);
> > > > +		    }
> > > > +		  else
> > > > +		    inform (loc,
> > > > +			    "%qE does not satisfy return-type-requirement",
> > > > t0);
> > > > +		}
> > > > +	      return error_mark_node;
> > > > +	    }
> > > >    	}
> > > >          else if (!expression_convertible_p (expr, type, quiet))
> > > > -	return error_mark_node;
> > > > +	{
> > > > +	  if (info.diagnose_unsatisfaction_p ())
> > > > +	    {
> > > > +	      if (diagnosing_failed_constraint::replay_errors_p ())
> > > > +		{
> > > > +		  inform (loc, "cannot convert %qE to %qT because", t0, type);
> > > > +		  /* Further explain the reason for the error.  */
> > > > +		  expression_convertible_p (expr, type, info);
> > > > +		}
> > > > +	      else
> > > > +		inform (loc, "cannot convert %qE to %qT", t0, type);
> > > > +	    }
> > > > +	  return error_mark_node;
> > > > +	}
> > > >        }
> > > >        return boolean_true_node;
> > > >    }
> > > >    +/* Substitute through the nested requirement.  */
> > > > +
> > > >    static tree
> > > > -tsubst_nested_requirement (tree t, tree args, subst_info info)
> > > > +tsubst_nested_requirement (tree t, tree args, sat_info info)
> > > >    {
> > > >      sat_info quiet (tf_none, info.in_decl);
> > > >      tree result = constraint_satisfaction_value (t, args, quiet);
> > > > -  if (result != boolean_true_node)
> > > > -    return error_mark_node;
> > > > -  return boolean_true_node;
> > > > +  if (result == boolean_true_node)
> > > > +    return boolean_true_node;
> > > > +
> > > > +  if (result == boolean_false_node
> > > > +      && info.diagnose_unsatisfaction_p ())
> > > > +    {
> > > > +      tree expr = TREE_OPERAND (t, 0);
> > > > +      location_t loc = cp_expr_location (t);
> > > > +      if (diagnosing_failed_constraint::replay_errors_p ())
> > > > +	{
> > > > +	  /* Replay the substitution error.  */
> > > > +	  inform (loc, "nested requirement %qE is not satisfied, because",
> > > > expr);
> > > > +	  constraint_satisfaction_value (t, args, info);
> > > > +	}
> > > > +      else
> > > > +	inform (loc, "nested requirement %qE is not satisfied", expr);
> > > > +    }
> > > > +
> > > > +  return error_mark_node;
> > > >    }
> > > >      /* Substitute ARGS into the requirement T.  */
> > > >      static tree
> > > > -tsubst_requirement (tree t, tree args, subst_info info)
> > > > +tsubst_requirement (tree t, tree args, sat_info info)
> > > >    {
> > > >      iloc_sentinel loc_s (cp_expr_location (t));
> > > >      switch (TREE_CODE (t))
> > > > @@ -2151,30 +2268,22 @@ tsubst_constraint_variables (tree t, tree args,
> > > > subst_info info)
> > > >       in its requirements ... In such cases, the expression evaluates
> > > >       to false; it does not cause the program to be ill-formed.
> > > >    -   However, there are cases where substitution must produce a
> > > > -   new requires-expression, that is not a template constraint.
> > > > -   For example:
> > > > +   When substituting through a REQUIRES_EXPR as part of template
> > > > +   instantiation, we call this routine with info.quiet() true.
> > > >    -        template<typename T>
> > > > -        class X {
> > > > -          template<typename U>
> > > > -          static constexpr bool var = requires (U u) { T::fn(u); };
> > > > -        };
> > > > +   When evaluating a REQUIRES_EXPR that appears outside a template in
> > > > +   cp_parser_requires_expression, we call this routine with
> > > > +   info.noisy() true.
> > > >    -   In the instantiation of X<Y> (assuming Y defines fn), then the
> > > > -   instantiated requires-expression would include Y::fn(u). If any
> > > > -   substitution in the requires-expression fails, we can immediately
> > > > -   fold the expression to false, as would be the case e.g., when
> > > > -   instantiation X<int>.  */
> > > > +   Finally, when diagnosing unsatisfaction from
> > > > diagnose_atomic_constraint
> > > > +   and when diagnosing a false REQUIRES_EXPR via diagnose_constraints,
> > > > +   we call this routine with info.diagnose_unsatisfaction_p() true.  */
> > > >    -tree
> > > > -tsubst_requires_expr (tree t, tree args,
> > > > -		      tsubst_flags_t complain, tree in_decl)
> > > > +static tree
> > > > +tsubst_requires_expr (tree t, tree args, sat_info info)
> > > >    {
> > > >      local_specialization_stack stack (lss_copy);
> > > >    -  subst_info info (complain, in_decl);
> > > > -
> > > >      /* A requires-expression is an unevaluated context.  */
> > > >      cp_unevaluated u;
> > > >    @@ -2186,7 +2295,7 @@ tsubst_requires_expr (tree t, tree args,
> > > >    	 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);
> > > > +      REQUIRES_EXPR_EXTRA_ARGS (t) = build_extra_args (t, args,
> > > > info.complain);
> > > >          return t;
> > > >        }
> > > >    @@ -2207,6 +2316,16 @@ tsubst_requires_expr (tree t, tree args,
> > > >      return boolean_true_node;
> > > >    }
> > > >    +/* Public wrapper for the above.  */
> > > > +
> > > > +tree
> > > > +tsubst_requires_expr (tree t, tree args,
> > > > +		      tsubst_flags_t complain, tree in_decl)
> > > > +{
> > > > +  sat_info info (complain, in_decl);
> > > > +  return tsubst_requires_expr (t, args, info);
> > > > +}
> > > > +
> > > >    /* Substitute ARGS into the constraint information CI, producing a
> > > > new
> > > >       constraint record.  */
> > > >    @@ -2790,7 +2909,7 @@ get_mapped_args (tree map)
> > > >      return args;
> > > >    }
> > > >    -static void diagnose_atomic_constraint (tree, tree, tree,
> > > > subst_info);
> > > > +static void diagnose_atomic_constraint (tree, tree, tree, sat_info);
> > > >      /* Compute the satisfaction of an atomic constraint.  */
> > > >    @@ -3440,11 +3559,10 @@ get_constraint_error_location (tree t)
> > > >      /* Emit a diagnostic for a failed trait.  */
> > > >    -void
> > > > -diagnose_trait_expr (tree expr, tree map)
> > > > +static void
> > > > +diagnose_trait_expr (tree expr, tree args)
> > > >    {
> > > >      location_t loc = cp_expr_location (expr);
> > > > -  tree args = get_mapped_args (map);
> > > >        /* Build a "fake" version of the instantiated trait, so we can
> > > >         get the instantiated types from result.  */
> > > > @@ -3524,192 +3642,11 @@ diagnose_trait_expr (tree expr, tree map)
> > > >        }
> > > >    }
> > > >    -static tree
> > > > -diagnose_valid_expression (tree expr, tree args, tree in_decl)
> > > > -{
> > > > -  tree result = tsubst_expr (expr, args, tf_none, in_decl, false);
> > > > -  if (result != error_mark_node
> > > > -      && convert_to_void (result, ICV_STATEMENT, tf_none) !=
> > > > error_mark_node)
> > > > -    return result;
> > > > -
> > > > -  location_t loc = cp_expr_loc_or_input_loc (expr);
> > > > -  if (diagnosing_failed_constraint::replay_errors_p ())
> > > > -    {
> > > > -      /* Replay the substitution error.  */
> > > > -      inform (loc, "the required expression %qE is invalid, because",
> > > > expr);
> > > > -      if (result == error_mark_node)
> > > > -	tsubst_expr (expr, args, tf_error, in_decl, false);
> > > > -      else
> > > > -	convert_to_void (result, ICV_STATEMENT, tf_error);
> > > > -    }
> > > > -  else
> > > > -    inform (loc, "the required expression %qE is invalid", expr);
> > > > -
> > > > -  return error_mark_node;
> > > > -}
> > > > -
> > > > -static tree
> > > > -diagnose_valid_type (tree type, tree args, tree in_decl)
> > > > -{
> > > > -  tree result = tsubst (type, args, tf_none, in_decl);
> > > > -  if (result != error_mark_node)
> > > > -    return result;
> > > > -
> > > > -  location_t loc = cp_expr_loc_or_input_loc (type);
> > > > -  if (diagnosing_failed_constraint::replay_errors_p ())
> > > > -    {
> > > > -      /* Replay the substitution error.  */
> > > > -      inform (loc, "the required type %qT is invalid, because", type);
> > > > -      tsubst (type, args, tf_error, in_decl);
> > > > -    }
> > > > -  else
> > > > -    inform (loc, "the required type %qT is invalid", type);
> > > > -
> > > > -  return error_mark_node;
> > > > -}
> > > > -
> > > > -static void
> > > > -diagnose_simple_requirement (tree req, tree args, tree in_decl)
> > > > -{
> > > > -  diagnose_valid_expression (TREE_OPERAND (req, 0), args, in_decl);
> > > > -}
> > > > -
> > > > -static void
> > > > -diagnose_compound_requirement (tree req, tree args, tree in_decl)
> > > > -{
> > > > -  tree expr = TREE_OPERAND (req, 0);
> > > > -  expr = diagnose_valid_expression (expr, args, in_decl);
> > > > -  if (expr == error_mark_node)
> > > > -    return;
> > > > -
> > > > -  location_t loc = cp_expr_loc_or_input_loc (expr);
> > > > -
> > > > -  /* Check the noexcept condition.  */
> > > > -  if (COMPOUND_REQ_NOEXCEPT_P (req) && !expr_noexcept_p (expr,
> > > > tf_none))
> > > > -    inform (loc, "%qE is not %<noexcept%>", expr);
> > > > -
> > > > -  tree type = TREE_OPERAND (req, 1);
> > > > -  type = diagnose_valid_type (type, args, in_decl);
> > > > -  if (type == error_mark_node)
> > > > -    return;
> > > > -
> > > > -  if (type)
> > > > -    {
> > > > -      subst_info quiet (tf_none, in_decl);
> > > > -      subst_info noisy (tf_error, in_decl);
> > > > -
> > > > -      /* Check the expression against the result type.  */
> > > > -      if (tree placeholder = type_uses_auto (type))
> > > > -	{
> > > > -	  if (!type_deducible_p (expr, type, placeholder, args, quiet))
> > > > -	    {
> > > > -	      tree orig_expr = TREE_OPERAND (req, 0);
> > > > -	      if (diagnosing_failed_constraint::replay_errors_p ())
> > > > -		{
> > > > -		  inform (loc,
> > > > -			  "%qE does not satisfy return-type-requirement, "
> > > > -			  "because", orig_expr);
> > > > -		  /* Further explain the reason for the error.  */
> > > > -		  type_deducible_p (expr, type, placeholder, args, noisy);
> > > > -		}
> > > > -	      else
> > > > -		inform (loc, "%qE does not satisfy return-type-requirement",
> > > > -			orig_expr);
> > > > -	    }
> > > > -	}
> > > > -      else if (!expression_convertible_p (expr, type, quiet))
> > > > -	{
> > > > -	  tree orig_expr = TREE_OPERAND (req, 0);
> > > > -	  if (diagnosing_failed_constraint::replay_errors_p ())
> > > > -	    {
> > > > -	      inform (loc, "cannot convert %qE to %qT because", orig_expr,
> > > > type);
> > > > -	      /* Further explain the reason for the error.  */
> > > > -	      expression_convertible_p (expr, type, noisy);
> > > > -	    }
> > > > -	  else
> > > > -	    inform (loc, "cannot convert %qE to %qT", orig_expr, type);
> > > > -	}
> > > > -    }
> > > > -}
> > > > -
> > > > -static void
> > > > -diagnose_type_requirement (tree req, tree args, tree in_decl)
> > > > -{
> > > > -  tree type = TREE_OPERAND (req, 0);
> > > > -  diagnose_valid_type (type, args, in_decl);
> > > > -}
> > > > -
> > > > -static void
> > > > -diagnose_nested_requirement (tree req, tree args)
> > > > -{
> > > > -  /* Quietly check for satisfaction first.  */
> > > > -  sat_info quiet (tf_none, NULL_TREE);
> > > > -  tree result = satisfy_nondeclaration_constraints (req, args, quiet);
> > > > -  if (result == boolean_true_node)
> > > > -    return;
> > > > -
> > > > -  tree expr = TREE_OPERAND (req, 0);
> > > > -  location_t loc = cp_expr_location (expr);
> > > > -  if (diagnosing_failed_constraint::replay_errors_p ())
> > > > -    {
> > > > -      /* Replay the substitution error with re-normalized requirements.
> > > > */
> > > > -      inform (loc, "nested requirement %qE is not satisfied, because",
> > > > expr);
> > > > -
> > > > -      sat_info noisy (tf_warning_or_error, NULL_TREE,
> > > > /*diag_unsat=*/true);
> > > > -      satisfy_nondeclaration_constraints (req, args, noisy);
> > > > -    }
> > > > -  else
> > > > -    inform (loc, "nested requirement %qE is not satisfied", expr);
> > > > -
> > > > -}
> > > > -
> > > > -static void
> > > > -diagnose_requirement (tree req, tree args, tree in_decl)
> > > > -{
> > > > -  iloc_sentinel loc_s (cp_expr_location (req));
> > > > -  switch (TREE_CODE (req))
> > > > -    {
> > > > -    case SIMPLE_REQ:
> > > > -      return diagnose_simple_requirement (req, args, in_decl);
> > > > -    case COMPOUND_REQ:
> > > > -      return diagnose_compound_requirement (req, args, in_decl);
> > > > -    case TYPE_REQ:
> > > > -      return diagnose_type_requirement (req, args, in_decl);
> > > > -    case NESTED_REQ:
> > > > -      return diagnose_nested_requirement (req, args);
> > > > -    default:
> > > > -       gcc_unreachable ();
> > > > -    }
> > > > -}
> > > > -
> > > > -static void
> > > > -diagnose_requires_expr (tree expr, tree map, tree in_decl)
> > > > -{
> > > > -  local_specialization_stack stack (lss_copy);
> > > > -  tree parms = TREE_OPERAND (expr, 0);
> > > > -  tree body = TREE_OPERAND (expr, 1);
> > > > -  tree args = get_mapped_args (map);
> > > > -
> > > > -  cp_unevaluated u;
> > > > -  subst_info info (tf_warning_or_error, NULL_TREE);
> > > > -  tree vars = tsubst_constraint_variables (parms, args, info);
> > > > -  if (vars == error_mark_node)
> > > > -    return;
> > > > -
> > > > -  tree p = body;
> > > > -  while (p)
> > > > -    {
> > > > -      tree req = TREE_VALUE (p);
> > > > -      diagnose_requirement (req, args, in_decl);
> > > > -      p = TREE_CHAIN (p);
> > > > -    }
> > > > -}
> > > > -
> > > >    /* Diagnose a substitution failure in the atomic constraint T when
> > > > applied
> > > >       with the instantiated parameter mapping MAP.  */
> > > >      static void
> > > > -diagnose_atomic_constraint (tree t, tree map, tree result, subst_info
> > > > info)
> > > > +diagnose_atomic_constraint (tree t, tree map, tree result, sat_info
> > > > info)
> > > >    {
> > > >      /* If the constraint is already ill-formed, we've previously
> > > > diagnosed
> > > >         the reason. We should still say why the constraints aren't
> > > > satisfied.
> > > > */
> > > > @@ -3730,13 +3667,16 @@ diagnose_atomic_constraint (tree t, tree map,
> > > > tree
> > > > result, subst_info info)
> > > >      /* Generate better diagnostics for certain kinds of expressions.
> > > > */
> > > >      tree expr = ATOMIC_CONSTR_EXPR (t);
> > > >      STRIP_ANY_LOCATION_WRAPPER (expr);
> > > > +  tree args = get_mapped_args (map);
> > > >      switch (TREE_CODE (expr))
> > > >        {
> > > >        case TRAIT_EXPR:
> > > > -      diagnose_trait_expr (expr, map);
> > > > +      diagnose_trait_expr (expr, args);
> > > >          break;
> > > >        case REQUIRES_EXPR:
> > > > -      diagnose_requires_expr (expr, map, info.in_decl);
> > > > +      gcc_checking_assert (info.diagnose_unsatisfaction_p ());
> > > > +      info.in_decl = NULL_TREE;
> > > > +      tsubst_requires_expr (expr, args, info);
> > > >          break;
> > > >        default:
> > > >          if (!same_type_p (TREE_TYPE (result), boolean_type_node))
> > > > @@ -3807,7 +3747,7 @@ diagnose_constraints (location_t loc, tree t, tree
> > > > args)
> > > >        {
> > > >          gcc_assert (!args);
> > > >          ++current_constraint_diagnosis_depth;
> > > > -      diagnose_requires_expr (t, /*map=*/NULL_TREE,
> > > > /*in_decl=*/NULL_TREE);
> > > > +      tsubst_requires_expr (t, /*args=*/NULL_TREE, noisy);
> > > >          --current_constraint_diagnosis_depth;
> > > >        }
> > > >      else
> > > > diff --git a/gcc/testsuite/g++.dg/concepts/diagnostic1.C
> > > > b/gcc/testsuite/g++.dg/concepts/diagnostic1.C
> > > > index 29c78c4c730..23bd592411e 100644
> > > > --- a/gcc/testsuite/g++.dg/concepts/diagnostic1.C
> > > > +++ b/gcc/testsuite/g++.dg/concepts/diagnostic1.C
> > > > @@ -8,7 +8,7 @@ concept bool SameAs = __is_same_as(T, U);
> > > >    template <class T>
> > > >    concept bool R1 = requires (T& t) { // { dg-message "in requirements"
> > > > }
> > > >      { t.begin() } -> T;		// { dg-error "no match" }
> > > > -  { t.end() } -> SameAs<T*>;	// { dg-message "does not satisfy" }
> > > > +  { t.end() } -> SameAs<T*>;
> > > 
> > > Are we no longer giving a message for this line?  That seems like a
> > > diagnostic
> > > quality regression.
> > 
> > This happens because diagnose_requires_expr didn't short-circuit its
> > processing of requirements upon seeing a failed requirement, and this
> > behavior got lost when it was merged with tsubst_requires_expr, which
> > does short-circuit.  I wasn't sure if we wanted to keep this behavior or
> > not :)
> > 
> > The below restores the previous non-short-circuiting behavior of
> > diagnose_requires_expr inside tsubst_requires_expr, and addresses the
> > TODO added by v2 of patch5/6.
> > 
> > -- >8 --
> > 
> > Subject: [PATCH] c++: Unify REQUIRES_EXPR evaluation / diagnostic routines
> > 
> > This folds the diagnose_requires_expr routines into the corresponding
> > tsubst_requires_expr ones.  This is achieved by making the latter
> > routines take a sat_info instead of a subst_info, and assigning the
> > appropriate meanings to the flags sat_info::noisy and
> > sat_info::diagnose_unsatisfaction_p during tsubst_requires_expr:
> > info.noisy() controls whether to diagnose invalid types and expressions
> > inside the requirements, and info.diagnose_unsatisfaction_p() controls
> > whether to additionally diagnose why the requires-expression evaluates
> > to false.
> > 
> > gcc/cp/ChangeLog:
> > 
> > 	* constraint.cc (struct sat_info): Document the different
> > 	meanings of noisy() and diagnose_unsatisfaction_p() during
> > 	satisfaction and requires-expression evaluation.
> > 	(tsubst_valid_expression_requirement): Take a sat_info instead
> > 	of a subst_info.  Perform the substitution quietly first.  Fold
> > 	in error-replaying code from diagnose_valid_expression.
> > 	(tsubst_simple_requirement): Take a sat_info instead of a
> > 	subst_info.
> > 	(tsubst_type_requirement_1): New.  Fold in error-replaying code
> > 	from diagnose_valid_type.
> > 	(tsubst_type_requirement): Use the above.  Take a sat_info
> > 	instead of a subst_info.
> > 	(tsubst_compound_requirement): Likewise.  Fold in
> > 	error-replaying code from diagnose_compound_requirement.
> > 	(tsubst_nested_requirement): Take a sat_info instead of a
> > 	subst_info.  Fold in error-replaying code from
> > 	diagnose_nested_requirement.
> > 	(tsubst_requirement): Take a sat_info instead of a subst_info.
> > 	(tsubst_requires_expr): Split into two versions, one that takes
> > 	a sat_info argument and another that takes a complain and
> > 	in_decl argument.  Remove outdated documentation.  Document the
> > 	effects of the sat_info argument.  Don't short-circuit
> > 	processing of requirements when diagnosing unsatisfaction,
> > 	mirroring diagnose_requires_expr.
> > 	(satisfy_nondeclaration_constraint) <case REQUIRES_EXPR>: Remove
> > 	assert, and se the three-parameter version of tsubst_requires_expr.
> > 	(diagnose_trait_expr): Make static.  Take a template argument
> > 	vector instead of a parameter mapping.
> > 	(diagnose_valid_expression): Remove.
> > 	(diagnose_valid_type): Remove.
> > 	(diagnose_simple_requirement): Remove.
> > 	(diagnose_compound_requirement): Remove.
> > 	(diagnose_type_requirement): Remove.
> > 	(diagnose_nested_requirement): Remove.
> > 	(diagnose_requirement): Remove.
> > 	(diagnose_requires_expr): Remove.
> > 	(diagnose_atomic_constraint): Take a sat_info instead of a
> > 	subst_info.  Adjust call to diagnose_trait_expr.  Call
> > 	tsubst_requires_expr instead of diagnose_requires_expr.
> > 	(diagnose_constraints): Remove special casing of REQUIRES_EXPR
> > 	and just always call constraint_satisfaction_value.
> > ---
> >   gcc/cp/constraint.cc | 444 ++++++++++++++++++-------------------------
> >   1 file changed, 189 insertions(+), 255 deletions(-)
> > 
> > diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> > index 0949788aa29..b2d25f3c232 100644
> > --- a/gcc/cp/constraint.cc
> > +++ b/gcc/cp/constraint.cc
> > @@ -100,17 +100,30 @@ struct subst_info
> >     /* Provides additional context for satisfaction.
> >   -   The flag noisy() controls whether to diagnose ill-formed satisfaction,
> > -   such as the satisfaction value of an atom being non-bool or
> > non-constant.
> > -
> > -   The flag diagnose_unsatisfaction_p() controls whether to explain why
> > -   a constraint is not satisfied.
> > -
> > -   The entrypoints to satisfaction for which we set noisy+unsat are
> > -   diagnose_constraints and diagnose_nested_requirement.  The entrypoint
> > for
> > -   which we set noisy-unsat is the replay inside
> > constraint_satisfaction_value.
> > -   From constraints_satisfied_p, we enter satisfaction quietly (both flags
> > -   cleared).  */
> > +   During satisfaction:
> > +    - The flag noisy() controls whether to diagnose ill-formed
> > satisfaction,
> > +      such as the satisfaction value of an atom being non-bool or
> > non-constant.
> > +    - The flag diagnose_unsatisfaction_p() controls whether to additionally
> > +      explain why a constraint is not satisfied.
> > +    - We enter satisfaction with noisy+unsat from diagnose_constraints.
> > +    - We enter satisfaction with noisy-unsat from the replay inside
> > +      constraint_satisfaction_value.
> > +    - We enter satisfaction quietly (both flags cleared) from
> > +      constraints_satisfied_p.
> > +
> > +   During evaluation of a requires-expression:
> > +    - The flag noisy() controls whether to diagnose ill-formed types and
> > +      expressions inside its requirements.
> > +    - The flag diagnose_unsatisfaction_p() controls whether to additionally
> > +      explain why the requires-expression evaluates to false.
> > +    - We enter tsubst_requires_expr with noisy+unsat from
> > diagnose_constraints
> > +      and from diagnose_atomic_constraint.
> > +    - We enter tsubst_requires_expr with noisy-unsat from
> > +      cp_parser_requires_expression when processing a requires-expression
> > that
> > +      appears outside a template.
> > +    - We enter tsubst_requires_expr quietly (both flags cleared) when
> > +      substituting through a requires-expression as part of template
> > +      instantiation.  */
> >     struct sat_info : subst_info
> >   {
> > @@ -1926,22 +1939,44 @@ hash_placeholder_constraint (tree c)
> >     return val;
> >   }
> >   -/* Substitute through the simple requirement.  */
> > +/* Substitute through the expression of a simple requirement or
> > +   compound requirement.  */
> >     static tree
> > -tsubst_valid_expression_requirement (tree t, tree args, subst_info info)
> > +tsubst_valid_expression_requirement (tree t, tree args, sat_info info)
> >   {
> > -  tree r = tsubst_expr (t, args, info.complain, info.in_decl, false);
> > -  if (convert_to_void (r, ICV_STATEMENT, info.complain) == error_mark_node)
> > -    return error_mark_node;
> > -  return r;
> > +  tree r = tsubst_expr (t, args, tf_none, info.in_decl, false);
> > +  if (convert_to_void (r, ICV_STATEMENT, tf_none) != error_mark_node)
> > +    return r;
> > +
> > +  if (info.diagnose_unsatisfaction_p ())
> > +    {
> > +      location_t loc = cp_expr_loc_or_input_loc (t);
> > +      if (diagnosing_failed_constraint::replay_errors_p ())
> > +	{
> > +	  inform (loc, "the required expression %qE is invalid, because", t);
> > +	  if (r == error_mark_node)
> > +	    tsubst_expr (t, args, info.complain, info.in_decl, false);
> > +	  else
> > +	    convert_to_void (r, ICV_STATEMENT, info.complain);
> > +	}
> > +      else
> > +	inform (loc, "the required expression %qE is invalid", t);
> > +    }
> > +  else if (info.noisy ())
> > +    {
> > +      r = tsubst_expr (t, args, info.complain, info.in_decl, false);
> > +      convert_to_void (r, ICV_STATEMENT, info.complain);
> > +    }
> > +
> > +  return error_mark_node;
> >   }
> >       /* Substitute through the simple requirement.  */
> >     static tree
> > -tsubst_simple_requirement (tree t, tree args, subst_info info)
> > +tsubst_simple_requirement (tree t, tree args, sat_info info)
> >   {
> >     tree t0 = TREE_OPERAND (t, 0);
> >     tree expr = tsubst_valid_expression_requirement (t0, args, info);
> > @@ -1950,13 +1985,41 @@ tsubst_simple_requirement (tree t, tree args,
> > subst_info info)
> >     return boolean_true_node;
> >   }
> >   +/* Subroutine of tsubst_type_requirement that performs the actual
> > substitution
> > +   and diagnosing.  Also used by tsubst_compound_requirement.  */
> > +
> > +static tree
> > +tsubst_type_requirement_1 (tree t, tree args, sat_info info, location_t
> > loc)
> > +{
> > +  tree r = tsubst (t, args, tf_none, info.in_decl);
> > +  if (r != error_mark_node)
> > +    return r;
> > +
> > +  if (info.diagnose_unsatisfaction_p ())
> > +    {
> > +      if (diagnosing_failed_constraint::replay_errors_p ())
> > +	{
> > +	  /* Replay the substitution error.  */
> > +	  inform (loc, "the required type %qT is invalid, because", t);
> > +	  tsubst (t, args, info.complain, info.in_decl);
> > +	}
> > +      else
> > +	inform (loc, "the required type %qT is invalid", t);
> > +    }
> > +  else if (info.noisy ())
> > +    tsubst (t, args, info.complain, info.in_decl);
> > +
> > +  return error_mark_node;
> > +}
> > +
> > +
> >   /* Substitute through the type requirement.  */
> >     static tree
> > -tsubst_type_requirement (tree t, tree args, subst_info info)
> > +tsubst_type_requirement (tree t, tree args, sat_info info)
> >   {
> >     tree t0 = TREE_OPERAND (t, 0);
> > -  tree type = tsubst (t0, args, info.complain, info.in_decl);
> > +  tree type = tsubst_type_requirement_1 (t0, args, info, EXPR_LOCATION
> > (t));
> >     if (type == error_mark_node)
> >       return error_mark_node;
> >     return boolean_true_node;
> > @@ -2013,7 +2076,7 @@ expression_convertible_p (tree expr, tree type,
> > subst_info info)
> >   /* Substitute through the compound requirement.  */
> >     static tree
> > -tsubst_compound_requirement (tree t, tree args, subst_info info)
> > +tsubst_compound_requirement (tree t, tree args, sat_info info)
> >   {
> >     tree t0 = TREE_OPERAND (t, 0);
> >     tree t1 = TREE_OPERAND (t, 1);
> > @@ -2021,13 +2084,20 @@ tsubst_compound_requirement (tree t, tree args,
> > subst_info info)
> >     if (expr == error_mark_node)
> >       return error_mark_node;
> >   +  location_t loc = cp_expr_loc_or_input_loc (expr);
> > +
> >     /* Check the noexcept condition.  */
> >     bool noexcept_p = COMPOUND_REQ_NOEXCEPT_P (t);
> >     if (noexcept_p && !expr_noexcept_p (expr, tf_none))
> > -    return error_mark_node;
> > +    {
> > +      if (info.diagnose_unsatisfaction_p ())
> > +	inform (loc, "%qE is not %<noexcept%>", expr);
> > +      else
> > +	return error_mark_node;
> > +    }
> >       /* Substitute through the type expression, if any.  */
> > -  tree type = tsubst (t1, args, info.complain, info.in_decl);
> > +  tree type = tsubst_type_requirement_1 (t1, args, info, EXPR_LOCATION
> > (t));
> >     if (type == error_mark_node)
> >       return error_mark_node;
> >   @@ -2039,29 +2109,76 @@ tsubst_compound_requirement (tree t, tree args,
> > subst_info info)
> >         if (tree placeholder = type_uses_auto (type))
> >   	{
> >   	  if (!type_deducible_p (expr, type, placeholder, args, quiet))
> > -	    return error_mark_node;
> > +	    {
> > +	      if (info.diagnose_unsatisfaction_p ())
> > +		{
> > +		  if (diagnosing_failed_constraint::replay_errors_p ())
> > +		    {
> > +		      inform (loc,
> > +			      "%qE does not satisfy return-type-requirement, "
> > +			      "because", t0);
> > +		      /* Further explain the reason for the error.  */
> > +		      type_deducible_p (expr, type, placeholder, args, info);
> > +		    }
> > +		  else
> > +		    inform (loc,
> > +			    "%qE does not satisfy return-type-requirement",
> > t0);
> > +		}
> > +	      return error_mark_node;
> > +	    }
> >   	}
> >         else if (!expression_convertible_p (expr, type, quiet))
> > -	return error_mark_node;
> > +	{
> > +	  if (info.diagnose_unsatisfaction_p ())
> > +	    {
> > +	      if (diagnosing_failed_constraint::replay_errors_p ())
> > +		{
> > +		  inform (loc, "cannot convert %qE to %qT because", t0, type);
> > +		  /* Further explain the reason for the error.  */
> > +		  expression_convertible_p (expr, type, info);
> > +		}
> > +	      else
> > +		inform (loc, "cannot convert %qE to %qT", t0, type);
> > +	    }
> > +	  return error_mark_node;
> > +	}
> >       }
> >       return boolean_true_node;
> >   }
> >   +/* Substitute through the nested requirement.  */
> > +
> >   static tree
> > -tsubst_nested_requirement (tree t, tree args, subst_info info)
> > +tsubst_nested_requirement (tree t, tree args, sat_info info)
> >   {
> >     sat_info quiet (tf_none, info.in_decl);
> >     tree result = constraint_satisfaction_value (t, args, quiet);
> > -  if (result != boolean_true_node)
> > -    return error_mark_node;
> > -  return boolean_true_node;
> > +  if (result == boolean_true_node)
> > +    return boolean_true_node;
> > +
> > +  if (result == boolean_false_node
> > +      && info.diagnose_unsatisfaction_p ())
> > +    {
> > +      tree expr = TREE_OPERAND (t, 0);
> > +      location_t loc = cp_expr_location (t);
> > +      if (diagnosing_failed_constraint::replay_errors_p ())
> > +	{
> > +	  /* Replay the substitution error.  */
> > +	  inform (loc, "nested requirement %qE is not satisfied, because",
> > expr);
> > +	  constraint_satisfaction_value (t, args, info);
> > +	}
> > +      else
> > +	inform (loc, "nested requirement %qE is not satisfied", expr);
> > +    }
> > +
> > +  return error_mark_node;
> >   }
> >     /* Substitute ARGS into the requirement T.  */
> >     static tree
> > -tsubst_requirement (tree t, tree args, subst_info info)
> > +tsubst_requirement (tree t, tree args, sat_info info)
> >   {
> >     iloc_sentinel loc_s (cp_expr_location (t));
> >     switch (TREE_CODE (t))
> > @@ -2151,30 +2268,22 @@ tsubst_constraint_variables (tree t, tree args,
> > subst_info info)
> >      in its requirements ... In such cases, the expression evaluates
> >      to false; it does not cause the program to be ill-formed.
> >   -   However, there are cases where substitution must produce a
> > -   new requires-expression, that is not a template constraint.
> > -   For example:
> > +   When substituting through a REQUIRES_EXPR as part of template
> > +   instantiation, we call this routine with info.quiet() true.
> >   -        template<typename T>
> > -        class X {
> > -          template<typename U>
> > -          static constexpr bool var = requires (U u) { T::fn(u); };
> > -        };
> > +   When evaluating a REQUIRES_EXPR that appears outside a template in
> > +   cp_parser_requires_expression, we call this routine with
> > +   info.noisy() true.
> >   -   In the instantiation of X<Y> (assuming Y defines fn), then the
> > -   instantiated requires-expression would include Y::fn(u). If any
> > -   substitution in the requires-expression fails, we can immediately
> > -   fold the expression to false, as would be the case e.g., when
> > -   instantiation X<int>.  */
> > +   Finally, when diagnosing unsatisfaction from diagnose_atomic_constraint
> > +   and when diagnosing a false REQUIRES_EXPR via diagnose_constraints,
> > +   we call this routine with info.diagnose_unsatisfaction_p() true.  */
> >   -tree
> > -tsubst_requires_expr (tree t, tree args,
> > -		      tsubst_flags_t complain, tree in_decl)
> > +static tree
> > +tsubst_requires_expr (tree t, tree args, sat_info info)
> >   {
> >     local_specialization_stack stack (lss_copy);
> >   -  subst_info info (complain, in_decl);
> > -
> >     /* A requires-expression is an unevaluated context.  */
> >     cp_unevaluated u;
> >   @@ -2186,7 +2295,7 @@ tsubst_requires_expr (tree t, tree args,
> >   	 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);
> > +      REQUIRES_EXPR_EXTRA_ARGS (t) = build_extra_args (t, args,
> > info.complain);
> >         return t;
> >       }
> >   @@ -2197,14 +2306,30 @@ tsubst_requires_expr (tree t, tree args,
> >   	return boolean_false_node;
> >       }
> >   +  tree result = boolean_true_node;
> >     for (tree reqs = REQUIRES_EXPR_REQS (t); reqs; reqs = TREE_CHAIN (reqs))
> >       {
> >         tree req = TREE_VALUE (reqs);
> > -      tree result = tsubst_requirement (req, args, info);
> > -      if (result == error_mark_node)
> > -	return boolean_false_node;
> > +      if (tsubst_requirement (req, args, info) == error_mark_node)
> > +	{
> > +	  result = boolean_false_node;
> > +	  if (info.diagnose_unsatisfaction_p ())
> > +	    /* Keep going so that we diagnose all failed requirements.  */;
> > +	  else
> > +	    break;
> > +	}
> >       }
> > -  return boolean_true_node;
> > +  return result;
> > +}
> > +
> > +/* Public wrapper for the above.  */
> > +
> > +tree
> > +tsubst_requires_expr (tree t, tree args,
> > +		      tsubst_flags_t complain, tree in_decl)
> > +{
> > +  sat_info info (complain, in_decl);
> > +  return tsubst_requires_expr (t, args, info);
> >   }
> >     /* Substitute ARGS into the constraint information CI, producing a new
> > @@ -2790,7 +2915,7 @@ get_mapped_args (tree map)
> >     return args;
> >   }
> >   -static void diagnose_atomic_constraint (tree, tree, tree, subst_info);
> > +static void diagnose_atomic_constraint (tree, tree, tree, sat_info);
> >     /* Compute the satisfaction of an atomic constraint.  */
> >   @@ -2976,14 +3101,10 @@ satisfy_nondeclaration_constraints (tree t, tree
> > args, sat_info info)
> >     /* Handle REQUIRES_EXPR directly, bypassing satisfaction.  */
> >     if (TREE_CODE (t) == REQUIRES_EXPR)
> >       {
> > -      /* TODO: Remove this assert and the special casing of REQUIRES_EXPRs
> > -	 from diagnose_constraints once we merge tsubst_requires_expr and
> > -	 diagnose_requires_expr.  */
> > -      gcc_assert (!info.diagnose_unsatisfaction_p ());
> >         auto ovr = make_temp_override (current_constraint_diagnosis_depth);
> >         if (info.noisy ())
> >   	++current_constraint_diagnosis_depth;
> > -      return tsubst_requires_expr (t, args, info.complain, info.in_decl);
> > +      return tsubst_requires_expr (t, args, info);
> >       }
> >       /* Get the normalized constraints.  */
> > @@ -3466,11 +3587,10 @@ get_constraint_error_location (tree t)
> >     /* Emit a diagnostic for a failed trait.  */
> >   -void
> > -diagnose_trait_expr (tree expr, tree map)
> > +static void
> > +diagnose_trait_expr (tree expr, tree args)
> >   {
> >     location_t loc = cp_expr_location (expr);
> > -  tree args = get_mapped_args (map);
> >       /* Build a "fake" version of the instantiated trait, so we can
> >        get the instantiated types from result.  */
> > @@ -3550,192 +3670,11 @@ diagnose_trait_expr (tree expr, tree map)
> >       }
> >   }
> >   -static tree
> > -diagnose_valid_expression (tree expr, tree args, tree in_decl)
> > -{
> > -  tree result = tsubst_expr (expr, args, tf_none, in_decl, false);
> > -  if (result != error_mark_node
> > -      && convert_to_void (result, ICV_STATEMENT, tf_none) !=
> > error_mark_node)
> > -    return result;
> > -
> > -  location_t loc = cp_expr_loc_or_input_loc (expr);
> > -  if (diagnosing_failed_constraint::replay_errors_p ())
> > -    {
> > -      /* Replay the substitution error.  */
> > -      inform (loc, "the required expression %qE is invalid, because",
> > expr);
> > -      if (result == error_mark_node)
> > -	tsubst_expr (expr, args, tf_error, in_decl, false);
> > -      else
> > -	convert_to_void (result, ICV_STATEMENT, tf_error);
> > -    }
> > -  else
> > -    inform (loc, "the required expression %qE is invalid", expr);
> > -
> > -  return error_mark_node;
> > -}
> > -
> > -static tree
> > -diagnose_valid_type (tree type, tree args, tree in_decl)
> > -{
> > -  tree result = tsubst (type, args, tf_none, in_decl);
> > -  if (result != error_mark_node)
> > -    return result;
> > -
> > -  location_t loc = cp_expr_loc_or_input_loc (type);
> > -  if (diagnosing_failed_constraint::replay_errors_p ())
> > -    {
> > -      /* Replay the substitution error.  */
> > -      inform (loc, "the required type %qT is invalid, because", type);
> > -      tsubst (type, args, tf_error, in_decl);
> > -    }
> > -  else
> > -    inform (loc, "the required type %qT is invalid", type);
> > -
> > -  return error_mark_node;
> > -}
> > -
> > -static void
> > -diagnose_simple_requirement (tree req, tree args, tree in_decl)
> > -{
> > -  diagnose_valid_expression (TREE_OPERAND (req, 0), args, in_decl);
> > -}
> > -
> > -static void
> > -diagnose_compound_requirement (tree req, tree args, tree in_decl)
> > -{
> > -  tree expr = TREE_OPERAND (req, 0);
> > -  expr = diagnose_valid_expression (expr, args, in_decl);
> > -  if (expr == error_mark_node)
> > -    return;
> > -
> > -  location_t loc = cp_expr_loc_or_input_loc (expr);
> > -
> > -  /* Check the noexcept condition.  */
> > -  if (COMPOUND_REQ_NOEXCEPT_P (req) && !expr_noexcept_p (expr, tf_none))
> > -    inform (loc, "%qE is not %<noexcept%>", expr);
> > -
> > -  tree type = TREE_OPERAND (req, 1);
> > -  type = diagnose_valid_type (type, args, in_decl);
> > -  if (type == error_mark_node)
> > -    return;
> > -
> > -  if (type)
> > -    {
> > -      subst_info quiet (tf_none, in_decl);
> > -      subst_info noisy (tf_error, in_decl);
> > -
> > -      /* Check the expression against the result type.  */
> > -      if (tree placeholder = type_uses_auto (type))
> > -	{
> > -	  if (!type_deducible_p (expr, type, placeholder, args, quiet))
> > -	    {
> > -	      tree orig_expr = TREE_OPERAND (req, 0);
> > -	      if (diagnosing_failed_constraint::replay_errors_p ())
> > -		{
> > -		  inform (loc,
> > -			  "%qE does not satisfy return-type-requirement, "
> > -			  "because", orig_expr);
> > -		  /* Further explain the reason for the error.  */
> > -		  type_deducible_p (expr, type, placeholder, args, noisy);
> > -		}
> > -	      else
> > -		inform (loc, "%qE does not satisfy return-type-requirement",
> > -			orig_expr);
> > -	    }
> > -	}
> > -      else if (!expression_convertible_p (expr, type, quiet))
> > -	{
> > -	  tree orig_expr = TREE_OPERAND (req, 0);
> > -	  if (diagnosing_failed_constraint::replay_errors_p ())
> > -	    {
> > -	      inform (loc, "cannot convert %qE to %qT because", orig_expr,
> > type);
> > -	      /* Further explain the reason for the error.  */
> > -	      expression_convertible_p (expr, type, noisy);
> > -	    }
> > -	  else
> > -	    inform (loc, "cannot convert %qE to %qT", orig_expr, type);
> > -	}
> > -    }
> > -}
> > -
> > -static void
> > -diagnose_type_requirement (tree req, tree args, tree in_decl)
> > -{
> > -  tree type = TREE_OPERAND (req, 0);
> > -  diagnose_valid_type (type, args, in_decl);
> > -}
> > -
> > -static void
> > -diagnose_nested_requirement (tree req, tree args)
> > -{
> > -  /* Quietly check for satisfaction first.  */
> > -  sat_info quiet (tf_none, NULL_TREE);
> > -  tree result = satisfy_nondeclaration_constraints (req, args, quiet);
> > -  if (result == boolean_true_node)
> > -    return;
> > -
> > -  tree expr = TREE_OPERAND (req, 0);
> > -  location_t loc = cp_expr_location (expr);
> > -  if (diagnosing_failed_constraint::replay_errors_p ())
> > -    {
> > -      /* Replay the substitution error with re-normalized requirements.  */
> > -      inform (loc, "nested requirement %qE is not satisfied, because",
> > expr);
> > -
> > -      sat_info noisy (tf_warning_or_error, NULL_TREE, /*diag_unsat=*/true);
> > -      satisfy_nondeclaration_constraints (req, args, noisy);
> > -    }
> > -  else
> > -    inform (loc, "nested requirement %qE is not satisfied", expr);
> > -
> > -}
> > -
> > -static void
> > -diagnose_requirement (tree req, tree args, tree in_decl)
> > -{
> > -  iloc_sentinel loc_s (cp_expr_location (req));
> > -  switch (TREE_CODE (req))
> > -    {
> > -    case SIMPLE_REQ:
> > -      return diagnose_simple_requirement (req, args, in_decl);
> > -    case COMPOUND_REQ:
> > -      return diagnose_compound_requirement (req, args, in_decl);
> > -    case TYPE_REQ:
> > -      return diagnose_type_requirement (req, args, in_decl);
> > -    case NESTED_REQ:
> > -      return diagnose_nested_requirement (req, args);
> > -    default:
> > -       gcc_unreachable ();
> > -    }
> > -}
> > -
> > -static void
> > -diagnose_requires_expr (tree expr, tree map, tree in_decl)
> > -{
> > -  local_specialization_stack stack (lss_copy);
> > -  tree parms = TREE_OPERAND (expr, 0);
> > -  tree body = TREE_OPERAND (expr, 1);
> > -  tree args = get_mapped_args (map);
> > -
> > -  cp_unevaluated u;
> > -  subst_info info (tf_warning_or_error, NULL_TREE);
> > -  tree vars = tsubst_constraint_variables (parms, args, info);
> > -  if (vars == error_mark_node)
> > -    return;
> > -
> > -  tree p = body;
> > -  while (p)
> > -    {
> > -      tree req = TREE_VALUE (p);
> > -      diagnose_requirement (req, args, in_decl);
> > -      p = TREE_CHAIN (p);
> > -    }
> > -}
> > -
> >   /* Diagnose a substitution failure in the atomic constraint T when applied
> >      with the instantiated parameter mapping MAP.  */
> >     static void
> > -diagnose_atomic_constraint (tree t, tree map, tree result, subst_info info)
> > +diagnose_atomic_constraint (tree t, tree map, tree result, sat_info info)
> >   {
> >     /* If the constraint is already ill-formed, we've previously diagnosed
> >        the reason. We should still say why the constraints aren't satisfied.
> > */
> > @@ -3756,13 +3695,16 @@ diagnose_atomic_constraint (tree t, tree map, tree
> > result, subst_info info)
> >     /* Generate better diagnostics for certain kinds of expressions.  */
> >     tree expr = ATOMIC_CONSTR_EXPR (t);
> >     STRIP_ANY_LOCATION_WRAPPER (expr);
> > +  tree args = get_mapped_args (map);
> >     switch (TREE_CODE (expr))
> >       {
> >       case TRAIT_EXPR:
> > -      diagnose_trait_expr (expr, map);
> > +      diagnose_trait_expr (expr, args);
> >         break;
> >       case REQUIRES_EXPR:
> > -      diagnose_requires_expr (expr, map, info.in_decl);
> > +      gcc_checking_assert (info.diagnose_unsatisfaction_p ());
> > +      info.in_decl = NULL_TREE;
> 
> This line needs a comment.  OK with that change.

Thanks a lot.  I committed the patch with the comment:

  /* Clear in_decl before replaying the substitution to avoid emitting
     seemingly unhelpful "in declaration ..." notes that follow some
     substitution failure error messages.  */

(Currently, if in_decl is nonempty here, then it points to the constrained
declaration to which this atom belongs.  But this information is already neatly
emitted via the diagnosing_failed_constraint / maybe_print_constraint_context
machinery, so it'd be at best unnecessary to display it yet again via an "in
declaration" note.  It's a minor issue, but the testcase
g++.dg/cpp2a/concepts-pr66844.C was sensitive to this.)

> 
> > +      tsubst_requires_expr (expr, args, info);
> >         break;
> >       default:
> >         if (!same_type_p (TREE_TYPE (result), boolean_type_node))
> > @@ -3827,15 +3769,7 @@ diagnose_constraints (location_t loc, tree t, tree
> > args)
> >       /* Replay satisfaction, but diagnose unsatisfaction.  */
> >     sat_info noisy (tf_warning_or_error, NULL_TREE, /*diag_unsat=*/true);
> > -  if (TREE_CODE (t) == REQUIRES_EXPR)
> > -    {
> > -      gcc_assert (!args);
> > -      ++current_constraint_diagnosis_depth;
> > -      diagnose_requires_expr (t, /*map=*/NULL_TREE, /*in_decl=*/NULL_TREE);
> > -      --current_constraint_diagnosis_depth;
> > -    }
> > -  else
> > -    constraint_satisfaction_value (t, args, noisy);
> > +  constraint_satisfaction_value (t, args, noisy);
> >       static bool suggested_p;
> >     if (concepts_diagnostics_max_depth_exceeded_p
> > 
> 
> 


^ permalink raw reply	[flat|nested] 29+ messages in thread

end of thread, other threads:[~2021-03-03 17:10 UTC | newest]

Thread overview: 29+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-02-08 19:03 [PATCH 1/4] c++: Avoid building garbage trees from tsubst_requires_expr Patrick Palka
2021-02-08 19:03 ` [PATCH 2/4] c++: Preparatory type canonicalization fixes Patrick Palka
2021-02-09  5:11   ` Jason Merrill
2021-02-08 19:03 ` [PATCH 3/4] c++: Delay normalizing nested requirements until satisfaction Patrick Palka
2021-02-09 23:04   ` Jason Merrill
2021-02-10  0:52     ` Jason Merrill
2021-02-10 14:41     ` Patrick Palka
2021-02-12 19:11       ` Jason Merrill
2021-02-28 17:40         ` Patrick Palka
2021-03-01 22:23           ` Jason Merrill
2021-03-01 23:09             ` Patrick Palka
2021-03-02  2:26               ` Jason Merrill
2021-02-08 19:03 ` [PATCH 4/4] c++: dependent constraint on placeholder 'auto' [PR96443] Patrick Palka
2021-02-08 19:39   ` Patrick Palka
2021-02-11 16:19   ` Jason Merrill
2021-02-11 22:14     ` Patrick Palka
2021-02-12 19:14       ` Jason Merrill
2021-02-28 17:55         ` Patrick Palka
2021-03-01 22:31           ` Jason Merrill
2021-02-09  5:09 ` [PATCH 1/4] c++: Avoid building garbage trees from tsubst_requires_expr Jason Merrill
2021-02-28 17:58 ` [PATCH 5/6] c++: Clean up normalization / satisfaction routines Patrick Palka
2021-03-01 22:50   ` Jason Merrill
2021-03-02 16:25     ` Patrick Palka
2021-03-02 22:58       ` Jason Merrill
2021-02-28 17:59 ` [PATCH 6/6] c++: Consolidate REQUIRES_EXPR evaluation/diagnostic routines Patrick Palka
2021-03-02  2:18   ` Jason Merrill
2021-03-02 16:45     ` Patrick Palka
2021-03-02 23:04       ` Jason Merrill
2021-03-03 17:10         ` Patrick Palka

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).