From 50e0c6e429e7cc664f6fcea59db22826f005ca19 Mon Sep 17 00:00:00 2001 From: Andrew Sutton Date: Wed, 27 Nov 2019 15:16:37 +0000 Subject: [PATCH] re PR c++/92439 ([concepts] trunk crashes on constraint satisfaction failure) 2019-11-27 Andrew Sutton 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 | 27 ++ gcc/cp/parser.c | 276 +++++++++++------- gcc/testsuite/ChangeLog | 7 +- .../g++.dg/cpp2a/concepts-requires20.C | 65 +++++ 4 files changed, 275 insertions(+), 100 deletions(-) create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-requires20.C diff --git a/gcc/cp/ChangeLog b/gcc/cp/ChangeLog index 470a5271ded..36302e43aca 100644 --- a/gcc/cp/ChangeLog +++ b/gcc/cp/ChangeLog @@ -1,3 +1,30 @@ +2019-11-27 Andrew Sutton + + 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 PR c++/88395 diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c index d0c9ff01d9e..27318d3aeed 100644 --- a/gcc/cp/parser.c +++ b/gcc/cp/parser.c @@ -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 % 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 % 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 + struct S { + S() requires C = 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 - struct S { - S() requires C = default; - } + [] 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; } diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog index 8605b0b38fa..9b22c91ce69 100644 --- a/gcc/testsuite/ChangeLog +++ b/gcc/testsuite/ChangeLog @@ -1,4 +1,9 @@ -2019-11-18 Andrew Sutton +2019-11-27 Andrew Sutton + + PR c++/92439 + * g++.dg/cpp2a/concepts-requires20.C: New. + +2019-11-27 Andrew Sutton 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 index 00000000000..089db2ba013 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-requires20.C @@ -0,0 +1,65 @@ +// { dg-do compile { target c++2a } } + +template +constexpr bool r () { return true; } + +template + requires r() // { dg-error "enclose" } +void f() { } + +template + requires ++N // { dg-error "enclose" } +void f() { } + +template + requires N++ // { dg-error "enclose" } +void f() { } + +template + requires N == 0 // { dg-error "enclose" } +void f() { } + +template + requires N ? true : false // { dg-error "enclose" } +void f() { } + +template + requires N = 0 // { dg-error "enclose" } +void f() { } + +template + requires N + 1 // { dg-error "enclose" } +void f() { } + +template + requires N - 1 // { dg-error "enclose" } +void f() { } + +template + requires N.x // { dg-error "enclose" } +void f() { } + +template + requires N->x && true // { dg-error "enclose" } +void f() { } + +template + requires N && N +void f() { } + +template + requires N || N +void f() { } + +template + requires N || !N // { dg-error "enclose" } +void f() { } + +template + requires N[0] // { dg-error "enclose" } +void f() { } + +template + requires static_cast(N) // { dg-error "enclose" } +void f() { } + -- 2.30.2