re PR c++/92439 ([concepts] trunk crashes on constraint satisfaction failure)
authorAndrew Sutton <asutton@lock3software.com>
Wed, 27 Nov 2019 15:16:37 +0000 (15:16 +0000)
committerAndrew Sutton <asutton@gcc.gnu.org>
Wed, 27 Nov 2019 15:16:37 +0000 (15:16 +0000)
2019-11-27  Andrew Sutton  <asutton@lock3software.com>

PR c++/92439
Improve quality of diagnostics for subexpressions that need parens.

gcc/cp/
* parser.c (cp_parser_requires_clause_opt): Add a flag to indicate
when parsing a requires-clause before lambda parameters, and...
(cp_parser_lambda_declarator_opt): ... use that here ...
(cp_parser_type_parameter): ... and here ...
(cp_parser_late_return_type_opt): ... and here ...
(cp_parser_explicit_template_declaration): ... and here.
(cp_parser_diagnose_ungrouped_constraint_plain): Adjust the message
because this can apply to subexpressions that are not immediately
after a requires-clause.
(cp_parser_diagnose_ungrouped_constraint_rich): Likewise.
(primary_constraint_error): New.
(cp_parser_constraint_requires_parens): New.
(cp_parser_unary_constraint_requires_parens): New.
(cp_parser_constraint_primary_expression): Check for unary expressions
before parsing the primary expression. Also check for binary and
postfix operators after a successful parse of the primary expression.
Force a re-parse if the result would form a lower-precedence string.
(cp_parser_constraint_logical_and_expression): Propagate lambda flag;
move checks for ill-formed constraints into the constraint primary
expression.
(cp_parser_constraint_logical_or_expression): Likewise.
(cp_parser_requires_clause_expression): Propagate lambda flag.

gcc/testsuite/
* g++.dg/cpp2a/concepts-requires20.C: New.

From-SVN: r278774

gcc/cp/ChangeLog
gcc/cp/parser.c
gcc/testsuite/ChangeLog
gcc/testsuite/g++.dg/cpp2a/concepts-requires20.C [new file with mode: 0644]

index 470a5271ded85945291fdc26d13b6be2c57516b0..36302e43aca7995645a621e836a5a352365cde49 100644 (file)
@@ -1,3 +1,30 @@
+2019-11-27  Andrew Sutton  <asutton@lock3software.com>
+
+       PR c++/92439
+       Improve quality of diagnostics for subexpressions that need parens.
+       * parser.c (cp_parser_requires_clause_opt): Add a flag to indicate
+       when parsing a requires-clause before lambda parameters, and...
+       (cp_parser_lambda_declarator_opt): ... use that here ...
+       (cp_parser_type_parameter): ... and here ...
+       (cp_parser_late_return_type_opt): ... and here ...
+       (cp_parser_explicit_template_declaration): ... and here.
+       (cp_parser_diagnose_ungrouped_constraint_plain): Adjust the message
+       because this can apply to subexpressions that are not immediately
+       after a requires-clause.
+       (cp_parser_diagnose_ungrouped_constraint_rich): Likewise.
+       (primary_constraint_error): New.
+       (cp_parser_constraint_requires_parens): New.
+       (cp_parser_unary_constraint_requires_parens): New.
+       (cp_parser_constraint_primary_expression): Check for unary expressions
+       before parsing the primary expression. Also check for binary and
+       postfix operators after a successful parse of the primary expression.
+       Force a re-parse if the result would form a lower-precedence string.
+       (cp_parser_constraint_logical_and_expression): Propagate lambda flag;
+       move checks for ill-formed constraints into the constraint primary
+       expression.
+       (cp_parser_constraint_logical_or_expression): Likewise.
+       (cp_parser_requires_clause_expression): Propagate lambda flag.
+
 2019-11-27  Andrew Sutton  <asutton@lock3software.com>
 
        PR c++/88395
index d0c9ff01d9ee30aaf11f9c79f80e777e9884904b..27318d3aeed9c321950809354306e8f56d13911f 100644 (file)
@@ -2440,7 +2440,7 @@ static tree cp_parser_concept_definition
 static tree cp_parser_constraint_expression
   (cp_parser *);
 static tree cp_parser_requires_clause_opt
-  (cp_parser *);
+  (cp_parser *, bool);
 static tree cp_parser_requires_expression
   (cp_parser *);
 static tree cp_parser_requirement_parameter_list
@@ -10897,7 +10897,7 @@ cp_parser_lambda_declarator_opt (cp_parser* parser, tree lambda_expr)
       /* We may have a constrained generic lambda; parse the requires-clause
         immediately after the template-parameter-list and combine with any
         shorthand constraints present.  */
-      tree dreqs = cp_parser_requires_clause_opt (parser);
+      tree dreqs = cp_parser_requires_clause_opt (parser, true);
       if (flag_concepts)
        {
          tree reqs = get_shorthand_constraints (current_template_parms);
@@ -10990,7 +10990,7 @@ cp_parser_lambda_declarator_opt (cp_parser* parser, tree lambda_expr)
        gnu_attrs = cp_parser_gnu_attributes_opt (parser);
 
       /* Parse optional trailing requires clause.  */
-      trailing_requires_clause = cp_parser_requires_clause_opt (parser);
+      trailing_requires_clause = cp_parser_requires_clause_opt (parser, false);
 
       /* The function parameters must be in scope all the way until after the
          trailing-return-type in case of decltype.  */
@@ -16328,11 +16328,11 @@ cp_parser_type_parameter (cp_parser* parser, bool *is_parameter_pack)
        /* Look for the `>'.  */
        cp_parser_require (parser, CPP_GREATER, RT_GREATER);
 
-        // If template requirements are present, parse them.
+       /* If template requirements are present, parse them.  */
        if (flag_concepts)
           {
            tree reqs = get_shorthand_constraints (current_template_parms);
-           if (tree dreqs = cp_parser_requires_clause_opt (parser))
+           if (tree dreqs = cp_parser_requires_clause_opt (parser, false))
               reqs = combine_constraint_expressions (reqs, dreqs);
            TEMPLATE_PARMS_CONSTRAINTS (current_template_parms) = reqs;
           }
@@ -21940,7 +21940,7 @@ cp_parser_late_return_type_opt (cp_parser* parser, cp_declarator *declarator,
 
   /* Function declarations may be followed by a trailing
      requires-clause.  */
-  requires_clause = cp_parser_requires_clause_opt (parser);
+  requires_clause = cp_parser_requires_clause_opt (parser, false);
 
   if (declare_simd_p)
     declarator->attributes
@@ -27122,8 +27122,7 @@ cp_parser_concept_definition (cp_parser *parser)
 static void
 cp_parser_diagnose_ungrouped_constraint_plain (location_t loc)
 {
-  error_at (loc, "expression after %<requires%> must be enclosed "
-                "in parentheses");
+  error_at (loc, "expression must be enclosed in parentheses");
 }
 
 static void
@@ -27132,56 +27131,48 @@ cp_parser_diagnose_ungrouped_constraint_rich (location_t loc)
   gcc_rich_location richloc (loc);
   richloc.add_fixit_insert_before ("(");
   richloc.add_fixit_insert_after (")");
-  error_at (&richloc, "expression after %<requires%> must be enclosed "
-                     "in parentheses");
+  error_at (&richloc, "expression must be enclosed in parentheses");
 }
 
-/* Parse a primary expression within a constraint.  */
-
-static cp_expr
-cp_parser_constraint_primary_expression (cp_parser *parser)
+/* Characterizes the likely kind of expression intended by a mis-written
+   primary constraint.  */
+enum primary_constraint_error
 {
-  cp_parser_parse_tentatively (parser);
-  cp_id_kind idk;
-  location_t loc = input_location;
-  cp_expr expr = cp_parser_primary_expression (parser,
-                                              /*address_p=*/false,
-                                              /*cast_p=*/false,
-                                              /*template_arg_p=*/false,
-                                              &idk);
-  expr.maybe_add_location_wrapper ();
-  if (expr != error_mark_node)
-    expr = finish_constraint_primary_expr (expr);
-  if (cp_parser_parse_definitely (parser))
-    return expr;
-
-  /* Retry the parse at a lower precedence. If that succeeds, diagnose the
-     error, but return the expression as if it were valid.  */
-  cp_parser_parse_tentatively (parser);
-  expr = cp_parser_simple_cast_expression (parser);
-  if (cp_parser_parse_definitely (parser))
-    {
-      cp_parser_diagnose_ungrouped_constraint_rich (expr.get_location());
-      return expr;
-    }
-
-  /* Otherwise, something has gone wrong, but we can't generate a more
-     meaningful diagnostic or recover.  */
-  cp_parser_diagnose_ungrouped_constraint_plain (loc);
-  return error_mark_node;
-}
+  pce_ok,
+  pce_maybe_operator,
+  pce_maybe_postfix,
+};
 
-/* Examine the token following EXPR. If it is an operator in a non-logical
-   binary expression, diagnose that as an error. Returns ERROR_MARK_NODE.  */
+/* Returns true if the token(s) following a primary-expression in a
+   constraint-logical-* expression would require parentheses.  */
 
-static cp_expr
-cp_parser_check_non_logical_constraint (cp_parser *parser, cp_expr lhs)
+static primary_constraint_error
+cp_parser_constraint_requires_parens (cp_parser *parser, bool lambda_p)
 {
   cp_token *token = cp_lexer_peek_token (parser->lexer);
   switch (token->type)
     {
       default:
-        return lhs;
+       return pce_ok;
+
+      case CPP_EQ:
+       {
+         /* An equal sign may be part of the the definition of a function,
+            and not an assignment operator, when parsing the expression
+            for a trailing requires-clause. For example:
+
+               template<typename T>
+               struct S {
+                 S() requires C<T> = default;
+               };
+
+            Don't try to reparse this a binary operator.  */
+         if (cp_lexer_nth_token_is_keyword (parser->lexer, 2, RID_DELETE)
+             || cp_lexer_nth_token_is_keyword (parser->lexer, 2, RID_DEFAULT))
+           return pce_ok;
+
+         gcc_fallthrough ();
+       }
 
       /* Arithmetic operators.  */
       case CPP_PLUS:
@@ -27196,15 +27187,13 @@ cp_parser_check_non_logical_constraint (cp_parser *parser, cp_expr lhs)
       case CPP_RSHIFT:
       case CPP_LSHIFT:
       /* Relational operators.  */
-      /* FIXME: Handle '<=>'.  */
       case CPP_EQ_EQ:
       case CPP_NOT_EQ:
       case CPP_LESS:
       case CPP_GREATER:
       case CPP_LESS_EQ:
       case CPP_GREATER_EQ:
-      /* Conditional operator */
-      case CPP_QUERY:
+      case CPP_SPACESHIP:
       /* Pointer-to-member.  */
       case CPP_DOT_STAR:
       case CPP_DEREF_STAR:
@@ -27219,52 +27208,138 @@ cp_parser_check_non_logical_constraint (cp_parser *parser, cp_expr lhs)
       case CPP_XOR_EQ:
       case CPP_RSHIFT_EQ:
       case CPP_LSHIFT_EQ:
-        break;
+      /* Conditional operator */
+      case CPP_QUERY:
+       /* Unenclosed binary or conditional operator.  */
+       return pce_maybe_operator;
 
-      case CPP_EQ: {
-       /* An equal sign may be part of the the definition of a function,
-          and not an assignment operator, when parsing the expression
-          for a trailing requires-clause. For example:
+      case CPP_OPEN_PAREN:
+       {
+         /* A primary constraint that precedes the parameter-list of a
+            lambda expression is followed by an open paren.
 
-             template<typename T>
-             struct S {
-               S() requires C<T> = default;
-             }
+               []<typename T> requires C (T a, T b) { ... }
 
-          This is not an error.  */
-       if (cp_lexer_nth_token_is_keyword (parser->lexer, 2, RID_DELETE)
-           || cp_lexer_nth_token_is_keyword (parser->lexer, 2, RID_DEFAULT))
-         return lhs;
+            Don't try to re-parse this as a postfix expression.  */
+         if (lambda_p)
+           return pce_ok;
 
-        break;
-      }
+         gcc_fallthrough ();
+       }
+      case CPP_OPEN_SQUARE:
+      case CPP_PLUS_PLUS:
+      case CPP_MINUS_MINUS:
+      case CPP_DOT:
+      case CPP_DEREF:
+       /* Unenclosed postfix operator.  */
+       return pce_maybe_postfix;
    }
+}
+
+/* Returns true if the next token begins a unary expression, preceded by
+   an operator or keyword.  */
+
+static bool
+cp_parser_unary_constraint_requires_parens (cp_parser *parser)
+{
+  cp_token *token = cp_lexer_peek_token (parser->lexer);
+  switch (token->type)
+    {
+      case CPP_NOT:
+      case CPP_PLUS:
+      case CPP_MINUS:
+      case CPP_MULT:
+      case CPP_COMPL:
+      case CPP_PLUS_PLUS:
+      case CPP_MINUS_MINUS:
+       return true;
+
+      case CPP_KEYWORD:
+       {
+         switch (token->keyword)
+           {
+             case RID_STATCAST:
+             case RID_DYNCAST:
+             case RID_REINTCAST:
+             case RID_CONSTCAST:
+             case RID_TYPEID:
+             case RID_SIZEOF:
+             case RID_ALIGNOF:
+             case RID_NOEXCEPT:
+             case RID_NEW:
+             case RID_DELETE:
+             case RID_THROW:
+               return true;
+
+            default:
+               break;
+         }
+       }
+
+      default:
+       break;
+    }
+
+  return false;
+}
+
+/* Parse a primary expression within a constraint.  */
+
+static cp_expr
+cp_parser_constraint_primary_expression (cp_parser *parser, bool lambda_p)
+{
+  /* If this looks like a unary expression, parse it as such, but diagnose
+     it as ill-formed; it requires parens.  */
+  if (cp_parser_unary_constraint_requires_parens (parser))
+    {
+      cp_expr e = cp_parser_assignment_expression (parser, NULL, false, false);
+      cp_parser_diagnose_ungrouped_constraint_rich (e.get_location());
+      return e;
+    }
 
-   /* Try to parse the RHS as either the remainder of a conditional-expression
-      or a logical-or-expression so we can form a good diagnostic.  */
   cp_parser_parse_tentatively (parser);
-  cp_expr rhs;
-  if (token->type == CPP_QUERY)
-    rhs = cp_parser_question_colon_clause (parser, lhs);
-  else
+  cp_id_kind idk;
+  location_t loc = input_location;
+  cp_expr expr = cp_parser_primary_expression (parser,
+                                              /*address_p=*/false,
+                                              /*cast_p=*/false,
+                                              /*template_arg_p=*/false,
+                                              &idk);
+  expr.maybe_add_location_wrapper ();
+
+  primary_constraint_error pce = pce_ok;
+  if (expr != error_mark_node)
     {
-      cp_lexer_consume_token (parser->lexer);
-      rhs = cp_parser_binary_expression (parser, false, false, false,
-                                        PREC_NOT_OPERATOR, NULL);
+      /* The primary-expression could be part of an unenclosed non-logical
+        compound expression.  */
+      pce = cp_parser_constraint_requires_parens (parser, lambda_p);
+      if (pce != pce_ok)
+       cp_parser_simulate_error (parser);
+      else
+       expr = finish_constraint_primary_expr (expr);
     }
+  if (cp_parser_parse_definitely (parser))
+    return expr;
+  if (expr == error_mark_node)
+    return error_mark_node;
 
-  /* If we couldn't parse the RHS, then emit the best diagnostic we can.  */
-  if (!cp_parser_parse_definitely (parser))
+  /* Retry the parse at a lower precedence. If that succeeds, diagnose the
+     error, but return the expression as if it were valid.  */
+  gcc_assert (pce != pce_ok);
+  cp_parser_parse_tentatively (parser);
+  if (pce == pce_maybe_operator)
+    expr = cp_parser_assignment_expression (parser, NULL, false, false);
+  else
+    expr = cp_parser_simple_cast_expression (parser);
+  if (cp_parser_parse_definitely (parser))
     {
-      cp_parser_diagnose_ungrouped_constraint_plain (token->location);
-      return error_mark_node;
+      cp_parser_diagnose_ungrouped_constraint_rich (expr.get_location());
+      return expr;
     }
 
-  /* Otherwise, emit a fixit for the complete binary expression.  */
-  location_t loc = make_location (token->location,
-                                 lhs.get_start(),
-                                 rhs.get_finish());
-  cp_parser_diagnose_ungrouped_constraint_rich (loc);
+  /* Otherwise, something has gone very wrong, and we can't generate a more
+     meaningful diagnostic or recover.  */
+  cp_parser_diagnose_ungrouped_constraint_plain (loc);
   return error_mark_node;
 }
 
@@ -27275,16 +27350,16 @@ cp_parser_check_non_logical_constraint (cp_parser *parser, cp_expr lhs)
        constraint-logical-and-expression '&&' primary-expression  */
 
 static cp_expr
-cp_parser_constraint_logical_and_expression (cp_parser *parser)
+cp_parser_constraint_logical_and_expression (cp_parser *parser, bool lambda_p)
 {
-  cp_expr lhs = cp_parser_constraint_primary_expression (parser);
+  cp_expr lhs = cp_parser_constraint_primary_expression (parser, lambda_p);
   while (cp_lexer_next_token_is (parser->lexer, CPP_AND_AND))
     {
       cp_token *op = cp_lexer_consume_token (parser->lexer);
-      tree rhs = cp_parser_constraint_primary_expression (parser);
+      tree rhs = cp_parser_constraint_primary_expression (parser, lambda_p);
       lhs = finish_constraint_and_expr (op->location, lhs, rhs);
     }
-  return cp_parser_check_non_logical_constraint (parser, lhs);
+  return lhs;
 }
 
 /* Parse a constraint-logical-or-expression.
@@ -27294,27 +27369,27 @@ cp_parser_constraint_logical_and_expression (cp_parser *parser)
        constraint-logical-or-expression '||' constraint-logical-and-expression  */
 
 static cp_expr
-cp_parser_constraint_logical_or_expression (cp_parser *parser)
+cp_parser_constraint_logical_or_expression (cp_parser *parser, bool lambda_p)
 {
-  cp_expr lhs = cp_parser_constraint_logical_and_expression (parser);
+  cp_expr lhs = cp_parser_constraint_logical_and_expression (parser, lambda_p);
   while (cp_lexer_next_token_is (parser->lexer, CPP_OR_OR))
     {
       cp_token *op = cp_lexer_consume_token (parser->lexer);
-      cp_expr rhs = cp_parser_constraint_logical_and_expression (parser);
+      cp_expr rhs = cp_parser_constraint_logical_and_expression (parser, lambda_p);
       lhs = finish_constraint_or_expr (op->location, lhs, rhs);
     }
-  return cp_parser_check_non_logical_constraint (parser, lhs);
+  return lhs;
 }
 
 /* Parse the expression after a requires-clause. This has a different grammar
     than that in the concepts TS.  */
 
 static tree
-cp_parser_requires_clause_expression (cp_parser *parser)
+cp_parser_requires_clause_expression (cp_parser *parser, bool lambda_p)
 {
   processing_constraint_expression_sentinel parsing_constraint;
   ++processing_template_decl;
-  cp_expr expr = cp_parser_constraint_logical_or_expression (parser);
+  cp_expr expr = cp_parser_constraint_logical_or_expression (parser, lambda_p);
   if (check_for_bare_parameter_packs (expr))
     expr = error_mark_node;
   --processing_template_decl;
@@ -27347,12 +27422,15 @@ cp_parser_constraint_expression (cp_parser *parser)
 /* Optionally parse a requires clause:
 
       requires-clause:
-        `requires` constraint-logical-or-expression.
+       `requires` constraint-logical-or-expression.
    [ConceptsTS]
-        `requires constraint-expression.  */
+       `requires constraint-expression.
+
+   LAMBDA_P is true when the requires-clause is parsed before the
+   parameter-list of a lambda-declarator.  */
 
 static tree
-cp_parser_requires_clause_opt (cp_parser *parser)
+cp_parser_requires_clause_opt (cp_parser *parser, bool lambda_p)
 {
   cp_token *tok = cp_lexer_peek_token (parser->lexer);
   if (tok->keyword != RID_REQUIRES)
@@ -27372,7 +27450,7 @@ cp_parser_requires_clause_opt (cp_parser *parser)
   cp_lexer_consume_token (parser->lexer);
 
   if (!flag_concepts_ts)
-    return cp_parser_requires_clause_expression (parser);
+    return cp_parser_requires_clause_expression (parser, lambda_p);
   else
     return cp_parser_constraint_expression (parser);
 }
@@ -28927,7 +29005,7 @@ cp_parser_explicit_template_declaration (cp_parser* parser, bool member_p)
   if (flag_concepts)
   {
     tree reqs = get_shorthand_constraints (current_template_parms);
-    if (tree treqs = cp_parser_requires_clause_opt (parser))
+    if (tree treqs = cp_parser_requires_clause_opt (parser, false))
       reqs = combine_constraint_expressions (reqs, treqs);
     TEMPLATE_PARMS_CONSTRAINTS (current_template_parms) = reqs;
   }
index 8605b0b38fad6a64b815cab04f2ddf050e5d08ba..9b22c91ce69bf1d752e58f9419fd26ad9ca03f3e 100644 (file)
@@ -1,4 +1,9 @@
-2019-11-18  Andrew Sutton  <asutton@lock3software.com>
+2019-11-27  Andrew Sutton  <asutton@lock3software.com>
+
+       PR c++/92439
+       * g++.dg/cpp2a/concepts-requires20.C: New.
+
+2019-11-27  Andrew Sutton  <asutton@lock3software.com>
 
        PR c++/88395
        * g++.dg/cpp2a/concepts-pr88395.C: New.
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-requires20.C b/gcc/testsuite/g++.dg/cpp2a/concepts-requires20.C
new file mode 100644 (file)
index 0000000..089db2b
--- /dev/null
@@ -0,0 +1,65 @@
+// { dg-do compile { target c++2a } }
+
+template<typename ...>
+constexpr bool r () { return true; }
+
+template<typename ... Ts>
+  requires r<Ts...>() // { dg-error "enclose" }
+void f() { }
+
+template<typename T, T N>
+  requires ++N // { dg-error "enclose" }
+void f() { }
+
+template<typename T, T N>
+  requires N++ // { dg-error "enclose" }
+void f() { }
+
+template<typename T, T N>
+  requires N == 0 // { dg-error "enclose" }
+void f() { }
+
+template<typename T, T N>
+  requires N ? true : false // { dg-error "enclose" }
+void f() { }
+
+template<typename T, T N>
+  requires N = 0 // { dg-error "enclose" }
+void f() { }
+
+template<typename T, T N>
+  requires N + 1 // { dg-error "enclose" }
+void f() { }
+
+template<typename T, T N>
+  requires N - 1 // { dg-error "enclose" }
+void f() { }
+
+template<typename T, T N>
+  requires N.x // { dg-error "enclose" }
+void f() { }
+
+template<typename T, T N>
+  requires N->x && true // { dg-error "enclose" }
+void f() { }
+
+template<typename T, T N>
+  requires N && N
+void f() { }
+
+template<typename T, T N>
+  requires N || N
+void f() { }
+
+template<typename T, T N>
+  requires N || !N // { dg-error "enclose" }
+void f() { }
+
+template<typename T, T N>
+  requires N[0] // { dg-error "enclose" }
+void f() { }
+
+template<typename T, T N>
+  requires static_cast<bool>(N) // { dg-error "enclose" }
+void f() { }
+