This is the mail archive of the gcc-patches@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

concept diagnostics


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);
 }
+

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]