summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--prism/diagnostic.c4
-rw-r--r--prism/diagnostic.h4
-rw-r--r--prism/prism.c29
-rw-r--r--test/prism/errors_test.rb48
4 files changed, 85 insertions, 0 deletions
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")