diff mbox series

[1/2] c++: Replay errors during diagnosis of constraint satisfaction failures

Message ID 20200309222358.688229-1-ppalka@redhat.com
State New
Headers show
Series [1/2] c++: Replay errors during diagnosis of constraint satisfaction failures | expand

Commit Message

Patrick Palka March 9, 2020, 10:23 p.m. UTC
This patch adds a new flag -fconcepts-diagnostics-depth to the C++ frontend
which controls how deeply we replay errors when diagnosing a constraint
satisfaction failure.  The default is -fconcepts-diagnostics-depth=1 which
diagnoses only the topmost constraint satisfaction failure and is consistent
with our behavior before this patch.  By increasing this flag's value, the user
can control how deeply they want the compiler to explain a constraint
satisfaction error.

For example, if the unsatisfied constraint is a disjunction, then the default
behavior is to just say "no branch in the disjunction is satisfied", but with
-fconcepts-diagnostics-depth=2 we will additionally replay and diagnose the
error in each branch of the disjunction.  And if the unsatisfied constraint is a
requires expression, then we will replay the error in the requires expression,
etc.  This proceeds recursively until there is nothing more to replay or we
reached the exceeded the maximum depth specified by the flag.

Implementation wise, this patch essentially just uncomments the existing
commented-out code that performs the error-replaying, adding logic to keep track
of the current replay depth along the way.  Besides that, there is a new routine
collect_operands_of_disjunction which flattens a disjunction and collects all of
its operands into a vector.

Here are some examples of diagnostics with the two patches in this series.

For the simple test case in which we call ranges::begin() on something that's
not a range:

    #include <ranges>

    struct S { } s;
    auto x = std::ranges::begin(s);

we get the following diagnostics with -fconcepts-diagnostics-depth={1,2,3}
respectively:

     https://pppalka.github.io/ranges-begin-depth-1.html
     https://pppalka.github.io/ranges-begin-depth-2.html
     https://pppalka.github.io/ranges-begin-depth-3.html

And for the new test g++.dg/concepts/diagnostic5.C, we get:

    https://pppalka.github.io/diagnostic5-depth-1.html
    https://pppalka.github.io/diagnostic5-depth-2.html
    https://pppalka.github.io/diagnostic5-depth-3.html
    https://pppalka.github.io/diagnostic5-depth-4.html

The extra diagnostics enabled by this flag are at times longer than they need to
be (e.g.  "the operand is_array_v<...> is unsatisfied because \n the expression
is_array_v<...> [with ...] evaluated to false") and not immediately easy to
follow (especially when there are nested disjunctions), but the transparency
provided by these optional diagnostics seems to be pretty helpful in practice.

Does this seem like a sensible approach?  Thoughts and ideas for improvement
welcome.  Wording and naming suggestions would be much appreciated.

gcc/c-family/ChangeLog:

	* c.opt: Add -fconcepts-diagnostics-depth.

gcc/cp/ChangeLog:

	* constraint.cc (finish_constraint_binary_op): Set the location of EXPR
	as well as its range, because build_x_binary_op doesn't always do so.
	(current_constraint_diagnosis_depth): New.
	(concepts_diagnostics_max_depth_exceeded_p): New.
	(collect_operands_of_disjunction): New.
	(satisfy_disjunction): When diagnosing a satisfaction failure, maybe
	replay each branch of the disjunction, subject to the current diagnosis
	depth.
	(diagnose_valid_expression): When diagnosing a satisfaction failure,
	maybe replay the substitution error, subject to the current diagnosis
	recursion.
	(diagnose_valid_type): Likewise.
	(diagnose_nested_requiremnet): Likewise.
	(diagnosing_failed_constraint::diagnosing_failed_constraint): Increment
	current_constraint_diagnosis_depth when diagnosing.
	(diagnosing_failed_constraint::~diagnosing_failed_constraint): Decrement
	current_constraint_diagnosis_depth when diagnosing.
	(diagnose_constraints): Don't diagnose if concepts_diagnostics_max_depth
	is 0.  Emit a one-off note to increase -fconcepts-diagnostics-depth if
	the limit was exceeded.

gcc/testsuite/ChangeLog:

	* g++.dg/concepts/diagnostic2.C: Expect "no operand" instead of
	"neither operand".
	* g++.dg/concepts/diagnostic5.C: New test.
---
 gcc/c-family/c.opt                          |   4 +
 gcc/cp/constraint.cc                        | 146 +++++++++++++++++---
 gcc/testsuite/g++.dg/concepts/diagnostic2.C |   2 +-
 gcc/testsuite/g++.dg/concepts/diagnostic5.C |  46 ++++++
 4 files changed, 177 insertions(+), 21 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/concepts/diagnostic5.C

Comments

Li, Pan2 via Gcc-patches March 11, 2020, 8:32 p.m. UTC | #1
On 3/9/20 6:23 PM, Patrick Palka wrote:
> This patch adds a new flag -fconcepts-diagnostics-depth to the C++ frontend
> which controls how deeply we replay errors when diagnosing a constraint
> satisfaction failure.  The default is -fconcepts-diagnostics-depth=1 which
> diagnoses only the topmost constraint satisfaction failure and is consistent
> with our behavior before this patch.  By increasing this flag's value, the user
> can control how deeply they want the compiler to explain a constraint
> satisfaction error.
> 
> For example, if the unsatisfied constraint is a disjunction, then the default
> behavior is to just say "no branch in the disjunction is satisfied", but with
> -fconcepts-diagnostics-depth=2 we will additionally replay and diagnose the
> error in each branch of the disjunction.  And if the unsatisfied constraint is a
> requires expression, then we will replay the error in the requires expression,
> etc.  This proceeds recursively until there is nothing more to replay or we
> reached the exceeded the maximum depth specified by the flag.
> 
> Implementation wise, this patch essentially just uncomments the existing
> commented-out code that performs the error-replaying, adding logic to keep track
> of the current replay depth along the way.  Besides that, there is a new routine
> collect_operands_of_disjunction which flattens a disjunction and collects all of
> its operands into a vector.
> 
> Here are some examples of diagnostics with the two patches in this series.
> 
> For the simple test case in which we call ranges::begin() on something that's
> not a range:
> 
>      #include <ranges>
> 
>      struct S { } s;
>      auto x = std::ranges::begin(s);
> 
> we get the following diagnostics with -fconcepts-diagnostics-depth={1,2,3}
> respectively:
> 
>       https://pppalka.github.io/ranges-begin-depth-1.html
>       https://pppalka.github.io/ranges-begin-depth-2.html
>       https://pppalka.github.io/ranges-begin-depth-3.html
> 
> And for the new test g++.dg/concepts/diagnostic5.C, we get:
> 
>      https://pppalka.github.io/diagnostic5-depth-1.html
>      https://pppalka.github.io/diagnostic5-depth-2.html
>      https://pppalka.github.io/diagnostic5-depth-3.html
>      https://pppalka.github.io/diagnostic5-depth-4.html
> 
> The extra diagnostics enabled by this flag are at times longer than they need to
> be (e.g.  "the operand is_array_v<...> is unsatisfied because \n the expression
> is_array_v<...> [with ...] evaluated to false") and not immediately easy to
> follow (especially when there are nested disjunctions), but the transparency
> provided by these optional diagnostics seems to be pretty helpful in practice.
> 
> Does this seem like a sensible approach?  Thoughts and ideas for improvement
> welcome.  Wording and naming suggestions would be much appreciated.

This does seem like a good approach, thanks.

> gcc/c-family/ChangeLog:
> 
> 	* c.opt: Add -fconcepts-diagnostics-depth.
> 
> gcc/cp/ChangeLog:
> 
> 	* constraint.cc (finish_constraint_binary_op): Set the location of EXPR
> 	as well as its range, because build_x_binary_op doesn't always do so.
> 	(current_constraint_diagnosis_depth): New.
> 	(concepts_diagnostics_max_depth_exceeded_p): New.
> 	(collect_operands_of_disjunction): New.
> 	(satisfy_disjunction): When diagnosing a satisfaction failure, maybe
> 	replay each branch of the disjunction, subject to the current diagnosis
> 	depth.
> 	(diagnose_valid_expression): When diagnosing a satisfaction failure,
> 	maybe replay the substitution error, subject to the current diagnosis
> 	recursion.
> 	(diagnose_valid_type): Likewise.
> 	(diagnose_nested_requiremnet): Likewise.
> 	(diagnosing_failed_constraint::diagnosing_failed_constraint): Increment
> 	current_constraint_diagnosis_depth when diagnosing.
> 	(diagnosing_failed_constraint::~diagnosing_failed_constraint): Decrement
> 	current_constraint_diagnosis_depth when diagnosing.
> 	(diagnose_constraints): Don't diagnose if concepts_diagnostics_max_depth
> 	is 0.  Emit a one-off note to increase -fconcepts-diagnostics-depth if
> 	the limit was exceeded.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/concepts/diagnostic2.C: Expect "no operand" instead of
> 	"neither operand".
> 	* g++.dg/concepts/diagnostic5.C: New test.
> ---
>   gcc/c-family/c.opt                          |   4 +
>   gcc/cp/constraint.cc                        | 146 +++++++++++++++++---
>   gcc/testsuite/g++.dg/concepts/diagnostic2.C |   2 +-
>   gcc/testsuite/g++.dg/concepts/diagnostic5.C |  46 ++++++
>   4 files changed, 177 insertions(+), 21 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/concepts/diagnostic5.C
> 
> diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
> index 1cd585fa71d..97ef488931d 100644
> --- a/gcc/c-family/c.opt
> +++ b/gcc/c-family/c.opt
> @@ -1453,6 +1453,10 @@ fconcepts-ts
>   C++ ObjC++ Var(flag_concepts_ts) Init(0)
>   Enable certain features present in the Concepts TS.
>   
> +fconcepts-diagnostics-depth=
> +C++ ObjC++ Joined RejectNegative UInteger Var(concepts_diagnostics_max_depth) Init(1)
> +Specify maximum error replay depth during recursive diagnosis of a constraint satisfaction failure.
> +
>   fcond-mismatch
>   C ObjC C++ ObjC++
>   Allow the arguments of the '?' operator to have different types.
> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> index 4bb4a3f7252..c5e3d64daa6 100644
> --- a/gcc/cp/constraint.cc
> +++ b/gcc/cp/constraint.cc
> @@ -162,6 +162,7 @@ finish_constraint_binary_op (location_t loc,
>     /* When either operand is dependent, the overload set may be non-empty.  */
>     if (expr == error_mark_node)
>       return error_mark_node;
> +  expr.set_location (loc);
>     expr.set_range (lhs.get_start (), rhs.get_finish ());
>     return expr;
>   }
> @@ -2386,6 +2387,50 @@ satisfy_conjunction (tree t, tree args, subst_info info)
>     return satisfy_constraint_r (TREE_OPERAND (t, 1), args, info);
>   }
>   
> +/* The current depth at which we're replaying an error during recursive
> +   diagnosis of a constraint satisfaction failure.  */
> +
> +static int current_constraint_diagnosis_depth;
> +
> +/* Whether CURRENT_CONSTRAINT_DIAGNOSIS_DEPTH has ever exceeded
> +   CONCEPTS_DIAGNOSTICS_MAX_DEPTH during recursive diagnosis of a constraint
> +   satisfaction error.  */
> +
> +static bool concepts_diagnostics_max_depth_exceeded_p;
> +
> +/* Recursive subroutine of collect_operands_of_disjunction.  T is a normalized
> +   subexpression of a constraint (composed of CONJ_CONSTRs and DISJ_CONSTRs)
> +   and E is the corresponding unnormalized subexpression (composed of
> +   TRUTH_ANDIF_EXPRs and TRUTH_ORIF_EXPRs).  */
> +
> +static void
> +collect_operands_of_disjunction_r (tree t, tree e,
> +				   auto_vec<std::pair<tree, tree> > *operands)
> +{
> +  if (TREE_CODE (e) == TRUTH_ORIF_EXPR)
> +    {
> +      collect_operands_of_disjunction_r (TREE_OPERAND (t, 0),
> +					 TREE_OPERAND (e, 0), operands);
> +      collect_operands_of_disjunction_r (TREE_OPERAND (t, 1),
> +					 TREE_OPERAND (e, 1), operands);
> +    }
> +  else
> +    {
> +      std::pair<tree, tree> p = {t, e};
> +      operands->safe_push (p);
> +    }
> +}
> +
> +/* Recursively collect the normalized and unnormalized operands of the
> +   disjunction T and append them to OPERANDS in order.  */
> +
> +static void
> +collect_operands_of_disjunction (tree t,
> +				 auto_vec<std::pair<tree, tree> > *operands)
> +{
> +  collect_operands_of_disjunction_r (t, CONSTR_EXPR (t), operands);
> +}
> +
>   /* Compute the satisfaction of a disjunction.  */
>   
>   static tree
> @@ -2403,11 +2448,27 @@ satisfy_disjunction (tree t, tree args, subst_info info)
>     tree rhs = satisfy_constraint_r (TREE_OPERAND (t, 1), args, quiet);
>     if (rhs != boolean_true_node && info.noisy ())
>       {
> -      location_t loc = cp_expr_location (CONSTR_EXPR (t));
> -      inform (loc, "neither operand of the disjunction is satisfied");
> -      /* TODO: Replay the LHS and RHS to find failures in both branches.  */
> -      // satisfy_constraint_r (TREE_OPERAND (t, 0), args, info);
> -      // satisfy_constraint_r (TREE_OPERAND (t, 1), args, info);
> +      cp_expr disj_expr = CONSTR_EXPR (t);
> +      inform (disj_expr.get_location (),
> +	      "no operand of the disjunction is satisfied");
> +      if (current_constraint_diagnosis_depth < concepts_diagnostics_max_depth)

Could we localize all the tracking of current_constraint_diagnosis_depth 
vs. max_depth and max_depth_exceeded_p in diagnosing_failed_constraint, 
where you're already doing the increment/decrement?

> +	{
> +	  /* Replay the error in each branch of the disjunction.  */
> +	  auto_vec<std::pair<tree, tree> > operands;
> +	  collect_operands_of_disjunction (t, &operands);
> +	  for (unsigned i = 0; i < operands.length (); i++)
> +	    {
> +	      tree norm_op = operands[i].first;
> +	      tree op = operands[i].second;
> +	      location_t loc = make_location (cp_expr_location (op),
> +					      disj_expr.get_start (),
> +					      disj_expr.get_finish ());
> +	      inform (loc, "the operand %qE is unsatisfied because", op);
> +	      satisfy_constraint_r (norm_op, args, info);
> +	    }
> +	}
> +      else
> +	concepts_diagnostics_max_depth_exceeded_p = true;
>       }
>     return rhs;
>   }
> @@ -3147,10 +3208,17 @@ diagnose_valid_expression (tree expr, tree args, tree in_decl)
>       return result;
>   
>     location_t loc = cp_expr_loc_or_input_loc (expr);
> -  inform (loc, "the required expression %qE is invalid", expr);
> -
> -  /* TODO: Replay the substitution to diagnose the error?  */
> -  // tsubst_expr (expr, args, tf_error, in_decl, false);
> +  if (current_constraint_diagnosis_depth < concepts_diagnostics_max_depth)
> +    {
> +      /* Replay the substitution error.  */
> +      inform (loc, "the required expression %qE is invalid, because", expr);
> +      tsubst_expr (expr, args, tf_error, in_decl, false);
> +    }
> +  else
> +    {
> +      inform (loc, "the required expression %qE is invalid", expr);
> +      concepts_diagnostics_max_depth_exceeded_p = true;
> +    }
>   
>     return error_mark_node;
>   }
> @@ -3163,10 +3231,17 @@ diagnose_valid_type (tree type, tree args, tree in_decl)
>       return result;
>   
>     location_t loc = cp_expr_loc_or_input_loc (type);
> -  inform (loc, "the required type %qT is invalid", type);
> -
> -  /* TODO: Replay the substitution to diagnose the error?  */
> -  // tsubst (type, args, tf_error, in_decl);
> +  if (current_constraint_diagnosis_depth < concepts_diagnostics_max_depth)
> +    {
> +      /* 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);
> +      concepts_diagnostics_max_depth_exceeded_p = true;
> +    }
>   
>     return error_mark_node;
>   }
> @@ -3245,11 +3320,19 @@ diagnose_nested_requirement (tree req, tree args)
>   
>     tree expr = TREE_OPERAND (req, 0);
>     location_t loc = cp_expr_location (expr);
> -  inform (loc, "nested requirement %qE is not satisfied", expr);
> +  if (current_constraint_diagnosis_depth < concepts_diagnostics_max_depth)
> +    {
> +      /* Replay the substitution error.  */
> +      inform (loc, "nested requirement %qE is not satisfied, because", expr);
> +      subst_info noisy (tf_warning_or_error, NULL_TREE);
> +      satisfy_constraint_expression (expr, args, noisy);
> +    }
> +  else
> +    {
> +      inform (loc, "nested requirement %qE is not satisfied", expr);
> +      concepts_diagnostics_max_depth_exceeded_p = true;
> +    }
>   
> -  /* TODO: Replay the substitution to diagnose the error?  */
> -  // subst_info noisy (tf_warning_or_error, NULL_TREE);
> -  // satisfy_constraint (norm, args, info);
>   }
>   
>   static void
> @@ -3346,14 +3429,23 @@ diagnosing_failed_constraint (tree t, tree args, bool diag)
>     : diagnosing_error (diag)
>   {
>     if (diagnosing_error)
> -    current_failed_constraint = tree_cons (args, t, current_failed_constraint);
> +    {
> +      current_failed_constraint
> +	= tree_cons (args, t, current_failed_constraint);
> +      ++current_constraint_diagnosis_depth;
> +    }
>   }
>   
>   diagnosing_failed_constraint::
>   ~diagnosing_failed_constraint ()
>   {
> -  if (diagnosing_error && current_failed_constraint)
> -    current_failed_constraint = TREE_CHAIN (current_failed_constraint);
> +  if (diagnosing_error)
> +    {
> +      --current_constraint_diagnosis_depth;
> +      if (current_failed_constraint)
> +	current_failed_constraint = TREE_CHAIN (current_failed_constraint);
> +    }
> +
>   }
>   
>   /* Emit diagnostics detailing the failure ARGS to satisfy the constraints
> @@ -3364,11 +3456,25 @@ diagnose_constraints (location_t loc, tree t, tree args)
>   {
>     inform (loc, "constraints not satisfied");
>   
> +  if (concepts_diagnostics_max_depth == 0)
> +    return;
> +
>     /* Replay satisfaction, but diagnose errors.  */
>     if (!args)
>       constraint_satisfaction_value (t, tf_warning_or_error);
>     else
>       constraint_satisfaction_value (t, args, tf_warning_or_error);
> +
> +  static bool suggested_p;
> +  if (concepts_diagnostics_max_depth_exceeded_p
> +      && current_constraint_diagnosis_depth == 0
> +      && !suggested_p)
> +    {
> +      inform (UNKNOWN_LOCATION,
> +	      "set -fconcepts-diagnostics-depth= to at least %d for more detail",
> +	      concepts_diagnostics_max_depth + 1);
> +      suggested_p = true;
> +    }
>   }
>   
>   #include "gt-cp-constraint.h"
> diff --git a/gcc/testsuite/g++.dg/concepts/diagnostic2.C b/gcc/testsuite/g++.dg/concepts/diagnostic2.C
> index ce51b71fa8b..47accb8366e 100644
> --- a/gcc/testsuite/g++.dg/concepts/diagnostic2.C
> +++ b/gcc/testsuite/g++.dg/concepts/diagnostic2.C
> @@ -5,7 +5,7 @@ template<typename T>
>     inline constexpr bool foo_v = false;
>   
>   template<typename T>
> -  concept foo = foo_v<T> || foo_v<T&>; // { dg-message "neither operand" }
> +  concept foo = foo_v<T> || foo_v<T&>; // { dg-message "no operand" }
>   /* { dg-begin-multiline-output "" }
>      concept foo = foo_v<T> || foo_v<T&>;
>                    ~~~~~~~~~^~~~~~~~~~~~
> diff --git a/gcc/testsuite/g++.dg/concepts/diagnostic5.C b/gcc/testsuite/g++.dg/concepts/diagnostic5.C
> new file mode 100644
> index 00000000000..3c3b42f566c
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/concepts/diagnostic5.C
> @@ -0,0 +1,46 @@
> +// { dg-do compile { target c++2a } }
> +// { dg-additional-options "-fconcepts-diagnostics-depth=2" }
> +
> +template<typename T>
> +  concept c1 = requires { typename T::blah; };
> +// { dg-message "satisfaction of .c1<char>." "" { target *-*-* } .-1 }
> +// { dg-message "satisfaction of .c1<char\\*>." "" { target *-*-* } .-2 }
> +// { dg-message ".typename T::blah. is invalid" "" { target *-*-* } .-3 }
> +
> +template<typename T>
> +  concept c2 = requires (T x) { *x; };
> +// { dg-message "satisfaction of .c2<char>." "" { target *-*-* } .-1 }
> +// { dg-message "in requirements with .char x." "" { target *-*-* } .-2 }
> +// { dg-message "required expression .* is invalid" "" { target *-*-* } .-3 }
> +
> +template<typename T>
> +  concept c3 = __is_same(T, const T) || __is_same(T, int);
> +// { dg-message "satisfaction of .c3<char>." "" { target *-*-* } .-1 }
> +// { dg-message "no operand of the disjunction is satisfied" "" { target *-*-* } .-2 }
> +
> +template<typename T>
> +  concept c4 = requires (T x) { requires c2<const T> || c2<volatile T>; };
> +// { dg-message "satisfaction of .c4<char>." "" { target *-*-* } .-1 }
> +// { dg-message "nested requirement" "" { target *-*-* } .-2 }
> +
> +template<typename T>
> +  concept c5 = requires (T x) { { &x } -> c1; };
> +// { dg-message "satisfaction of .c5<char>." "" { target *-*-* } .-1 }
> +// { dg-message "in requirements with .char x." "" { target *-*-* } .-2 }
> +// { dg-message "does not satisfy return-type-requirement" "" { target *-*-* } .-3 }
> +// { dg-error "deduced expression type does not satisfy" "" { target *-*-* } .-4 }
> +
> +template<typename T>
> +  requires (c1<T> || c2<T>) || (c3<T> || c4<T>) || c5<T> // { dg-message "49: no operand" }
> +  // { dg-message ".c1<T>. is unsatisfied because" "" { target *-*-* } .-1 }
> +  // { dg-message ".c2<T>. is unsatisfied because" "" { target *-*-* } .-2 }
> +  // { dg-message ".c3<T>. is unsatisfied because" "" { target *-*-* } .-3 }
> +  // { dg-message ".c4<T>. is unsatisfied because" "" { target *-*-* } .-4 }
> +  // { dg-message ".c5<T>. is unsatisfied because" "" { target *-*-* } .-5 }
> +  void foo() { }
> +
> +void
> +bar()
> +{
> +  foo<char>(); // { dg-error "use of" }
> +}
>
Li, Pan2 via Gcc-patches March 11, 2020, 10:18 p.m. UTC | #2
On Wed, 11 Mar 2020, Jason Merrill wrote:

> On 3/9/20 6:23 PM, Patrick Palka wrote:
> > This patch adds a new flag -fconcepts-diagnostics-depth to the C++ frontend
> > which controls how deeply we replay errors when diagnosing a constraint
> > satisfaction failure.  The default is -fconcepts-diagnostics-depth=1 which
> > diagnoses only the topmost constraint satisfaction failure and is consistent
> > with our behavior before this patch.  By increasing this flag's value, the
> > user
> > can control how deeply they want the compiler to explain a constraint
> > satisfaction error.
> > 
> > For example, if the unsatisfied constraint is a disjunction, then the
> > default
> > behavior is to just say "no branch in the disjunction is satisfied", but
> > with
> > -fconcepts-diagnostics-depth=2 we will additionally replay and diagnose the
> > error in each branch of the disjunction.  And if the unsatisfied constraint
> > is a
> > requires expression, then we will replay the error in the requires
> > expression,
> > etc.  This proceeds recursively until there is nothing more to replay or we
> > reached the exceeded the maximum depth specified by the flag.
> > 
> > Implementation wise, this patch essentially just uncomments the existing
> > commented-out code that performs the error-replaying, adding logic to keep
> > track
> > of the current replay depth along the way.  Besides that, there is a new
> > routine
> > collect_operands_of_disjunction which flattens a disjunction and collects
> > all of
> > its operands into a vector.
> > 
> > Here are some examples of diagnostics with the two patches in this series.
> > 
> > For the simple test case in which we call ranges::begin() on something
> > that's
> > not a range:
> > 
> >      #include <ranges>
> > 
> >      struct S { } s;
> >      auto x = std::ranges::begin(s);
> > 
> > we get the following diagnostics with -fconcepts-diagnostics-depth={1,2,3}
> > respectively:
> > 
> >       https://pppalka.github.io/ranges-begin-depth-1.html
> >       https://pppalka.github.io/ranges-begin-depth-2.html
> >       https://pppalka.github.io/ranges-begin-depth-3.html
> > 
> > And for the new test g++.dg/concepts/diagnostic5.C, we get:
> > 
> >      https://pppalka.github.io/diagnostic5-depth-1.html
> >      https://pppalka.github.io/diagnostic5-depth-2.html
> >      https://pppalka.github.io/diagnostic5-depth-3.html
> >      https://pppalka.github.io/diagnostic5-depth-4.html
> > 
> > The extra diagnostics enabled by this flag are at times longer than they
> > need to
> > be (e.g.  "the operand is_array_v<...> is unsatisfied because \n the
> > expression
> > is_array_v<...> [with ...] evaluated to false") and not immediately easy to
> > follow (especially when there are nested disjunctions), but the transparency
> > provided by these optional diagnostics seems to be pretty helpful in
> > practice.
> > 
> > Does this seem like a sensible approach?  Thoughts and ideas for improvement
> > welcome.  Wording and naming suggestions would be much appreciated.
> 
> This does seem like a good approach, thanks.
> 
> > gcc/c-family/ChangeLog:
> > 
> > 	* c.opt: Add -fconcepts-diagnostics-depth.
> > 
> > gcc/cp/ChangeLog:
> > 
> > 	* constraint.cc (finish_constraint_binary_op): Set the location of
> > EXPR
> > 	as well as its range, because build_x_binary_op doesn't always do so.
> > 	(current_constraint_diagnosis_depth): New.
> > 	(concepts_diagnostics_max_depth_exceeded_p): New.
> > 	(collect_operands_of_disjunction): New.
> > 	(satisfy_disjunction): When diagnosing a satisfaction failure, maybe
> > 	replay each branch of the disjunction, subject to the current
> > diagnosis
> > 	depth.
> > 	(diagnose_valid_expression): When diagnosing a satisfaction failure,
> > 	maybe replay the substitution error, subject to the current diagnosis
> > 	recursion.
> > 	(diagnose_valid_type): Likewise.
> > 	(diagnose_nested_requiremnet): Likewise.
> > 	(diagnosing_failed_constraint::diagnosing_failed_constraint):
> > Increment
> > 	current_constraint_diagnosis_depth when diagnosing.
> > 	(diagnosing_failed_constraint::~diagnosing_failed_constraint):
> > Decrement
> > 	current_constraint_diagnosis_depth when diagnosing.
> > 	(diagnose_constraints): Don't diagnose if
> > concepts_diagnostics_max_depth
> > 	is 0.  Emit a one-off note to increase -fconcepts-diagnostics-depth if
> > 	the limit was exceeded.
> > 
> > gcc/testsuite/ChangeLog:
> > 
> > 	* g++.dg/concepts/diagnostic2.C: Expect "no operand" instead of
> > 	"neither operand".
> > 	* g++.dg/concepts/diagnostic5.C: New test.
> > ---
> >   gcc/c-family/c.opt                          |   4 +
> >   gcc/cp/constraint.cc                        | 146 +++++++++++++++++---
> >   gcc/testsuite/g++.dg/concepts/diagnostic2.C |   2 +-
> >   gcc/testsuite/g++.dg/concepts/diagnostic5.C |  46 ++++++
> >   4 files changed, 177 insertions(+), 21 deletions(-)
> >   create mode 100644 gcc/testsuite/g++.dg/concepts/diagnostic5.C
> > 
> > diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
> > index 1cd585fa71d..97ef488931d 100644
> > --- a/gcc/c-family/c.opt
> > +++ b/gcc/c-family/c.opt
> > @@ -1453,6 +1453,10 @@ fconcepts-ts
> >   C++ ObjC++ Var(flag_concepts_ts) Init(0)
> >   Enable certain features present in the Concepts TS.
> >   +fconcepts-diagnostics-depth=
> > +C++ ObjC++ Joined RejectNegative UInteger
> > Var(concepts_diagnostics_max_depth) Init(1)
> > +Specify maximum error replay depth during recursive diagnosis of a
> > constraint satisfaction failure.
> > +
> >   fcond-mismatch
> >   C ObjC C++ ObjC++
> >   Allow the arguments of the '?' operator to have different types.
> > diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> > index 4bb4a3f7252..c5e3d64daa6 100644
> > --- a/gcc/cp/constraint.cc
> > +++ b/gcc/cp/constraint.cc
> > @@ -162,6 +162,7 @@ finish_constraint_binary_op (location_t loc,
> >     /* When either operand is dependent, the overload set may be non-empty.
> > */
> >     if (expr == error_mark_node)
> >       return error_mark_node;
> > +  expr.set_location (loc);
> >     expr.set_range (lhs.get_start (), rhs.get_finish ());
> >     return expr;
> >   }
> > @@ -2386,6 +2387,50 @@ satisfy_conjunction (tree t, tree args, subst_info
> > info)
> >     return satisfy_constraint_r (TREE_OPERAND (t, 1), args, info);
> >   }
> >   +/* The current depth at which we're replaying an error during recursive
> > +   diagnosis of a constraint satisfaction failure.  */
> > +
> > +static int current_constraint_diagnosis_depth;
> > +
> > +/* Whether CURRENT_CONSTRAINT_DIAGNOSIS_DEPTH has ever exceeded
> > +   CONCEPTS_DIAGNOSTICS_MAX_DEPTH during recursive diagnosis of a
> > constraint
> > +   satisfaction error.  */
> > +
> > +static bool concepts_diagnostics_max_depth_exceeded_p;
> > +
> > +/* Recursive subroutine of collect_operands_of_disjunction.  T is a
> > normalized
> > +   subexpression of a constraint (composed of CONJ_CONSTRs and
> > DISJ_CONSTRs)
> > +   and E is the corresponding unnormalized subexpression (composed of
> > +   TRUTH_ANDIF_EXPRs and TRUTH_ORIF_EXPRs).  */
> > +
> > +static void
> > +collect_operands_of_disjunction_r (tree t, tree e,
> > +				   auto_vec<std::pair<tree, tree> > *operands)
> > +{
> > +  if (TREE_CODE (e) == TRUTH_ORIF_EXPR)
> > +    {
> > +      collect_operands_of_disjunction_r (TREE_OPERAND (t, 0),
> > +					 TREE_OPERAND (e, 0), operands);
> > +      collect_operands_of_disjunction_r (TREE_OPERAND (t, 1),
> > +					 TREE_OPERAND (e, 1), operands);
> > +    }
> > +  else
> > +    {
> > +      std::pair<tree, tree> p = {t, e};
> > +      operands->safe_push (p);
> > +    }
> > +}
> > +
> > +/* Recursively collect the normalized and unnormalized operands of the
> > +   disjunction T and append them to OPERANDS in order.  */
> > +
> > +static void
> > +collect_operands_of_disjunction (tree t,
> > +				 auto_vec<std::pair<tree, tree> > *operands)
> > +{
> > +  collect_operands_of_disjunction_r (t, CONSTR_EXPR (t), operands);
> > +}
> > +
> >   /* Compute the satisfaction of a disjunction.  */
> >     static tree
> > @@ -2403,11 +2448,27 @@ satisfy_disjunction (tree t, tree args, subst_info
> > info)
> >     tree rhs = satisfy_constraint_r (TREE_OPERAND (t, 1), args, quiet);
> >     if (rhs != boolean_true_node && info.noisy ())
> >       {
> > -      location_t loc = cp_expr_location (CONSTR_EXPR (t));
> > -      inform (loc, "neither operand of the disjunction is satisfied");
> > -      /* TODO: Replay the LHS and RHS to find failures in both branches.
> > */
> > -      // satisfy_constraint_r (TREE_OPERAND (t, 0), args, info);
> > -      // satisfy_constraint_r (TREE_OPERAND (t, 1), args, info);
> > +      cp_expr disj_expr = CONSTR_EXPR (t);
> > +      inform (disj_expr.get_location (),
> > +	      "no operand of the disjunction is satisfied");
> > +      if (current_constraint_diagnosis_depth <
> > concepts_diagnostics_max_depth)
> 
> Could we localize all the tracking of current_constraint_diagnosis_depth vs.
> max_depth and max_depth_exceeded_p in diagnosing_failed_constraint, where
> you're already doing the increment/decrement?

Hmm, like this?  This version introduces a new static member function
diagnosing_failed_constraint::replay_errors_p that checks
current_constraint_diagnosis_depth, and also sets max_depth_exceeded_p
when appropriate.

-- >8 --

gcc/c-family/ChangeLog:

	* c.opt: Add -fconcepts-diagnostics-depth.

gcc/cp/ChangeLog:

	* constraint.cc (finish_constraint_binary_op): Set the location of EXPR
	as well as its range, because build_x_binary_op doesn't always do so.
	(current_constraint_diagnosis_depth): New.
	(concepts_diagnostics_max_depth_exceeded_p): New.
	(collect_operands_of_disjunction): New.
	(satisfy_disjunction): When diagnosing a satisfaction failure, maybe
	replay each branch of the disjunction, subject to the current diagnosis
	depth.
	(diagnose_valid_expression): When diagnosing a satisfaction failure,
	maybe replay the substitution error, subject to the current diagnosis
	recursion.
	(diagnose_valid_type): Likewise.
	(diagnose_nested_requiremnet): Likewise.
	(diagnosing_failed_constraint::diagnosing_failed_constraint): Increment
	current_constraint_diagnosis_depth when diagnosing.
	(diagnosing_failed_constraint::~diagnosing_failed_constraint): Decrement
	current_constraint_diagnosis_depth when diagnosing.
	(diagnosing_failed_constraint::replay_errors_p): New static member
	function.
	(diagnose_constraints): Don't diagnose if concepts_diagnostics_max_depth
	is 0.  Emit a one-off note to increase -fconcepts-diagnostics-depth if
	the limit was exceeded.
	* cp-tree.h (diagnosing_failed_constraint::replay_errors_p): Declare.

gcc/testsuite/ChangeLog:

	* g++.dg/concepts/diagnostic2.C: Expect "no operand" instead of
	"neither operand".
	* g++.dg/concepts/diagnostic5.C: New test.
---
 gcc/c-family/c.opt                          |   4 +
 gcc/cp/constraint.cc                        | 150 +++++++++++++++++---
 gcc/cp/cp-tree.h                            |   1 +
 gcc/testsuite/g++.dg/concepts/diagnostic2.C |   2 +-
 gcc/testsuite/g++.dg/concepts/diagnostic5.C |  46 ++++++
 5 files changed, 182 insertions(+), 21 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/concepts/diagnostic5.C

diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 1cd585fa71d..97ef488931d 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -1453,6 +1453,10 @@ fconcepts-ts
 C++ ObjC++ Var(flag_concepts_ts) Init(0)
 Enable certain features present in the Concepts TS.
 
+fconcepts-diagnostics-depth=
+C++ ObjC++ Joined RejectNegative UInteger Var(concepts_diagnostics_max_depth) Init(1)
+Specify maximum error replay depth during recursive diagnosis of a constraint satisfaction failure.
+
 fcond-mismatch
 C ObjC C++ ObjC++
 Allow the arguments of the '?' operator to have different types.
diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index 697ed6726b8..6aa432aaaf7 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -162,6 +162,7 @@ finish_constraint_binary_op (location_t loc,
   /* When either operand is dependent, the overload set may be non-empty.  */
   if (expr == error_mark_node)
     return error_mark_node;
+  expr.set_location (loc);
   expr.set_range (lhs.get_start (), rhs.get_finish ());
   return expr;
 }
@@ -2390,6 +2391,50 @@ satisfy_conjunction (tree t, tree args, subst_info info)
   return satisfy_constraint_r (TREE_OPERAND (t, 1), args, info);
 }
 
+/* The current depth at which we're replaying an error during recursive
+   diagnosis of a constraint satisfaction failure.  */
+
+static int current_constraint_diagnosis_depth;
+
+/* Whether CURRENT_CONSTRAINT_DIAGNOSIS_DEPTH has ever exceeded
+   CONCEPTS_DIAGNOSTICS_MAX_DEPTH during recursive diagnosis of a constraint
+   satisfaction error.  */
+
+static bool concepts_diagnostics_max_depth_exceeded_p;
+
+/* Recursive subroutine of collect_operands_of_disjunction.  T is a normalized
+   subexpression of a constraint (composed of CONJ_CONSTRs and DISJ_CONSTRs)
+   and E is the corresponding unnormalized subexpression (composed of
+   TRUTH_ANDIF_EXPRs and TRUTH_ORIF_EXPRs).  */
+
+static void
+collect_operands_of_disjunction_r (tree t, tree e,
+				   auto_vec<std::pair<tree, tree> > *operands)
+{
+  if (TREE_CODE (e) == TRUTH_ORIF_EXPR)
+    {
+      collect_operands_of_disjunction_r (TREE_OPERAND (t, 0),
+					 TREE_OPERAND (e, 0), operands);
+      collect_operands_of_disjunction_r (TREE_OPERAND (t, 1),
+					 TREE_OPERAND (e, 1), operands);
+    }
+  else
+    {
+      std::pair<tree, tree> p = {t, e};
+      operands->safe_push (p);
+    }
+}
+
+/* Recursively collect the normalized and unnormalized operands of the
+   disjunction T and append them to OPERANDS in order.  */
+
+static void
+collect_operands_of_disjunction (tree t,
+				 auto_vec<std::pair<tree, tree> > *operands)
+{
+  collect_operands_of_disjunction_r (t, CONSTR_EXPR (t), operands);
+}
+
 /* Compute the satisfaction of a disjunction.  */
 
 static tree
@@ -2407,11 +2452,25 @@ satisfy_disjunction (tree t, tree args, subst_info info)
   tree rhs = satisfy_constraint_r (TREE_OPERAND (t, 1), args, quiet);
   if (rhs != boolean_true_node && info.noisy ())
     {
-      location_t loc = cp_expr_location (CONSTR_EXPR (t));
-      inform (loc, "neither operand of the disjunction is satisfied");
-      /* TODO: Replay the LHS and RHS to find failures in both branches.  */
-      // satisfy_constraint_r (TREE_OPERAND (t, 0), args, info);
-      // satisfy_constraint_r (TREE_OPERAND (t, 1), args, info);
+      cp_expr disj_expr = CONSTR_EXPR (t);
+      inform (disj_expr.get_location (),
+	      "no operand of the disjunction is satisfied");
+      if (diagnosing_failed_constraint::replay_errors_p ())
+	{
+	  /* Replay the error in each branch of the disjunction.  */
+	  auto_vec<std::pair<tree, tree> > operands;
+	  collect_operands_of_disjunction (t, &operands);
+	  for (unsigned i = 0; i < operands.length (); i++)
+	    {
+	      tree norm_op = operands[i].first;
+	      tree op = operands[i].second;
+	      location_t loc = make_location (cp_expr_location (op),
+					      disj_expr.get_start (),
+					      disj_expr.get_finish ());
+	      inform (loc, "the operand %qE is unsatisfied because", op);
+	      satisfy_constraint_r (norm_op, args, info);
+	    }
+	}
     }
   return rhs;
 }
@@ -3151,10 +3210,14 @@ diagnose_valid_expression (tree expr, tree args, tree in_decl)
     return result;
 
   location_t loc = cp_expr_loc_or_input_loc (expr);
-  inform (loc, "the required expression %qE is invalid", expr);
-
-  /* TODO: Replay the substitution to diagnose the error?  */
-  // tsubst_expr (expr, args, tf_error, in_decl, false);
+  if (diagnosing_failed_constraint::replay_errors_p ())
+    {
+      /* Replay the substitution error.  */
+      inform (loc, "the required expression %qE is invalid, because", expr);
+      tsubst_expr (expr, args, tf_error, in_decl, false);
+    }
+  else
+    inform (loc, "the required expression %qE is invalid", expr);
 
   return error_mark_node;
 }
@@ -3167,10 +3230,14 @@ diagnose_valid_type (tree type, tree args, tree in_decl)
     return result;
 
   location_t loc = cp_expr_loc_or_input_loc (type);
-  inform (loc, "the required type %qT is invalid", type);
-
-  /* TODO: Replay the substitution to diagnose the error?  */
-  // tsubst (type, args, tf_error, in_decl);
+  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;
 }
@@ -3249,11 +3316,16 @@ diagnose_nested_requirement (tree req, tree args)
 
   tree expr = TREE_OPERAND (req, 0);
   location_t loc = cp_expr_location (expr);
-  inform (loc, "nested requirement %qE is not satisfied", expr);
+  if (diagnosing_failed_constraint::replay_errors_p ())
+    {
+      /* Replay the substitution error.  */
+      inform (loc, "nested requirement %qE is not satisfied, because", expr);
+      subst_info noisy (tf_warning_or_error, NULL_TREE);
+      satisfy_constraint_expression (expr, args, noisy);
+    }
+  else
+    inform (loc, "nested requirement %qE is not satisfied", expr);
 
-  /* TODO: Replay the substitution to diagnose the error?  */
-  // subst_info noisy (tf_warning_or_error, NULL_TREE);
-  // satisfy_constraint (norm, args, info);
 }
 
 static void
@@ -3350,14 +3422,38 @@ diagnosing_failed_constraint (tree t, tree args, bool diag)
   : diagnosing_error (diag)
 {
   if (diagnosing_error)
-    current_failed_constraint = tree_cons (args, t, current_failed_constraint);
+    {
+      current_failed_constraint
+	= tree_cons (args, t, current_failed_constraint);
+      ++current_constraint_diagnosis_depth;
+    }
 }
 
 diagnosing_failed_constraint::
 ~diagnosing_failed_constraint ()
 {
-  if (diagnosing_error && current_failed_constraint)
-    current_failed_constraint = TREE_CHAIN (current_failed_constraint);
+  if (diagnosing_error)
+    {
+      --current_constraint_diagnosis_depth;
+      if (current_failed_constraint)
+	current_failed_constraint = TREE_CHAIN (current_failed_constraint);
+    }
+
+}
+
+/* Whether we can replay an error that underlies a constraint failure at the
+   current diagnosis depth.  */
+
+bool
+diagnosing_failed_constraint::replay_errors_p ()
+{
+  if (current_constraint_diagnosis_depth >= concepts_diagnostics_max_depth)
+    {
+      concepts_diagnostics_max_depth_exceeded_p = true;
+      return false;
+    }
+  else
+    return true;
 }
 
 /* Emit diagnostics detailing the failure ARGS to satisfy the constraints
@@ -3368,11 +3464,25 @@ diagnose_constraints (location_t loc, tree t, tree args)
 {
   inform (loc, "constraints not satisfied");
 
+  if (concepts_diagnostics_max_depth == 0)
+    return;
+
   /* Replay satisfaction, but diagnose errors.  */
   if (!args)
     constraint_satisfaction_value (t, tf_warning_or_error);
   else
     constraint_satisfaction_value (t, args, tf_warning_or_error);
+
+  static bool suggested_p;
+  if (concepts_diagnostics_max_depth_exceeded_p
+      && current_constraint_diagnosis_depth == 0
+      && !suggested_p)
+    {
+      inform (UNKNOWN_LOCATION,
+	      "set -fconcepts-diagnostics-depth= to at least %d for more detail",
+	      concepts_diagnostics_max_depth + 1);
+      suggested_p = true;
+    }
 }
 
 #include "gt-cp-constraint.h"
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 757cdd8168a..8235e5947a2 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7829,6 +7829,7 @@ struct diagnosing_failed_constraint
 {
   diagnosing_failed_constraint (tree, tree, bool);
   ~diagnosing_failed_constraint ();
+  static bool replay_errors_p ();
 
   bool diagnosing_error;
 };
diff --git a/gcc/testsuite/g++.dg/concepts/diagnostic2.C b/gcc/testsuite/g++.dg/concepts/diagnostic2.C
index ce51b71fa8b..47accb8366e 100644
--- a/gcc/testsuite/g++.dg/concepts/diagnostic2.C
+++ b/gcc/testsuite/g++.dg/concepts/diagnostic2.C
@@ -5,7 +5,7 @@ template<typename T>
   inline constexpr bool foo_v = false;
 
 template<typename T>
-  concept foo = foo_v<T> || foo_v<T&>; // { dg-message "neither operand" }
+  concept foo = foo_v<T> || foo_v<T&>; // { dg-message "no operand" }
 /* { dg-begin-multiline-output "" }
    concept foo = foo_v<T> || foo_v<T&>;
                  ~~~~~~~~~^~~~~~~~~~~~
diff --git a/gcc/testsuite/g++.dg/concepts/diagnostic5.C b/gcc/testsuite/g++.dg/concepts/diagnostic5.C
new file mode 100644
index 00000000000..3c3b42f566c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/concepts/diagnostic5.C
@@ -0,0 +1,46 @@
+// { dg-do compile { target c++2a } }
+// { dg-additional-options "-fconcepts-diagnostics-depth=2" }
+
+template<typename T>
+  concept c1 = requires { typename T::blah; };
+// { dg-message "satisfaction of .c1<char>." "" { target *-*-* } .-1 }
+// { dg-message "satisfaction of .c1<char\\*>." "" { target *-*-* } .-2 }
+// { dg-message ".typename T::blah. is invalid" "" { target *-*-* } .-3 }
+
+template<typename T>
+  concept c2 = requires (T x) { *x; };
+// { dg-message "satisfaction of .c2<char>." "" { target *-*-* } .-1 }
+// { dg-message "in requirements with .char x." "" { target *-*-* } .-2 }
+// { dg-message "required expression .* is invalid" "" { target *-*-* } .-3 }
+
+template<typename T>
+  concept c3 = __is_same(T, const T) || __is_same(T, int);
+// { dg-message "satisfaction of .c3<char>." "" { target *-*-* } .-1 }
+// { dg-message "no operand of the disjunction is satisfied" "" { target *-*-* } .-2 }
+
+template<typename T>
+  concept c4 = requires (T x) { requires c2<const T> || c2<volatile T>; };
+// { dg-message "satisfaction of .c4<char>." "" { target *-*-* } .-1 }
+// { dg-message "nested requirement" "" { target *-*-* } .-2 }
+
+template<typename T>
+  concept c5 = requires (T x) { { &x } -> c1; };
+// { dg-message "satisfaction of .c5<char>." "" { target *-*-* } .-1 }
+// { dg-message "in requirements with .char x." "" { target *-*-* } .-2 }
+// { dg-message "does not satisfy return-type-requirement" "" { target *-*-* } .-3 }
+// { dg-error "deduced expression type does not satisfy" "" { target *-*-* } .-4 }
+
+template<typename T>
+  requires (c1<T> || c2<T>) || (c3<T> || c4<T>) || c5<T> // { dg-message "49: no operand" }
+  // { dg-message ".c1<T>. is unsatisfied because" "" { target *-*-* } .-1 }
+  // { dg-message ".c2<T>. is unsatisfied because" "" { target *-*-* } .-2 }
+  // { dg-message ".c3<T>. is unsatisfied because" "" { target *-*-* } .-3 }
+  // { dg-message ".c4<T>. is unsatisfied because" "" { target *-*-* } .-4 }
+  // { dg-message ".c5<T>. is unsatisfied because" "" { target *-*-* } .-5 }
+  void foo() { }
+
+void
+bar()
+{
+  foo<char>(); // { dg-error "use of" }
+}
Li, Pan2 via Gcc-patches March 15, 2020, 8:02 p.m. UTC | #3
On 3/11/20 4:18 PM, Patrick Palka via Gcc-patches wrote:
...
> Hmm, like this?  This version introduces a new static member function
> diagnosing_failed_constraint::replay_errors_p that checks
> current_constraint_diagnosis_depth, and also sets max_depth_exceeded_p
> when appropriate.
> 
...

Just one small quoting nit:


> @@ -3368,11 +3464,25 @@ diagnose_constraints (location_t loc, tree t, tree args)
>   {
>     inform (loc, "constraints not satisfied");
>   
> +  if (concepts_diagnostics_max_depth == 0)
> +    return;
> +
>     /* Replay satisfaction, but diagnose errors.  */
>     if (!args)
>       constraint_satisfaction_value (t, tf_warning_or_error);
>     else
>       constraint_satisfaction_value (t, args, tf_warning_or_error);
> +
> +  static bool suggested_p;
> +  if (concepts_diagnostics_max_depth_exceeded_p
> +      && current_constraint_diagnosis_depth == 0
> +      && !suggested_p)
> +    {
> +      inform (UNKNOWN_LOCATION,
> +	      "set -fconcepts-diagnostics-depth= to at least %d for more detail",
> +	      concepts_diagnostics_max_depth + 1);


Can you please quote the option name in the diagnostic (e.g., by using
"set %qs to...", "-fconcepts-diagnostics-depth=" or equivalent) to avoid
-Wformat-diag?  It won't cause errors (yet) but will eventually need to
be cleaned up.

Thanks
Martin
Li, Pan2 via Gcc-patches March 16, 2020, 2:48 p.m. UTC | #4
Hi Martin,

On Sun, 15 Mar 2020, Martin Sebor wrote:

> On 3/11/20 4:18 PM, Patrick Palka via Gcc-patches wrote:
> ...
> > Hmm, like this?  This version introduces a new static member function
> > diagnosing_failed_constraint::replay_errors_p that checks
> > current_constraint_diagnosis_depth, and also sets max_depth_exceeded_p
> > when appropriate.
> > 
> ...
> 
> Just one small quoting nit:
> 
> 
> > @@ -3368,11 +3464,25 @@ diagnose_constraints (location_t loc, tree t, tree
> > args)
> >   {
> >     inform (loc, "constraints not satisfied");
> >   +  if (concepts_diagnostics_max_depth == 0)
> > +    return;
> > +
> >     /* Replay satisfaction, but diagnose errors.  */
> >     if (!args)
> >       constraint_satisfaction_value (t, tf_warning_or_error);
> >     else
> >       constraint_satisfaction_value (t, args, tf_warning_or_error);
> > +
> > +  static bool suggested_p;
> > +  if (concepts_diagnostics_max_depth_exceeded_p
> > +      && current_constraint_diagnosis_depth == 0
> > +      && !suggested_p)
> > +    {
> > +      inform (UNKNOWN_LOCATION,
> > +	      "set -fconcepts-diagnostics-depth= to at least %d for more
> > detail",
> > +	      concepts_diagnostics_max_depth + 1);
> 
> 
> Can you please quote the option name in the diagnostic (e.g., by using
> "set %qs to...", "-fconcepts-diagnostics-depth=" or equivalent) to avoid
> -Wformat-diag?  It won't cause errors (yet) but will eventually need to
> be cleaned up.

Yes sure, consider it done.  I've amended the patch locally with this
change.

> 
> Thanks
> Martin
> 
>
Li, Pan2 via Gcc-patches March 27, 2020, 4:17 a.m. UTC | #5
On Mon, 16 Mar 2020, Patrick Palka wrote:

> Hi Martin,
> 
> On Sun, 15 Mar 2020, Martin Sebor wrote:
> 
> > On 3/11/20 4:18 PM, Patrick Palka via Gcc-patches wrote:
> > ...
> > > Hmm, like this?  This version introduces a new static member function
> > > diagnosing_failed_constraint::replay_errors_p that checks
> > > current_constraint_diagnosis_depth, and also sets max_depth_exceeded_p
> > > when appropriate.
> > > 
> > ...
> > 
> > Just one small quoting nit:
> > 
> > 
> > > @@ -3368,11 +3464,25 @@ diagnose_constraints (location_t loc, tree t, tree
> > > args)
> > >   {
> > >     inform (loc, "constraints not satisfied");
> > >   +  if (concepts_diagnostics_max_depth == 0)
> > > +    return;
> > > +
> > >     /* Replay satisfaction, but diagnose errors.  */
> > >     if (!args)
> > >       constraint_satisfaction_value (t, tf_warning_or_error);
> > >     else
> > >       constraint_satisfaction_value (t, args, tf_warning_or_error);
> > > +
> > > +  static bool suggested_p;
> > > +  if (concepts_diagnostics_max_depth_exceeded_p
> > > +      && current_constraint_diagnosis_depth == 0
> > > +      && !suggested_p)
> > > +    {
> > > +      inform (UNKNOWN_LOCATION,
> > > +	      "set -fconcepts-diagnostics-depth= to at least %d for more
> > > detail",
> > > +	      concepts_diagnostics_max_depth + 1);
> > 
> > 
> > Can you please quote the option name in the diagnostic (e.g., by using
> > "set %qs to...", "-fconcepts-diagnostics-depth=" or equivalent) to avoid
> > -Wformat-diag?  It won't cause errors (yet) but will eventually need to
> > be cleaned up.
> 
> Yes sure, consider it done.  I've amended the patch locally with this
> change.

Here's a rebased version of this patch with the above diagnostic change,
no other changes made (aside from a minor adjustment to diagnostic5.C):

-- >8 --

gcc/c-family/ChangeLog:

	* c.opt: Add -fconcepts-diagnostics-depth.

gcc/cp/ChangeLog:

	* constraint.cc (finish_constraint_binary_op): Set the location of EXPR
	as well as its range, because build_x_binary_op doesn't always do so.
	(current_constraint_diagnosis_depth): New.
	(concepts_diagnostics_max_depth_exceeded_p): New.
	(collect_operands_of_disjunction): New.
	(satisfy_disjunction): When diagnosing a satisfaction failure, maybe
	replay each branch of the disjunction, subject to the current diagnosis
	depth.
	(diagnose_valid_expression): When diagnosing a satisfaction failure,
	maybe replay the substitution error, subject to the current diagnosis
	recursion.
	(diagnose_valid_type): Likewise.
	(diagnose_nested_requiremnet): Likewise.
	(diagnosing_failed_constraint::diagnosing_failed_constraint): Increment
	current_constraint_diagnosis_depth when diagnosing.
	(diagnosing_failed_constraint::~diagnosing_failed_constraint): Decrement
	current_constraint_diagnosis_depth when diagnosing.
	(diagnosing_failed_constraint::replay_errors_p): New static member
	function.
	(diagnose_constraints): Don't diagnose if concepts_diagnostics_max_depth
	is 0.  Emit a one-off note to increase -fconcepts-diagnostics-depth if
	the limit was exceeded.
	* cp-tree.h (diagnosing_failed_constraint::replay_errors_p): Declare.

gcc/testsuite/ChangeLog:

	* g++.dg/concepts/diagnostic2.C: Expect "no operand" instead of
	"neither operand".
	* g++.dg/concepts/diagnostic5.C: New test.
---
 gcc/c-family/c.opt                          |   4 +
 gcc/cp/constraint.cc                        | 150 +++++++++++++++++---
 gcc/cp/cp-tree.h                            |   1 +
 gcc/testsuite/g++.dg/concepts/diagnostic2.C |   2 +-
 gcc/testsuite/g++.dg/concepts/diagnostic5.C |  46 ++++++
 5 files changed, 182 insertions(+), 21 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/concepts/diagnostic5.C

diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index a1e0f4cdc3a..c49da99d395 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -1453,6 +1453,10 @@ fconcepts-ts
 C++ ObjC++ Var(flag_concepts_ts) Init(0)
 Enable certain features present in the Concepts TS.
 
+fconcepts-diagnostics-depth=
+C++ ObjC++ Joined RejectNegative UInteger Var(concepts_diagnostics_max_depth) Init(1)
+Specify maximum error replay depth during recursive diagnosis of a constraint satisfaction failure.
+
 fcond-mismatch
 C ObjC C++ ObjC++
 Allow the arguments of the '?' operator to have different types.
diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index a86bcdf603a..76c520318c3 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -162,6 +162,7 @@ finish_constraint_binary_op (location_t loc,
   /* When either operand is dependent, the overload set may be non-empty.  */
   if (expr == error_mark_node)
     return error_mark_node;
+  expr.set_location (loc);
   expr.set_range (lhs.get_start (), rhs.get_finish ());
   return expr;
 }
@@ -2395,6 +2396,49 @@ satisfy_conjunction (tree t, tree args, subst_info info)
   return satisfy_constraint_r (TREE_OPERAND (t, 1), args, info);
 }
 
+/* The current depth at which we're replaying an error during recursive
+   diagnosis of a constraint satisfaction failure.  */
+
+static int current_constraint_diagnosis_depth;
+
+/* Whether CURRENT_CONSTRAINT_DIAGNOSIS_DEPTH has ever exceeded
+   CONCEPTS_DIAGNOSTICS_MAX_DEPTH during recursive diagnosis of a constraint
+   satisfaction error.  */
+
+static bool concepts_diagnostics_max_depth_exceeded_p;
+
+/* Recursive subroutine of collect_operands_of_disjunction.  T is a normalized
+   subexpression of a constraint (composed of CONJ_CONSTRs and DISJ_CONSTRs)
+   and E is the corresponding unnormalized subexpression (composed of
+   TRUTH_ANDIF_EXPRs and TRUTH_ORIF_EXPRs).  */
+
+static void
+collect_operands_of_disjunction_r (tree t, tree e,
+				   auto_vec<tree_pair> *operands)
+{
+  if (TREE_CODE (e) == TRUTH_ORIF_EXPR)
+    {
+      collect_operands_of_disjunction_r (TREE_OPERAND (t, 0),
+					 TREE_OPERAND (e, 0), operands);
+      collect_operands_of_disjunction_r (TREE_OPERAND (t, 1),
+					 TREE_OPERAND (e, 1), operands);
+    }
+  else
+    {
+      tree_pair p = std::make_pair (t, e);
+      operands->safe_push (p);
+    }
+}
+
+/* Recursively collect the normalized and unnormalized operands of the
+   disjunction T and append them to OPERANDS in order.  */
+
+static void
+collect_operands_of_disjunction (tree t, auto_vec<tree_pair> *operands)
+{
+  collect_operands_of_disjunction_r (t, CONSTR_EXPR (t), operands);
+}
+
 /* Compute the satisfaction of a disjunction.  */
 
 static tree
@@ -2412,11 +2456,25 @@ satisfy_disjunction (tree t, tree args, subst_info info)
   tree rhs = satisfy_constraint_r (TREE_OPERAND (t, 1), args, quiet);
   if (rhs != boolean_true_node && info.noisy ())
     {
-      location_t loc = cp_expr_location (CONSTR_EXPR (t));
-      inform (loc, "neither operand of the disjunction is satisfied");
-      /* TODO: Replay the LHS and RHS to find failures in both branches.  */
-      // satisfy_constraint_r (TREE_OPERAND (t, 0), args, info);
-      // satisfy_constraint_r (TREE_OPERAND (t, 1), args, info);
+      cp_expr disj_expr = CONSTR_EXPR (t);
+      inform (disj_expr.get_location (),
+	      "no operand of the disjunction is satisfied");
+      if (diagnosing_failed_constraint::replay_errors_p ())
+	{
+	  /* Replay the error in each branch of the disjunction.  */
+	  auto_vec<tree_pair> operands;
+	  collect_operands_of_disjunction (t, &operands);
+	  for (unsigned i = 0; i < operands.length (); i++)
+	    {
+	      tree norm_op = operands[i].first;
+	      tree op = operands[i].second;
+	      location_t loc = make_location (cp_expr_location (op),
+					      disj_expr.get_start (),
+					      disj_expr.get_finish ());
+	      inform (loc, "the operand %qE is unsatisfied because", op);
+	      satisfy_constraint_r (norm_op, args, info);
+	    }
+	}
     }
   return rhs;
 }
@@ -3182,10 +3240,14 @@ diagnose_valid_expression (tree expr, tree args, tree in_decl)
     return result;
 
   location_t loc = cp_expr_loc_or_input_loc (expr);
-  inform (loc, "the required expression %qE is invalid", expr);
-
-  /* TODO: Replay the substitution to diagnose the error?  */
-  // tsubst_expr (expr, args, tf_error, in_decl, false);
+  if (diagnosing_failed_constraint::replay_errors_p ())
+    {
+      /* Replay the substitution error.  */
+      inform (loc, "the required expression %qE is invalid, because", expr);
+      tsubst_expr (expr, args, tf_error, in_decl, false);
+    }
+  else
+    inform (loc, "the required expression %qE is invalid", expr);
 
   return error_mark_node;
 }
@@ -3198,10 +3260,14 @@ diagnose_valid_type (tree type, tree args, tree in_decl)
     return result;
 
   location_t loc = cp_expr_loc_or_input_loc (type);
-  inform (loc, "the required type %qT is invalid", type);
-
-  /* TODO: Replay the substitution to diagnose the error?  */
-  // tsubst (type, args, tf_error, in_decl);
+  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;
 }
@@ -3280,11 +3346,16 @@ diagnose_nested_requirement (tree req, tree args)
 
   tree expr = TREE_OPERAND (req, 0);
   location_t loc = cp_expr_location (expr);
-  inform (loc, "nested requirement %qE is not satisfied", expr);
+  if (diagnosing_failed_constraint::replay_errors_p ())
+    {
+      /* Replay the substitution error.  */
+      inform (loc, "nested requirement %qE is not satisfied, because", expr);
+      subst_info noisy (tf_warning_or_error, NULL_TREE);
+      satisfy_constraint_expression (expr, args, noisy);
+    }
+  else
+    inform (loc, "nested requirement %qE is not satisfied", expr);
 
-  /* TODO: Replay the substitution to diagnose the error?  */
-  // subst_info noisy (tf_warning_or_error, NULL_TREE);
-  // satisfy_constraint (norm, args, info);
 }
 
 static void
@@ -3385,14 +3456,38 @@ diagnosing_failed_constraint (tree t, tree args, bool diag)
   : diagnosing_error (diag)
 {
   if (diagnosing_error)
-    current_failed_constraint = tree_cons (args, t, current_failed_constraint);
+    {
+      current_failed_constraint
+	= tree_cons (args, t, current_failed_constraint);
+      ++current_constraint_diagnosis_depth;
+    }
 }
 
 diagnosing_failed_constraint::
 ~diagnosing_failed_constraint ()
 {
-  if (diagnosing_error && current_failed_constraint)
-    current_failed_constraint = TREE_CHAIN (current_failed_constraint);
+  if (diagnosing_error)
+    {
+      --current_constraint_diagnosis_depth;
+      if (current_failed_constraint)
+	current_failed_constraint = TREE_CHAIN (current_failed_constraint);
+    }
+
+}
+
+/* Whether we are allowed to replay an error that underlies a constraint failure
+   at the current diagnosis depth.  */
+
+bool
+diagnosing_failed_constraint::replay_errors_p ()
+{
+  if (current_constraint_diagnosis_depth >= concepts_diagnostics_max_depth)
+    {
+      concepts_diagnostics_max_depth_exceeded_p = true;
+      return false;
+    }
+  else
+    return true;
 }
 
 /* Emit diagnostics detailing the failure ARGS to satisfy the constraints
@@ -3403,11 +3498,26 @@ diagnose_constraints (location_t loc, tree t, tree args)
 {
   inform (loc, "constraints not satisfied");
 
+  if (concepts_diagnostics_max_depth == 0)
+    return;
+
   /* Replay satisfaction, but diagnose errors.  */
   if (!args)
     constraint_satisfaction_value (t, tf_warning_or_error);
   else
     constraint_satisfaction_value (t, args, tf_warning_or_error);
+
+  static bool suggested_p;
+  if (concepts_diagnostics_max_depth_exceeded_p
+      && current_constraint_diagnosis_depth == 0
+      && !suggested_p)
+    {
+      inform (UNKNOWN_LOCATION,
+	      "set %qs to at least %d for more detail",
+	      "-fconcepts-diagnostics-depth=",
+	      concepts_diagnostics_max_depth + 1);
+      suggested_p = true;
+    }
 }
 
 #include "gt-cp-constraint.h"
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 4e1d0f1d42e..af9c4516996 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7833,6 +7833,7 @@ struct diagnosing_failed_constraint
 {
   diagnosing_failed_constraint (tree, tree, bool);
   ~diagnosing_failed_constraint ();
+  static bool replay_errors_p ();
 
   bool diagnosing_error;
 };
diff --git a/gcc/testsuite/g++.dg/concepts/diagnostic2.C b/gcc/testsuite/g++.dg/concepts/diagnostic2.C
index ce51b71fa8b..47accb8366e 100644
--- a/gcc/testsuite/g++.dg/concepts/diagnostic2.C
+++ b/gcc/testsuite/g++.dg/concepts/diagnostic2.C
@@ -5,7 +5,7 @@ template<typename T>
   inline constexpr bool foo_v = false;
 
 template<typename T>
-  concept foo = foo_v<T> || foo_v<T&>; // { dg-message "neither operand" }
+  concept foo = foo_v<T> || foo_v<T&>; // { dg-message "no operand" }
 /* { dg-begin-multiline-output "" }
    concept foo = foo_v<T> || foo_v<T&>;
                  ~~~~~~~~~^~~~~~~~~~~~
diff --git a/gcc/testsuite/g++.dg/concepts/diagnostic5.C b/gcc/testsuite/g++.dg/concepts/diagnostic5.C
new file mode 100644
index 00000000000..2641dc18423
--- /dev/null
+++ b/gcc/testsuite/g++.dg/concepts/diagnostic5.C
@@ -0,0 +1,46 @@
+// { dg-do compile { target c++2a } }
+// { dg-additional-options "-fconcepts-diagnostics-depth=2" }
+
+template<typename T>
+  concept c1 = requires { typename T::blah; };
+// { dg-message "satisfaction of .c1<T>. .with T = char." "" { target *-*-* } .-1 }
+// { dg-message "satisfaction of .c1<char\\*>." "" { target *-*-* } .-2 }
+// { dg-message ".typename T::blah. is invalid" "" { target *-*-* } .-3 }
+
+template<typename T>
+  concept c2 = requires (T x) { *x; };
+// { dg-message "satisfaction of .c2<T>. .with T = char." "" { target *-*-* } .-1 }
+// { dg-message "in requirements with .char x." "" { target *-*-* } .-2 }
+// { dg-message "required expression .* is invalid" "" { target *-*-* } .-3 }
+
+template<typename T>
+  concept c3 = __is_same(T, const T) || __is_same(T, int);
+// { dg-message "satisfaction of .c3<T>. .with T = char." "" { target *-*-* } .-1 }
+// { dg-message "no operand of the disjunction is satisfied" "" { target *-*-* } .-2 }
+
+template<typename T>
+  concept c4 = requires (T x) { requires c2<const T> || c2<volatile T>; };
+// { dg-message "satisfaction of .c4<T>. .with T = char." "" { target *-*-* } .-1 }
+// { dg-message "nested requirement" "" { target *-*-* } .-2 }
+
+template<typename T>
+  concept c5 = requires (T x) { { &x } -> c1; };
+// { dg-message "satisfaction of .c5<T>. .with T = char." "" { target *-*-* } .-1 }
+// { dg-message "in requirements with .char x." "" { target *-*-* } .-2 }
+// { dg-message "does not satisfy return-type-requirement" "" { target *-*-* } .-3 }
+// { dg-error "deduced expression type does not satisfy" "" { target *-*-* } .-4 }
+
+template<typename T>
+  requires (c1<T> || c2<T>) || (c3<T> || c4<T>) || c5<T> // { dg-message "49: no operand" }
+  // { dg-message ".c1<T>. is unsatisfied because" "" { target *-*-* } .-1 }
+  // { dg-message ".c2<T>. is unsatisfied because" "" { target *-*-* } .-2 }
+  // { dg-message ".c3<T>. is unsatisfied because" "" { target *-*-* } .-3 }
+  // { dg-message ".c4<T>. is unsatisfied because" "" { target *-*-* } .-4 }
+  // { dg-message ".c5<T>. is unsatisfied because" "" { target *-*-* } .-5 }
+  void foo() { }
+
+void
+bar()
+{
+  foo<char>(); // { dg-error "use of" }
+}
Li, Pan2 via Gcc-patches March 27, 2020, 7:20 p.m. UTC | #6
On 3/27/20 12:17 AM, Patrick Palka wrote:
> On Mon, 16 Mar 2020, Patrick Palka wrote:
> 
>> Hi Martin,
>>
>> On Sun, 15 Mar 2020, Martin Sebor wrote:
>>
>>> On 3/11/20 4:18 PM, Patrick Palka via Gcc-patches wrote:
>>> ...
>>>> Hmm, like this?  This version introduces a new static member function
>>>> diagnosing_failed_constraint::replay_errors_p that checks
>>>> current_constraint_diagnosis_depth, and also sets max_depth_exceeded_p
>>>> when appropriate.
>>>>
>>> ...
>>>
>>> Just one small quoting nit:
>>>
>>>
>>>> @@ -3368,11 +3464,25 @@ diagnose_constraints (location_t loc, tree t, tree
>>>> args)
>>>>    {
>>>>      inform (loc, "constraints not satisfied");
>>>>    +  if (concepts_diagnostics_max_depth == 0)
>>>> +    return;
>>>> +
>>>>      /* Replay satisfaction, but diagnose errors.  */
>>>>      if (!args)
>>>>        constraint_satisfaction_value (t, tf_warning_or_error);
>>>>      else
>>>>        constraint_satisfaction_value (t, args, tf_warning_or_error);
>>>> +
>>>> +  static bool suggested_p;
>>>> +  if (concepts_diagnostics_max_depth_exceeded_p
>>>> +      && current_constraint_diagnosis_depth == 0
>>>> +      && !suggested_p)
>>>> +    {
>>>> +      inform (UNKNOWN_LOCATION,
>>>> +	      "set -fconcepts-diagnostics-depth= to at least %d for more
>>>> detail",
>>>> +	      concepts_diagnostics_max_depth + 1);
>>>
>>>
>>> Can you please quote the option name in the diagnostic (e.g., by using
>>> "set %qs to...", "-fconcepts-diagnostics-depth=" or equivalent) to avoid
>>> -Wformat-diag?  It won't cause errors (yet) but will eventually need to
>>> be cleaned up.
>>
>> Yes sure, consider it done.  I've amended the patch locally with this
>> change.
> 
> Here's a rebased version of this patch with the above diagnostic change,
> no other changes made (aside from a minor adjustment to diagnostic5.C):

OK.

> -- >8 --
> 
> gcc/c-family/ChangeLog:
> 
> 	* c.opt: Add -fconcepts-diagnostics-depth.
> 
> gcc/cp/ChangeLog:
> 
> 	* constraint.cc (finish_constraint_binary_op): Set the location of EXPR
> 	as well as its range, because build_x_binary_op doesn't always do so.
> 	(current_constraint_diagnosis_depth): New.
> 	(concepts_diagnostics_max_depth_exceeded_p): New.
> 	(collect_operands_of_disjunction): New.
> 	(satisfy_disjunction): When diagnosing a satisfaction failure, maybe
> 	replay each branch of the disjunction, subject to the current diagnosis
> 	depth.
> 	(diagnose_valid_expression): When diagnosing a satisfaction failure,
> 	maybe replay the substitution error, subject to the current diagnosis
> 	recursion.
> 	(diagnose_valid_type): Likewise.
> 	(diagnose_nested_requiremnet): Likewise.
> 	(diagnosing_failed_constraint::diagnosing_failed_constraint): Increment
> 	current_constraint_diagnosis_depth when diagnosing.
> 	(diagnosing_failed_constraint::~diagnosing_failed_constraint): Decrement
> 	current_constraint_diagnosis_depth when diagnosing.
> 	(diagnosing_failed_constraint::replay_errors_p): New static member
> 	function.
> 	(diagnose_constraints): Don't diagnose if concepts_diagnostics_max_depth
> 	is 0.  Emit a one-off note to increase -fconcepts-diagnostics-depth if
> 	the limit was exceeded.
> 	* cp-tree.h (diagnosing_failed_constraint::replay_errors_p): Declare.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/concepts/diagnostic2.C: Expect "no operand" instead of
> 	"neither operand".
> 	* g++.dg/concepts/diagnostic5.C: New test.
> ---
>   gcc/c-family/c.opt                          |   4 +
>   gcc/cp/constraint.cc                        | 150 +++++++++++++++++---
>   gcc/cp/cp-tree.h                            |   1 +
>   gcc/testsuite/g++.dg/concepts/diagnostic2.C |   2 +-
>   gcc/testsuite/g++.dg/concepts/diagnostic5.C |  46 ++++++
>   5 files changed, 182 insertions(+), 21 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/concepts/diagnostic5.C
> 
> diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
> index a1e0f4cdc3a..c49da99d395 100644
> --- a/gcc/c-family/c.opt
> +++ b/gcc/c-family/c.opt
> @@ -1453,6 +1453,10 @@ fconcepts-ts
>   C++ ObjC++ Var(flag_concepts_ts) Init(0)
>   Enable certain features present in the Concepts TS.
>   
> +fconcepts-diagnostics-depth=
> +C++ ObjC++ Joined RejectNegative UInteger Var(concepts_diagnostics_max_depth) Init(1)
> +Specify maximum error replay depth during recursive diagnosis of a constraint satisfaction failure.
> +
>   fcond-mismatch
>   C ObjC C++ ObjC++
>   Allow the arguments of the '?' operator to have different types.
> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> index a86bcdf603a..76c520318c3 100644
> --- a/gcc/cp/constraint.cc
> +++ b/gcc/cp/constraint.cc
> @@ -162,6 +162,7 @@ finish_constraint_binary_op (location_t loc,
>     /* When either operand is dependent, the overload set may be non-empty.  */
>     if (expr == error_mark_node)
>       return error_mark_node;
> +  expr.set_location (loc);
>     expr.set_range (lhs.get_start (), rhs.get_finish ());
>     return expr;
>   }
> @@ -2395,6 +2396,49 @@ satisfy_conjunction (tree t, tree args, subst_info info)
>     return satisfy_constraint_r (TREE_OPERAND (t, 1), args, info);
>   }
>   
> +/* The current depth at which we're replaying an error during recursive
> +   diagnosis of a constraint satisfaction failure.  */
> +
> +static int current_constraint_diagnosis_depth;
> +
> +/* Whether CURRENT_CONSTRAINT_DIAGNOSIS_DEPTH has ever exceeded
> +   CONCEPTS_DIAGNOSTICS_MAX_DEPTH during recursive diagnosis of a constraint
> +   satisfaction error.  */
> +
> +static bool concepts_diagnostics_max_depth_exceeded_p;
> +
> +/* Recursive subroutine of collect_operands_of_disjunction.  T is a normalized
> +   subexpression of a constraint (composed of CONJ_CONSTRs and DISJ_CONSTRs)
> +   and E is the corresponding unnormalized subexpression (composed of
> +   TRUTH_ANDIF_EXPRs and TRUTH_ORIF_EXPRs).  */
> +
> +static void
> +collect_operands_of_disjunction_r (tree t, tree e,
> +				   auto_vec<tree_pair> *operands)
> +{
> +  if (TREE_CODE (e) == TRUTH_ORIF_EXPR)
> +    {
> +      collect_operands_of_disjunction_r (TREE_OPERAND (t, 0),
> +					 TREE_OPERAND (e, 0), operands);
> +      collect_operands_of_disjunction_r (TREE_OPERAND (t, 1),
> +					 TREE_OPERAND (e, 1), operands);
> +    }
> +  else
> +    {
> +      tree_pair p = std::make_pair (t, e);
> +      operands->safe_push (p);
> +    }
> +}
> +
> +/* Recursively collect the normalized and unnormalized operands of the
> +   disjunction T and append them to OPERANDS in order.  */
> +
> +static void
> +collect_operands_of_disjunction (tree t, auto_vec<tree_pair> *operands)
> +{
> +  collect_operands_of_disjunction_r (t, CONSTR_EXPR (t), operands);
> +}
> +
>   /* Compute the satisfaction of a disjunction.  */
>   
>   static tree
> @@ -2412,11 +2456,25 @@ satisfy_disjunction (tree t, tree args, subst_info info)
>     tree rhs = satisfy_constraint_r (TREE_OPERAND (t, 1), args, quiet);
>     if (rhs != boolean_true_node && info.noisy ())
>       {
> -      location_t loc = cp_expr_location (CONSTR_EXPR (t));
> -      inform (loc, "neither operand of the disjunction is satisfied");
> -      /* TODO: Replay the LHS and RHS to find failures in both branches.  */
> -      // satisfy_constraint_r (TREE_OPERAND (t, 0), args, info);
> -      // satisfy_constraint_r (TREE_OPERAND (t, 1), args, info);
> +      cp_expr disj_expr = CONSTR_EXPR (t);
> +      inform (disj_expr.get_location (),
> +	      "no operand of the disjunction is satisfied");
> +      if (diagnosing_failed_constraint::replay_errors_p ())
> +	{
> +	  /* Replay the error in each branch of the disjunction.  */
> +	  auto_vec<tree_pair> operands;
> +	  collect_operands_of_disjunction (t, &operands);
> +	  for (unsigned i = 0; i < operands.length (); i++)
> +	    {
> +	      tree norm_op = operands[i].first;
> +	      tree op = operands[i].second;
> +	      location_t loc = make_location (cp_expr_location (op),
> +					      disj_expr.get_start (),
> +					      disj_expr.get_finish ());
> +	      inform (loc, "the operand %qE is unsatisfied because", op);
> +	      satisfy_constraint_r (norm_op, args, info);
> +	    }
> +	}
>       }
>     return rhs;
>   }
> @@ -3182,10 +3240,14 @@ diagnose_valid_expression (tree expr, tree args, tree in_decl)
>       return result;
>   
>     location_t loc = cp_expr_loc_or_input_loc (expr);
> -  inform (loc, "the required expression %qE is invalid", expr);
> -
> -  /* TODO: Replay the substitution to diagnose the error?  */
> -  // tsubst_expr (expr, args, tf_error, in_decl, false);
> +  if (diagnosing_failed_constraint::replay_errors_p ())
> +    {
> +      /* Replay the substitution error.  */
> +      inform (loc, "the required expression %qE is invalid, because", expr);
> +      tsubst_expr (expr, args, tf_error, in_decl, false);
> +    }
> +  else
> +    inform (loc, "the required expression %qE is invalid", expr);
>   
>     return error_mark_node;
>   }
> @@ -3198,10 +3260,14 @@ diagnose_valid_type (tree type, tree args, tree in_decl)
>       return result;
>   
>     location_t loc = cp_expr_loc_or_input_loc (type);
> -  inform (loc, "the required type %qT is invalid", type);
> -
> -  /* TODO: Replay the substitution to diagnose the error?  */
> -  // tsubst (type, args, tf_error, in_decl);
> +  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;
>   }
> @@ -3280,11 +3346,16 @@ diagnose_nested_requirement (tree req, tree args)
>   
>     tree expr = TREE_OPERAND (req, 0);
>     location_t loc = cp_expr_location (expr);
> -  inform (loc, "nested requirement %qE is not satisfied", expr);
> +  if (diagnosing_failed_constraint::replay_errors_p ())
> +    {
> +      /* Replay the substitution error.  */
> +      inform (loc, "nested requirement %qE is not satisfied, because", expr);
> +      subst_info noisy (tf_warning_or_error, NULL_TREE);
> +      satisfy_constraint_expression (expr, args, noisy);
> +    }
> +  else
> +    inform (loc, "nested requirement %qE is not satisfied", expr);
>   
> -  /* TODO: Replay the substitution to diagnose the error?  */
> -  // subst_info noisy (tf_warning_or_error, NULL_TREE);
> -  // satisfy_constraint (norm, args, info);
>   }
>   
>   static void
> @@ -3385,14 +3456,38 @@ diagnosing_failed_constraint (tree t, tree args, bool diag)
>     : diagnosing_error (diag)
>   {
>     if (diagnosing_error)
> -    current_failed_constraint = tree_cons (args, t, current_failed_constraint);
> +    {
> +      current_failed_constraint
> +	= tree_cons (args, t, current_failed_constraint);
> +      ++current_constraint_diagnosis_depth;
> +    }
>   }
>   
>   diagnosing_failed_constraint::
>   ~diagnosing_failed_constraint ()
>   {
> -  if (diagnosing_error && current_failed_constraint)
> -    current_failed_constraint = TREE_CHAIN (current_failed_constraint);
> +  if (diagnosing_error)
> +    {
> +      --current_constraint_diagnosis_depth;
> +      if (current_failed_constraint)
> +	current_failed_constraint = TREE_CHAIN (current_failed_constraint);
> +    }
> +
> +}
> +
> +/* Whether we are allowed to replay an error that underlies a constraint failure
> +   at the current diagnosis depth.  */
> +
> +bool
> +diagnosing_failed_constraint::replay_errors_p ()
> +{
> +  if (current_constraint_diagnosis_depth >= concepts_diagnostics_max_depth)
> +    {
> +      concepts_diagnostics_max_depth_exceeded_p = true;
> +      return false;
> +    }
> +  else
> +    return true;
>   }
>   
>   /* Emit diagnostics detailing the failure ARGS to satisfy the constraints
> @@ -3403,11 +3498,26 @@ diagnose_constraints (location_t loc, tree t, tree args)
>   {
>     inform (loc, "constraints not satisfied");
>   
> +  if (concepts_diagnostics_max_depth == 0)
> +    return;
> +
>     /* Replay satisfaction, but diagnose errors.  */
>     if (!args)
>       constraint_satisfaction_value (t, tf_warning_or_error);
>     else
>       constraint_satisfaction_value (t, args, tf_warning_or_error);
> +
> +  static bool suggested_p;
> +  if (concepts_diagnostics_max_depth_exceeded_p
> +      && current_constraint_diagnosis_depth == 0
> +      && !suggested_p)
> +    {
> +      inform (UNKNOWN_LOCATION,
> +	      "set %qs to at least %d for more detail",
> +	      "-fconcepts-diagnostics-depth=",
> +	      concepts_diagnostics_max_depth + 1);
> +      suggested_p = true;
> +    }
>   }
>   
>   #include "gt-cp-constraint.h"
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index 4e1d0f1d42e..af9c4516996 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -7833,6 +7833,7 @@ struct diagnosing_failed_constraint
>   {
>     diagnosing_failed_constraint (tree, tree, bool);
>     ~diagnosing_failed_constraint ();
> +  static bool replay_errors_p ();
>   
>     bool diagnosing_error;
>   };
> diff --git a/gcc/testsuite/g++.dg/concepts/diagnostic2.C b/gcc/testsuite/g++.dg/concepts/diagnostic2.C
> index ce51b71fa8b..47accb8366e 100644
> --- a/gcc/testsuite/g++.dg/concepts/diagnostic2.C
> +++ b/gcc/testsuite/g++.dg/concepts/diagnostic2.C
> @@ -5,7 +5,7 @@ template<typename T>
>     inline constexpr bool foo_v = false;
>   
>   template<typename T>
> -  concept foo = foo_v<T> || foo_v<T&>; // { dg-message "neither operand" }
> +  concept foo = foo_v<T> || foo_v<T&>; // { dg-message "no operand" }
>   /* { dg-begin-multiline-output "" }
>      concept foo = foo_v<T> || foo_v<T&>;
>                    ~~~~~~~~~^~~~~~~~~~~~
> diff --git a/gcc/testsuite/g++.dg/concepts/diagnostic5.C b/gcc/testsuite/g++.dg/concepts/diagnostic5.C
> new file mode 100644
> index 00000000000..2641dc18423
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/concepts/diagnostic5.C
> @@ -0,0 +1,46 @@
> +// { dg-do compile { target c++2a } }
> +// { dg-additional-options "-fconcepts-diagnostics-depth=2" }
> +
> +template<typename T>
> +  concept c1 = requires { typename T::blah; };
> +// { dg-message "satisfaction of .c1<T>. .with T = char." "" { target *-*-* } .-1 }
> +// { dg-message "satisfaction of .c1<char\\*>." "" { target *-*-* } .-2 }
> +// { dg-message ".typename T::blah. is invalid" "" { target *-*-* } .-3 }
> +
> +template<typename T>
> +  concept c2 = requires (T x) { *x; };
> +// { dg-message "satisfaction of .c2<T>. .with T = char." "" { target *-*-* } .-1 }
> +// { dg-message "in requirements with .char x." "" { target *-*-* } .-2 }
> +// { dg-message "required expression .* is invalid" "" { target *-*-* } .-3 }
> +
> +template<typename T>
> +  concept c3 = __is_same(T, const T) || __is_same(T, int);
> +// { dg-message "satisfaction of .c3<T>. .with T = char." "" { target *-*-* } .-1 }
> +// { dg-message "no operand of the disjunction is satisfied" "" { target *-*-* } .-2 }
> +
> +template<typename T>
> +  concept c4 = requires (T x) { requires c2<const T> || c2<volatile T>; };
> +// { dg-message "satisfaction of .c4<T>. .with T = char." "" { target *-*-* } .-1 }
> +// { dg-message "nested requirement" "" { target *-*-* } .-2 }
> +
> +template<typename T>
> +  concept c5 = requires (T x) { { &x } -> c1; };
> +// { dg-message "satisfaction of .c5<T>. .with T = char." "" { target *-*-* } .-1 }
> +// { dg-message "in requirements with .char x." "" { target *-*-* } .-2 }
> +// { dg-message "does not satisfy return-type-requirement" "" { target *-*-* } .-3 }
> +// { dg-error "deduced expression type does not satisfy" "" { target *-*-* } .-4 }
> +
> +template<typename T>
> +  requires (c1<T> || c2<T>) || (c3<T> || c4<T>) || c5<T> // { dg-message "49: no operand" }
> +  // { dg-message ".c1<T>. is unsatisfied because" "" { target *-*-* } .-1 }
> +  // { dg-message ".c2<T>. is unsatisfied because" "" { target *-*-* } .-2 }
> +  // { dg-message ".c3<T>. is unsatisfied because" "" { target *-*-* } .-3 }
> +  // { dg-message ".c4<T>. is unsatisfied because" "" { target *-*-* } .-4 }
> +  // { dg-message ".c5<T>. is unsatisfied because" "" { target *-*-* } .-5 }
> +  void foo() { }
> +
> +void
> +bar()
> +{
> +  foo<char>(); // { dg-error "use of" }
> +}
>
diff mbox series

Patch

diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 1cd585fa71d..97ef488931d 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -1453,6 +1453,10 @@  fconcepts-ts
 C++ ObjC++ Var(flag_concepts_ts) Init(0)
 Enable certain features present in the Concepts TS.
 
+fconcepts-diagnostics-depth=
+C++ ObjC++ Joined RejectNegative UInteger Var(concepts_diagnostics_max_depth) Init(1)
+Specify maximum error replay depth during recursive diagnosis of a constraint satisfaction failure.
+
 fcond-mismatch
 C ObjC C++ ObjC++
 Allow the arguments of the '?' operator to have different types.
diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index 4bb4a3f7252..c5e3d64daa6 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -162,6 +162,7 @@  finish_constraint_binary_op (location_t loc,
   /* When either operand is dependent, the overload set may be non-empty.  */
   if (expr == error_mark_node)
     return error_mark_node;
+  expr.set_location (loc);
   expr.set_range (lhs.get_start (), rhs.get_finish ());
   return expr;
 }
@@ -2386,6 +2387,50 @@  satisfy_conjunction (tree t, tree args, subst_info info)
   return satisfy_constraint_r (TREE_OPERAND (t, 1), args, info);
 }
 
+/* The current depth at which we're replaying an error during recursive
+   diagnosis of a constraint satisfaction failure.  */
+
+static int current_constraint_diagnosis_depth;
+
+/* Whether CURRENT_CONSTRAINT_DIAGNOSIS_DEPTH has ever exceeded
+   CONCEPTS_DIAGNOSTICS_MAX_DEPTH during recursive diagnosis of a constraint
+   satisfaction error.  */
+
+static bool concepts_diagnostics_max_depth_exceeded_p;
+
+/* Recursive subroutine of collect_operands_of_disjunction.  T is a normalized
+   subexpression of a constraint (composed of CONJ_CONSTRs and DISJ_CONSTRs)
+   and E is the corresponding unnormalized subexpression (composed of
+   TRUTH_ANDIF_EXPRs and TRUTH_ORIF_EXPRs).  */
+
+static void
+collect_operands_of_disjunction_r (tree t, tree e,
+				   auto_vec<std::pair<tree, tree> > *operands)
+{
+  if (TREE_CODE (e) == TRUTH_ORIF_EXPR)
+    {
+      collect_operands_of_disjunction_r (TREE_OPERAND (t, 0),
+					 TREE_OPERAND (e, 0), operands);
+      collect_operands_of_disjunction_r (TREE_OPERAND (t, 1),
+					 TREE_OPERAND (e, 1), operands);
+    }
+  else
+    {
+      std::pair<tree, tree> p = {t, e};
+      operands->safe_push (p);
+    }
+}
+
+/* Recursively collect the normalized and unnormalized operands of the
+   disjunction T and append them to OPERANDS in order.  */
+
+static void
+collect_operands_of_disjunction (tree t,
+				 auto_vec<std::pair<tree, tree> > *operands)
+{
+  collect_operands_of_disjunction_r (t, CONSTR_EXPR (t), operands);
+}
+
 /* Compute the satisfaction of a disjunction.  */
 
 static tree
@@ -2403,11 +2448,27 @@  satisfy_disjunction (tree t, tree args, subst_info info)
   tree rhs = satisfy_constraint_r (TREE_OPERAND (t, 1), args, quiet);
   if (rhs != boolean_true_node && info.noisy ())
     {
-      location_t loc = cp_expr_location (CONSTR_EXPR (t));
-      inform (loc, "neither operand of the disjunction is satisfied");
-      /* TODO: Replay the LHS and RHS to find failures in both branches.  */
-      // satisfy_constraint_r (TREE_OPERAND (t, 0), args, info);
-      // satisfy_constraint_r (TREE_OPERAND (t, 1), args, info);
+      cp_expr disj_expr = CONSTR_EXPR (t);
+      inform (disj_expr.get_location (),
+	      "no operand of the disjunction is satisfied");
+      if (current_constraint_diagnosis_depth < concepts_diagnostics_max_depth)
+	{
+	  /* Replay the error in each branch of the disjunction.  */
+	  auto_vec<std::pair<tree, tree> > operands;
+	  collect_operands_of_disjunction (t, &operands);
+	  for (unsigned i = 0; i < operands.length (); i++)
+	    {
+	      tree norm_op = operands[i].first;
+	      tree op = operands[i].second;
+	      location_t loc = make_location (cp_expr_location (op),
+					      disj_expr.get_start (),
+					      disj_expr.get_finish ());
+	      inform (loc, "the operand %qE is unsatisfied because", op);
+	      satisfy_constraint_r (norm_op, args, info);
+	    }
+	}
+      else
+	concepts_diagnostics_max_depth_exceeded_p = true;
     }
   return rhs;
 }
@@ -3147,10 +3208,17 @@  diagnose_valid_expression (tree expr, tree args, tree in_decl)
     return result;
 
   location_t loc = cp_expr_loc_or_input_loc (expr);
-  inform (loc, "the required expression %qE is invalid", expr);
-
-  /* TODO: Replay the substitution to diagnose the error?  */
-  // tsubst_expr (expr, args, tf_error, in_decl, false);
+  if (current_constraint_diagnosis_depth < concepts_diagnostics_max_depth)
+    {
+      /* Replay the substitution error.  */
+      inform (loc, "the required expression %qE is invalid, because", expr);
+      tsubst_expr (expr, args, tf_error, in_decl, false);
+    }
+  else
+    {
+      inform (loc, "the required expression %qE is invalid", expr);
+      concepts_diagnostics_max_depth_exceeded_p = true;
+    }
 
   return error_mark_node;
 }
@@ -3163,10 +3231,17 @@  diagnose_valid_type (tree type, tree args, tree in_decl)
     return result;
 
   location_t loc = cp_expr_loc_or_input_loc (type);
-  inform (loc, "the required type %qT is invalid", type);
-
-  /* TODO: Replay the substitution to diagnose the error?  */
-  // tsubst (type, args, tf_error, in_decl);
+  if (current_constraint_diagnosis_depth < concepts_diagnostics_max_depth)
+    {
+      /* 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);
+      concepts_diagnostics_max_depth_exceeded_p = true;
+    }
 
   return error_mark_node;
 }
@@ -3245,11 +3320,19 @@  diagnose_nested_requirement (tree req, tree args)
 
   tree expr = TREE_OPERAND (req, 0);
   location_t loc = cp_expr_location (expr);
-  inform (loc, "nested requirement %qE is not satisfied", expr);
+  if (current_constraint_diagnosis_depth < concepts_diagnostics_max_depth)
+    {
+      /* Replay the substitution error.  */
+      inform (loc, "nested requirement %qE is not satisfied, because", expr);
+      subst_info noisy (tf_warning_or_error, NULL_TREE);
+      satisfy_constraint_expression (expr, args, noisy);
+    }
+  else
+    {
+      inform (loc, "nested requirement %qE is not satisfied", expr);
+      concepts_diagnostics_max_depth_exceeded_p = true;
+    }
 
-  /* TODO: Replay the substitution to diagnose the error?  */
-  // subst_info noisy (tf_warning_or_error, NULL_TREE);
-  // satisfy_constraint (norm, args, info);
 }
 
 static void
@@ -3346,14 +3429,23 @@  diagnosing_failed_constraint (tree t, tree args, bool diag)
   : diagnosing_error (diag)
 {
   if (diagnosing_error)
-    current_failed_constraint = tree_cons (args, t, current_failed_constraint);
+    {
+      current_failed_constraint
+	= tree_cons (args, t, current_failed_constraint);
+      ++current_constraint_diagnosis_depth;
+    }
 }
 
 diagnosing_failed_constraint::
 ~diagnosing_failed_constraint ()
 {
-  if (diagnosing_error && current_failed_constraint)
-    current_failed_constraint = TREE_CHAIN (current_failed_constraint);
+  if (diagnosing_error)
+    {
+      --current_constraint_diagnosis_depth;
+      if (current_failed_constraint)
+	current_failed_constraint = TREE_CHAIN (current_failed_constraint);
+    }
+
 }
 
 /* Emit diagnostics detailing the failure ARGS to satisfy the constraints
@@ -3364,11 +3456,25 @@  diagnose_constraints (location_t loc, tree t, tree args)
 {
   inform (loc, "constraints not satisfied");
 
+  if (concepts_diagnostics_max_depth == 0)
+    return;
+
   /* Replay satisfaction, but diagnose errors.  */
   if (!args)
     constraint_satisfaction_value (t, tf_warning_or_error);
   else
     constraint_satisfaction_value (t, args, tf_warning_or_error);
+
+  static bool suggested_p;
+  if (concepts_diagnostics_max_depth_exceeded_p
+      && current_constraint_diagnosis_depth == 0
+      && !suggested_p)
+    {
+      inform (UNKNOWN_LOCATION,
+	      "set -fconcepts-diagnostics-depth= to at least %d for more detail",
+	      concepts_diagnostics_max_depth + 1);
+      suggested_p = true;
+    }
 }
 
 #include "gt-cp-constraint.h"
diff --git a/gcc/testsuite/g++.dg/concepts/diagnostic2.C b/gcc/testsuite/g++.dg/concepts/diagnostic2.C
index ce51b71fa8b..47accb8366e 100644
--- a/gcc/testsuite/g++.dg/concepts/diagnostic2.C
+++ b/gcc/testsuite/g++.dg/concepts/diagnostic2.C
@@ -5,7 +5,7 @@  template<typename T>
   inline constexpr bool foo_v = false;
 
 template<typename T>
-  concept foo = foo_v<T> || foo_v<T&>; // { dg-message "neither operand" }
+  concept foo = foo_v<T> || foo_v<T&>; // { dg-message "no operand" }
 /* { dg-begin-multiline-output "" }
    concept foo = foo_v<T> || foo_v<T&>;
                  ~~~~~~~~~^~~~~~~~~~~~
diff --git a/gcc/testsuite/g++.dg/concepts/diagnostic5.C b/gcc/testsuite/g++.dg/concepts/diagnostic5.C
new file mode 100644
index 00000000000..3c3b42f566c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/concepts/diagnostic5.C
@@ -0,0 +1,46 @@ 
+// { dg-do compile { target c++2a } }
+// { dg-additional-options "-fconcepts-diagnostics-depth=2" }
+
+template<typename T>
+  concept c1 = requires { typename T::blah; };
+// { dg-message "satisfaction of .c1<char>." "" { target *-*-* } .-1 }
+// { dg-message "satisfaction of .c1<char\\*>." "" { target *-*-* } .-2 }
+// { dg-message ".typename T::blah. is invalid" "" { target *-*-* } .-3 }
+
+template<typename T>
+  concept c2 = requires (T x) { *x; };
+// { dg-message "satisfaction of .c2<char>." "" { target *-*-* } .-1 }
+// { dg-message "in requirements with .char x." "" { target *-*-* } .-2 }
+// { dg-message "required expression .* is invalid" "" { target *-*-* } .-3 }
+
+template<typename T>
+  concept c3 = __is_same(T, const T) || __is_same(T, int);
+// { dg-message "satisfaction of .c3<char>." "" { target *-*-* } .-1 }
+// { dg-message "no operand of the disjunction is satisfied" "" { target *-*-* } .-2 }
+
+template<typename T>
+  concept c4 = requires (T x) { requires c2<const T> || c2<volatile T>; };
+// { dg-message "satisfaction of .c4<char>." "" { target *-*-* } .-1 }
+// { dg-message "nested requirement" "" { target *-*-* } .-2 }
+
+template<typename T>
+  concept c5 = requires (T x) { { &x } -> c1; };
+// { dg-message "satisfaction of .c5<char>." "" { target *-*-* } .-1 }
+// { dg-message "in requirements with .char x." "" { target *-*-* } .-2 }
+// { dg-message "does not satisfy return-type-requirement" "" { target *-*-* } .-3 }
+// { dg-error "deduced expression type does not satisfy" "" { target *-*-* } .-4 }
+
+template<typename T>
+  requires (c1<T> || c2<T>) || (c3<T> || c4<T>) || c5<T> // { dg-message "49: no operand" }
+  // { dg-message ".c1<T>. is unsatisfied because" "" { target *-*-* } .-1 }
+  // { dg-message ".c2<T>. is unsatisfied because" "" { target *-*-* } .-2 }
+  // { dg-message ".c3<T>. is unsatisfied because" "" { target *-*-* } .-3 }
+  // { dg-message ".c4<T>. is unsatisfied because" "" { target *-*-* } .-4 }
+  // { dg-message ".c5<T>. is unsatisfied because" "" { target *-*-* } .-5 }
+  void foo() { }
+
+void
+bar()
+{
+  foo<char>(); // { dg-error "use of" }
+}