diff --git a/prism/diagnostic.c b/prism/diagnostic.c index 473960e361..1161dec6be 100644 --- a/prism/diagnostic.c +++ b/prism/diagnostic.c @@ -232,6 +232,10 @@ static const char* const diagnostic_messages[PM_DIAGNOSTIC_ID_LEN] = { [PM_ERR_RESCUE_TERM] = "Expected a closing delimiter for the `rescue` clause", [PM_ERR_RESCUE_VARIABLE] = "Expected an exception variable after `=>` in a rescue statement", [PM_ERR_RETURN_INVALID] = "Invalid `return` in a class or module body", + [PM_ERR_STATEMENT_ALIAS] = "Unexpected an `alias` at a non-statement position", + [PM_ERR_STATEMENT_POSTEXE_END] = "Unexpected an `END` at a non-statement position", + [PM_ERR_STATEMENT_PREEXE_BEGIN] = "Unexpected a `BEGIN` at a non-statement position", + [PM_ERR_STATEMENT_UNDEF] = "Unexpected an `undef` at a non-statement position", [PM_ERR_STRING_CONCATENATION] = "Expected a string for concatenation", [PM_ERR_STRING_INTERPOLATED_TERM] = "Expected a closing delimiter for the interpolated string", [PM_ERR_STRING_LITERAL_TERM] = "Expected a closing delimiter for the string literal", diff --git a/prism/diagnostic.h b/prism/diagnostic.h index 68e507929f..06d90ec026 100644 --- a/prism/diagnostic.h +++ b/prism/diagnostic.h @@ -226,6 +226,10 @@ typedef enum { PM_ERR_RESCUE_TERM, PM_ERR_RESCUE_VARIABLE, PM_ERR_RETURN_INVALID, + PM_ERR_STATEMENT_ALIAS, + PM_ERR_STATEMENT_POSTEXE_END, + PM_ERR_STATEMENT_PREEXE_BEGIN, + PM_ERR_STATEMENT_UNDEF, PM_ERR_STRING_CONCATENATION, PM_ERR_STRING_INTERPOLATED_TERM, PM_ERR_STRING_LITERAL_TERM, diff --git a/prism/prism.c b/prism/prism.c index e4c5235dfc..4918e8a992 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -14214,6 +14214,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power) { parser_lex(parser); return (pm_node_t *) pm_source_line_node_create(parser, &parser->previous); case PM_TOKEN_KEYWORD_ALIAS: { + if (binding_power != PM_BINDING_POWER_STATEMENT) { + pm_parser_err_current(parser, PM_ERR_STATEMENT_ALIAS); + } + parser_lex(parser); pm_token_t keyword = parser->previous; @@ -14451,6 +14455,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power) { return (pm_node_t *) begin_node; } case PM_TOKEN_KEYWORD_BEGIN_UPCASE: { + if (binding_power != PM_BINDING_POWER_STATEMENT) { + pm_parser_err_current(parser, PM_ERR_STATEMENT_PREEXE_BEGIN); + } + parser_lex(parser); pm_token_t keyword = parser->previous; @@ -14901,6 +14909,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power) { ); } case PM_TOKEN_KEYWORD_END_UPCASE: { + if (binding_power != PM_BINDING_POWER_STATEMENT) { + pm_parser_err_current(parser, PM_ERR_STATEMENT_POSTEXE_END); + } + parser_lex(parser); pm_token_t keyword = parser->previous; @@ -14983,6 +14995,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power) { parser_lex(parser); return parse_conditional(parser, PM_CONTEXT_IF); case PM_TOKEN_KEYWORD_UNDEF: { + if (binding_power != PM_BINDING_POWER_STATEMENT) { + pm_parser_err_current(parser, PM_ERR_STATEMENT_UNDEF); + } + parser_lex(parser); pm_undef_node_t *undef = pm_undef_node_create(parser, &parser->previous); pm_node_t *name = parse_undef_argument(parser); @@ -16760,6 +16776,19 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, pm_diagn return node; } + // The statements BEGIN { ... }, END { ... }, alias ..., and undef ... are statement. + // They cannot follow operators, but they can follow modifiers. + bool is_statement = + PM_NODE_TYPE_P(node, PM_PRE_EXECUTION_NODE) || PM_NODE_TYPE_P(node, PM_POST_EXECUTION_NODE) || + PM_NODE_TYPE_P(node, PM_ALIAS_GLOBAL_VARIABLE_NODE) || PM_NODE_TYPE_P(node, PM_ALIAS_METHOD_NODE) || + PM_NODE_TYPE_P(node, PM_UNDEF_NODE); + // TODO: the right condition should `pm_binding_powers[parser->current.type].left > PM_BINDING_POWER_MODIFIER_RESCUE` instead. + // However, it does not work because of the `rescue` modifier's binding power trick. + // After getting to merge #1879, this TODO can be removed. + if (is_statement && pm_binding_powers[parser->current.type].right > PM_BINDING_POWER_MODIFIER_RESCUE + 1) { + return node; + } + // Otherwise we'll look and see if the next token can be parsed as an infix // operator. If it can, then we'll parse it using parse_expression_infix. pm_binding_powers_t current_binding_powers; diff --git a/test/prism/errors_test.rb b/test/prism/errors_test.rb index 6297548668..bc3f3ebee9 100644 --- a/test/prism/errors_test.rb +++ b/test/prism/errors_test.rb @@ -1722,6 +1722,54 @@ module Prism ] end + def test_statement_operators + source = <<~RUBY + alias x y + 1 + alias x y.z + BEGIN { bar } + 1 + BEGIN { bar }.z + END { bar } + 1 + END { bar }.z + undef x + 1 + undef x.z + RUBY + message1 = 'Expected a newline or semicolon after the statement' + message2 = 'Cannot parse the expression' + assert_errors expression(source), source, [ + [message1, 9..9], + [message2, 9..9], + [message1, 23..23], + [message2, 23..23], + [message1, 39..39], + [message2, 39..39], + [message1, 57..57], + [message2, 57..57], + [message1, 71..71], + [message2, 71..71], + [message1, 87..87], + [message2, 87..87], + [message1, 97..97], + [message2, 97..97], + [message1, 109..109], + [message2, 109..109], + ] + end + + def test_statement_at_non_statement + source = <<~RUBY + foo(alias x y) + foo(BEGIN { bar }) + foo(END { bar }) + foo(undef x) + RUBY + assert_errors expression(source), source, [ + ['Unexpected an `alias` at a non-statement position', 4..9], + ['Unexpected a `BEGIN` at a non-statement position', 19..24], + ['Unexpected an `END` at a non-statement position', 38..41], + ['Unexpected an `undef` at a non-statement position', 55..60], + ] + end + private def assert_errors(expected, source, errors, compare_ripper: RUBY_ENGINE == "ruby")