Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reject non-assoc ranges with subsequent operators #3089

Merged
merged 2 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ errors:
- MULTI_ASSIGN_UNEXPECTED_REST
- NESTING_TOO_DEEP
- NO_LOCAL_VARIABLE
- NON_ASSOCIATIVE_OPERATOR
- NOT_EXPRESSION
- NUMBER_LITERAL_UNDERSCORE
- NUMBERED_PARAMETER_INNER_BLOCK
Expand Down
2 changes: 2 additions & 0 deletions gemfiles/jruby/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ GEM

PLATFORMS
universal-java-11
universal-java-17
universal-java-20
universal-java-21
universal-java-22

DEPENDENCIES
parser
Expand Down
28 changes: 19 additions & 9 deletions src/prism.c
Original file line number Diff line number Diff line change
Expand Up @@ -21643,16 +21643,26 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool acc
break;
}

// If the operator is nonassoc and we should not be able to parse the
// upcoming infix operator, break.
if (current_binding_powers.nonassoc) {
bool endless_range_p = PM_NODE_TYPE_P(node, PM_RANGE_NODE) && ((pm_range_node_t *) node)->right == NULL;
pm_binding_power_t left = endless_range_p ? PM_BINDING_POWER_TERM : current_binding_powers.left;
if (
left <= pm_binding_powers[parser->current.type].left ||
// Exceptionally to operator precedences, '1.. & 2' is rejected.
// '1.. || 2' is also an exception, but it is handled by the lexer.
// (Here, parser->current is PM_TOKEN_PIPE, not PM_TOKEN_PIPE_PIPE).
(endless_range_p && match1(parser, PM_TOKEN_AMPERSAND))
) {
// If this is an endless range, then we need to reject a couple of
// additional operators because it violates the normal operator
// precedence rules. Those patterns are:
//
// 1.. & 2
// 1.. * 2
//
if (PM_NODE_TYPE_P(node, PM_RANGE_NODE) && ((pm_range_node_t *) node)->right == NULL) {
if (match3(parser, PM_TOKEN_UAMPERSAND, PM_TOKEN_USTAR, PM_TOKEN_DOT)) {
PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_NON_ASSOCIATIVE_OPERATOR, pm_token_type_human(parser->current.type), pm_token_type_human(parser->previous.type));
break;
}

if (PM_BINDING_POWER_TERM <= pm_binding_powers[parser->current.type].left) {
break;
}
} else if (current_binding_powers.left <= pm_binding_powers[parser->current.type].left) {
break;
}
}
Expand Down
1 change: 1 addition & 0 deletions templates/src/diagnostic.c.erb
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = {
[PM_ERR_MULTI_ASSIGN_UNEXPECTED_REST] = { "unexpected '%.*s' resulting in multiple splats in multiple assignment", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_NESTING_TOO_DEEP] = { "nesting too deep", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_NO_LOCAL_VARIABLE] = { "%.*s: no such local variable", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_NON_ASSOCIATIVE_OPERATOR] = { "unexpected %s; %s is a non-associative operator", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_NOT_EXPRESSION] = { "expected an expression after `not`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_NUMBER_LITERAL_UNDERSCORE] = { "number literal ending with a `_`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_NUMBERED_PARAMETER_INNER_BLOCK] = { "numbered parameter is already used in inner block", PM_ERROR_LEVEL_SYNTAX },
Expand Down
1 change: 1 addition & 0 deletions test/prism/errors/non_assoc_range.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
1....2
^ unexpected '.'; ... is a non-associative operator
^ unexpected '.', expecting end-of-input
^ unexpected '.', ignoring it

1 change: 1 addition & 0 deletions test/prism/errors/range_and_bin_op_4.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
1.. & 2
^ unexpected '&'; .. is a non-associative operator
^ unexpected '&', expecting end-of-input
^ unexpected '&', ignoring it

1 change: 1 addition & 0 deletions test/prism/errors/range_and_bin_op_5.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
1.. * 2
^ unexpected *; .. is a non-associative operator
^ unexpected *, expecting end-of-input
^ unexpected write target
^~~ unexpected write target
Expand Down
Loading