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