[PATCH 1/2] c++: Replay errors during diagnosis of constraint satisfaction failures
Patrick Palka
ppalka@redhat.com
Wed Mar 11 22:18:08 GMT 2020
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" }
+}
--
2.26.0.rc1
More information about the Gcc-patches
mailing list