This is not a proper patch. I'm missing the usual changelog and I'm still running the regression tests, but I wanted to get some opinions before committing more time to it.
This patch extends the diagnostics for concepts to report precise failures when constraints are not satisfied. It currently reports up to 7 errors and then elides the rest. That should probably be under control of a compiler option, but I'd like some suggestions on how to proceed. Also, diagnostics are currently emitted as notes against the location of the concept declaration. It would be better to diagnose the failure against location of each requirement, but we're not doing a very good job tracking source locations for those. Also, in a lot of cases, we probably just want to replay a substitution with tf_error to generate precise failures. Although that potentially generates *way* more information (e.g., candidate sets for failed overload resolution). I also started try to apply these diagnostics to static_if. The basic idea being: if you write static_if(C<T>, "") where C is a concept, then you should get the full diagnostics for that concept. I suspect that this will be the most requested feature within a few months time. Unfortunately, I ran into a little problem that C<T> is immediately folded into true/false and the original expression is unrecoverable from finish_static_if. I tinkered with parsing the condition as a non-constant expression and then folding it on demand, but that caused a number of regressions, so I had to back it out. Any thoughts on how to proceed? Andrew
Index: cxx-pretty-print.c =================================================================== --- cxx-pretty-print.c (revision 226937) +++ cxx-pretty-print.c (working copy) @@ -2600,6 +2600,7 @@ pp_cxx_compound_requirement (cxx_pretty_ pp_cxx_ws_string (pp, "->"); pp->type_id (type); } + pp_cxx_semicolon (pp); } /* nested requirement: @@ -2646,7 +2647,7 @@ pp_cxx_implicit_conversion_constraint (c pp_left_paren (pp); pp->expression (ICONV_CONSTR_EXPR (t)); pp_cxx_separate_with (pp, ','); - pp->expression (ICONV_CONSTR_TYPE (t)); + pp->type_id (ICONV_CONSTR_TYPE (t)); pp_right_paren (pp); } Index: cp-tree.h =================================================================== --- cp-tree.h (revision 226937) +++ cp-tree.h (working copy) @@ -6680,6 +6680,8 @@ extern tree tsubst_requires_expr extern tree tsubst_constraint (tree, tree, tsubst_flags_t, tree); extern tree tsubst_constraint_info (tree, tree, tsubst_flags_t, tree); extern bool function_concept_check_p (tree); +extern bool variable_concept_check_p (tree); +extern bool concept_check_p (tree); extern tree evaluate_constraints (tree, tree); extern tree evaluate_function_concept (tree, tree); @@ -6687,6 +6689,7 @@ extern tree evaluate_variable_concept extern tree evaluate_constraint_expression (tree, tree); extern bool constraints_satisfied_p (tree); extern bool constraints_satisfied_p (tree, tree); +extern bool constraint_expression_satisfied_p (tree, tree); extern bool equivalent_constraints (tree, tree); extern bool equivalently_constrained (tree, tree); Index: constraint.cc =================================================================== --- constraint.cc (revision 226937) +++ constraint.cc (working copy) @@ -32,6 +32,7 @@ along with GCC; see the file COPYING3. #include "wide-int.h" #include "inchash.h" #include "tree.h" +#include "print-tree.h" #include "stringpool.h" #include "attribs.h" #include "intl.h" @@ -113,13 +114,22 @@ conjoin_constraints (tree t) return r; } -/* Returns true if T is a call expression to a function - concept. */ +/* Returns true if T is an expression that would evaluate + a variable or function concept. */ + +bool +concept_check_p (tree t) +{ + return function_concept_check_p (t) || variable_concept_check_p (t); +} + +/* Returns true if T is a call to a function concept. */ bool function_concept_check_p (tree t) { - gcc_assert (TREE_CODE (t) == CALL_EXPR); + if (!t || t == error_mark_node || TREE_CODE (t) != CALL_EXPR) + return false; tree fn = CALL_EXPR_FN (t); if (TREE_CODE (fn) == TEMPLATE_ID_EXPR && TREE_CODE (TREE_OPERAND (fn, 0)) == OVERLOAD) @@ -132,6 +142,17 @@ function_concept_check_p (tree t) return false; } +/* Returns true if T is a template-id referring to a variable concept. */ + +bool +variable_concept_check_p (tree t) +{ + if (!t || t == error_mark_node || TREE_CODE (t) != TEMPLATE_ID_EXPR) + return false; + return variable_template_p (TREE_OPERAND (t, 0)); +} + + /*--------------------------------------------------------------------------- Resolution of qualified concept names ---------------------------------------------------------------------------*/ @@ -350,12 +371,12 @@ lift_function_call (tree t) return lift_operands (t); } -/* Inline a function (concept) definition by substituting - ARGS into its body. */ + +/* Return the body of a concept definition. */ + tree -lift_function_definition (tree fn, tree args) +get_return_expression(tree fn) { - /* Extract the body of the function minus the return expression. */ tree body = DECL_SAVED_TREE (fn); if (!body) return error_mark_node; @@ -363,11 +384,22 @@ lift_function_definition (tree fn, tree body = BIND_EXPR_BODY (body); if (TREE_CODE (body) != RETURN_EXPR) return error_mark_node; + return TREE_OPERAND (body, 0); +} - body = TREE_OPERAND (body, 0); + +/* Inline a function (concept) definition by substituting + ARGS into its body. */ + +tree +lift_function_definition (tree fn, tree args) +{ + tree epxr = get_return_expression (fn); + if (epxr == error_mark_node) + return epxr; /* Substitute template arguments to produce our inline expression. */ - tree result = tsubst_expr (body, args, tf_none, NULL_TREE, false); + tree result = tsubst_expr (epxr, args, tf_none, NULL_TREE, false); if (result == error_mark_node) return error_mark_node; @@ -527,7 +559,6 @@ check_logical_expr (tree t) /* Resolve the logical operator. Note that template processing is disabled so we get the actual call or target expression back. - not_processing_template_sentinel sentinel. TODO: This check is actually subsumed by the requirement that constraint operands have type bool. I'm not sure we need it @@ -1775,7 +1806,7 @@ satisfy_implicit_conversion_constraint ( of the form TYPE <unspecified> = EXPR. */ tree conv = perform_direct_initialization_if_possible (type, expr, false, complain); - if (conv == error_mark_node) + if (!conv || conv == error_mark_node) return boolean_false_node; else return boolean_true_node; @@ -2072,22 +2103,17 @@ constraints_satisfied_p (tree t, tree ar return eval == boolean_true_node; } -namespace -{ - /* Normalize EXPR and determine if the resulting constraint is satisfied by ARGS. Returns true if and only if the constraint is satisfied. This is used extensively by diagnostics to determine causes for failure. */ -inline bool +bool constraint_expression_satisfied_p (tree expr, tree args) { return evaluate_constraint_expression (expr, args) == boolean_true_node; } -} /* namespace */ - /*--------------------------------------------------------------------------- Semantic analysis of requires-expressions @@ -2288,27 +2314,66 @@ at_least_as_constrained (tree d1, tree d namespace { +/* The nesting depth of constraint diagnostics. Each time + we investigate a concept, this is incremented. */ + +int constraint_depth = 0; + +/* The number of detailed constraint failures. */ + +int constraint_errors = 0; + +/* Do not generate errors after diagnosing this number + of constraint failures. */ + +int constraint_thresh = 7; + + +/* Returns true if we should elide the diagnostic for + a constraint failure. This is the case when then + number of errors has exceeded the pre-configured + threshold. */ + +inline bool +elide_constraint_failure_p () +{ + ++constraint_errors; + return constraint_thresh <= constraint_errors; +} + +/* Returns the number of undiagnosed errors. */ + +inline int +undiagnosed_constraint_failures () +{ + return constraint_errors - constraint_thresh; +} + void diagnose_expression (location_t, tree, tree); void diagnose_constraint (location_t, tree, tree); -/* Diagnose a conjunction of constraints. */ +/* Diagnose a conjunction of constraints. */ + void diagnose_logical_operation (location_t loc, tree t, tree args) { diagnose_expression (loc, TREE_OPERAND (t, 0), args); - diagnose_expression (loc, TREE_OPERAND (t, 0), args); + diagnose_expression (loc, TREE_OPERAND (t, 1), args); } /* Determine if the trait expression T is satisfied by ARGS. - Emit a precise diagnostic if it is not. */ + Emit a precise diagnostic if it is not. */ + void diagnose_trait_expression (location_t loc, tree t, tree args) { if (constraint_expression_satisfied_p (t, args)) return; + if (elide_constraint_failure_p()) + return; /* Rebuild the trait expression so we can diagnose the - specific failure. */ + specific failure. */ ++processing_template_decl; tree expr = tsubst_expr (t, args, tf_none, NULL_TREE, false); --processing_template_decl; @@ -2385,14 +2450,57 @@ diagnose_trait_expression (location_t lo } } + +/* When a function concept T is not satisfied, recurse into its + body and provide very specific diagnostics. Note that T must + be a call expression. */ +void +diagnose_function_concept_check (location_t loc, tree t) +{ + tree cands = resolve_constraint_check (t); + if (!cands) + { + inform (loc, "could not resolve reference to concept"); + } + else + { + tree fn = TREE_VALUE (cands); + tree tmpl = DECL_TI_TEMPLATE (fn); + tree args = TREE_PURPOSE (cands); + + /* Emit the context in which the failure happens. + Reset the error location, so errors are relative + to the concept in which they appear. */ + location_t errloc = DECL_SOURCE_LOCATION (tmpl); + tree cxt = build_tree_list (tmpl, args); + inform (errloc, "within the concept %S", cxt); + tree expr = get_return_expression (fn); + diagnose_expression (errloc, expr, args); + } +} + + +/* When a variable concept T is not satisfied, recurse + into its body and provide very, very specific diagnostics. + Note that T must be a template-id whose arguments have + been substituted. */ + +void +diagnose_variable_concept_check (location_t, tree t) +{ + tree tmpl = TREE_OPERAND (t, 0); + tree args = TREE_OPERAND (t, 1); + location_t errloc = DECL_SOURCE_LOCATION (tmpl); + tree cxt = build_tree_list (tmpl, args); + inform (errloc, "within the concept %S", cxt); + tree expr = DECL_INITIAL (DECL_TEMPLATE_RESULT (tmpl)); + diagnose_expression (errloc, expr, args); +} + + /* Determine if the call expression T, when normalized as a constraint, - is satisfied by ARGS. + is satisfied by ARGS. */ - TODO: If T is refers to a concept, We could recursively analyze - its definition to identify the exact failure, but that could - emit a *lot* of error messages (defeating the purpose of - improved diagnostics). Consider adding a flag to control the - depth of diagnostics. */ void diagnose_call_expression (location_t loc, tree t, tree args) { @@ -2404,16 +2512,20 @@ diagnose_call_expression (location_t loc tree expr = tsubst_expr (t, args, tf_none, NULL_TREE, false); --processing_template_decl; - /* If the function call is known to be a concept check, then - diagnose it differently (i.e., we may recurse). */ - if (resolve_constraint_check (t)) - inform (loc, " concept %qE was not satisfied", expr); + /* If the function call is known to be a concept check, recurse. */ + if (resolve_constraint_check (t)) + { + inform (loc, " concept %qE was not satisfied", expr); + diagnose_function_concept_check (loc, expr); + } else - inform (loc, " %qE evaluated to false", expr); + inform (EXPR_LOC_OR_LOC(expr, loc), " the predicate %qE evaluated to false", expr); } + /* Determine if the template-id T, when normalized as a constraint is satisfied by ARGS. */ + void diagnose_template_id (location_t loc, tree t, tree args) { @@ -2434,23 +2546,60 @@ diagnose_template_id (location_t loc, tr tree var = DECL_TEMPLATE_RESULT (TREE_OPERAND (t, 0)); if (DECL_DECLARED_CONCEPT_P (var)) - inform (loc, " concept %qE was not satisfied", expr); + { + inform (loc, " concept %qE was not satisfied", expr); + diagnose_variable_concept_check (loc, expr); + } else inform (loc, " %qE evaluated to false", expr); } + +/* Diagnose a requirement. */ + +void +diagnose_requirement (location_t loc, tree t, tree args) +{ + /* Don't allow nested references to concepts to be elided + by a nested requirement. */ + if (TREE_CODE (t) == NESTED_REQ) + diagnose_expression (loc, TREE_OPERAND (t, 0), args); + else + diagnose_constraint (loc, xform_requirement (t), args); +} + + /* Determine if the requires-expression, when normalized as a constraint is satisfied by ARGS. TODO: Build sets of expressions, types, and constraints based on the requirements in T and emit specific diagnostics for those. */ + void diagnose_requires_expression (location_t loc, tree t, tree args) { + /* Declare constraint variables before trying to diagnose + constraints. */ + local_specialization_stack stack; + if (tree parms = TREE_OPERAND (t, 0)) + { + parms = tsubst_constraint_variables (parms, args, tf_none, NULL_TREE); + if (error_operand_p (parms)) + { + inform (loc, "error substituting into constraint variables"); + return; + } + } + if (constraint_expression_satisfied_p (t, args)) return; - inform (loc, "requirements not satisfied"); + + tree req = TREE_OPERAND (t, 1); + while (req) { + diagnose_requirement (loc, TREE_VALUE (req), args); + req = TREE_CHAIN (req); + } } void @@ -2459,6 +2608,10 @@ diagnose_pack_expansion (location_t loc, if (constraint_expression_satisfied_p (t, args)) return; + /* Treat failures occurring here as a single error. */ + if (elide_constraint_failure_p()) + return; + /* Make sure that we don't have naked packs that we don't expect. */ if (!same_type_p (TREE_TYPE (t), boolean_type_node)) { @@ -2497,6 +2650,8 @@ diagnose_other_expression (location_t lo { if (constraint_expression_satisfied_p (t, args)) return; + if (elide_constraint_failure_p()) + return; inform (loc, " %qE evaluated to false", t); } @@ -2540,18 +2695,113 @@ diagnose_expression (location_t loc, tre } inline void -diagnose_predicate_constraint (location_t loc, tree t, tree args) +diagnose_conjunction (location_t loc, tree t, tree args) { - diagnose_expression (loc, PRED_CONSTR_EXPR (t), args); + diagnose_constraint (loc, TREE_OPERAND (t, 0), args); + diagnose_constraint (loc, TREE_OPERAND (t, 1), args); } +/* Diagnose a constraint failure. */ + +void +diagnose_expression_constraint (location_t loc, tree t, tree args) +{ + if (constraints_satisfied_p (t, args)) + return; + if (elide_constraint_failure_p()) + return; + + tree expr = EXPR_CONSTR_EXPR (t); + inform (loc, " the required expresion %qE would be ill-formed", expr); +} + + +/* Diagnose a potentially failed type constraint. */ + +void +diagnose_type_constraint (location_t loc, tree t, tree args) +{ + if (constraints_satisfied_p (t, args)) + return; + if (elide_constraint_failure_p()) + return; + + /* Note the failed requirement. */ + inform (loc, " the required type %qT would be ill-formed", TREE_OPERAND (t, 0)); +} + +/* Diagnose a potentially unsatisfied conversion constraint. */ + +void +diagnose_implicit_conversion_constraint (location_t loc, tree t, tree args) +{ + if (constraints_satisfied_p (t, args)) + return; + if (elide_constraint_failure_p()) + return; + + /* Substitute through the expression and type to get the final + versions of each used to check the constraint. If either + fail, then don't emit a diagnostic (their failures will + already have been diagnosed). */ + tree expr = tsubst_expr (ICONV_CONSTR_EXPR (t), args, tf_none, NULL_TREE, false); + if (error_operand_p (expr)) + return; + tree type = tsubst (ICONV_CONSTR_TYPE (t), args, tf_none, NULL_TREE); + if (error_operand_p (expr)) + return; + + inform(loc, " %qE is not implicitly convertible to %qT", expr, type); +} + +/* Diagnose an argument deduction constraint. */ + +void +diagnose_argument_deduction_constraint (location_t loc, tree t, tree args) +{ + if (constraints_satisfied_p (t, args)) + return; + if (elide_constraint_failure_p()) + return; + + /* Get the instantiated expression. If this fails, then we + would have already emitted a diagnostic so no further work + is required. */ + tree expr = tsubst_expr (DEDUCT_CONSTR_EXPR (t), args, tf_none, NULL_TREE, false); + if (error_operand_p (expr)) + return; + tree pattern = DEDUCT_CONSTR_PATTERN (t); + + inform (loc, " unable to deduce for %qT from %qE", pattern, expr); +} + +/* Diagnose an exception constraint. */ + +void +diagnose_exception_constraint (location_t loc, tree t, tree args) +{ + if (constraints_satisfied_p (t, args)) + return; + if (elide_constraint_failure_p()) + return; + + /* Rebuild a noexcept expression. */ + tree expr = tsubst_expr (EXCEPT_CONSTR_EXPR (t), args, tf_none, NULL_TREE, false); + if (error_operand_p(expr)) + return; + expr = build_min(NOEXCEPT_EXPR, boolean_type_node, expr); + + inform (loc, " %qE evaluated to false", expr); +} + + inline void -diagnose_conjunction (location_t loc, tree t, tree args) +diagnose_predicate_constraint (location_t loc, tree t, tree args) { - diagnose_constraint (loc, TREE_OPERAND (t, 0), args); - diagnose_constraint (loc, TREE_OPERAND (t, 1), args); + diagnose_expression (loc, PRED_CONSTR_EXPR (t), args); } + /* Diagnose the constraint T for the given ARGS. This is only ever invoked on the associated constraints, so we can only have conjunctions of predicate constraints. */ @@ -2564,6 +2814,26 @@ diagnose_constraint (location_t loc, tre diagnose_conjunction (loc, t, args); break; + case EXPR_CONSTR: + diagnose_expression_constraint (loc, t, args); + break; + + case TYPE_CONSTR: + diagnose_type_constraint (loc, t, args); + break; + + case ICONV_CONSTR: + diagnose_implicit_conversion_constraint (loc, t, args); + break; + + case DEDUCT_CONSTR: + diagnose_argument_deduction_constraint (loc, t, args); + break; + + case EXCEPT_CONSTR: + diagnose_exception_constraint (loc, t, args); + break; + case PRED_CONSTR: diagnose_predicate_constraint (loc, t, args); break; @@ -2611,8 +2881,18 @@ diagnose_declaration_constraints (locati void diagnose_constraints (location_t loc, tree t, tree args) { + constraint_errors = 0; + if (constraint_p (t)) diagnose_constraint (loc, t, args); - else + else if (DECL_P (t)) diagnose_declaration_constraints (loc, t, args); + else if (EXPR_P (t)) + diagnose_expression (loc, t, args); + + /* Note the number of elided failures. */ + int n = undiagnosed_constraint_failures (); + if (n > 0) + inform (loc, "... %d constraint errors not shown", n); } +