From 86559611665bfe3662e08332d8df9117220a35cf Mon Sep 17 00:00:00 2001 From: kai-matsudate Date: Mon, 8 Jun 2026 09:18:29 +0900 Subject: [PATCH] Accept a multiple assignment in a rescue modifier value A rescue modifier value is a statement in the grammar, so it may be a multiple assignment, which parse.y accepts but prism rejected: a rescue b, c = 1 # parse.y: Syntax OK, prism: unexpected ',' It is parsed below the statement level where multiple assignment detection starts, and lowering the binding power would absorb statement modifiers too, so set a flag while reading the value instead. --- snapshots/rescue_modifier.txt | 432 +++++++++++++++--- src/prism.c | 48 +- ...ltiple_assignment_in_single_assignment.txt | 4 + ...fier_multiple_assignment_without_value.txt | 3 + test/prism/fixtures/rescue_modifier.txt | 16 + 5 files changed, 416 insertions(+), 87 deletions(-) create mode 100644 test/prism/errors/rescue_modifier_multiple_assignment_in_single_assignment.txt create mode 100644 test/prism/errors/rescue_modifier_multiple_assignment_without_value.txt diff --git a/snapshots/rescue_modifier.txt b/snapshots/rescue_modifier.txt index c6d6e67ef2..f7dec63d2c 100644 --- a/snapshots/rescue_modifier.txt +++ b/snapshots/rescue_modifier.txt @@ -1,10 +1,10 @@ -@ ProgramNode (location: (1,0)-(7,23)) +@ ProgramNode (location: (1,0)-(23,24)) ├── flags: ∅ -├── locals: [:a] +├── locals: [:a, :b, :c, :d, :x, :y] └── statements: - @ StatementsNode (location: (1,0)-(7,23)) + @ StatementsNode (location: (1,0)-(23,24)) ├── flags: ∅ - └── body: (length: 4) + └── body: (length: 12) ├── @ IfNode (location: (1,0)-(1,15)) │ ├── flags: newline │ ├── if_keyword_loc: (1,11)-(1,13) = "if" @@ -174,69 +174,363 @@ │ │ └── block: ∅ │ ├── subsequent: ∅ │ └── end_keyword_loc: ∅ - └── @ IfNode (location: (7,0)-(7,23)) + ├── @ IfNode (location: (7,0)-(7,23)) + │ ├── flags: newline + │ ├── if_keyword_loc: (7,19)-(7,21) = "if" + │ ├── predicate: + │ │ @ CallNode (location: (7,22)-(7,23)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :d + │ │ ├── message_loc: (7,22)-(7,23) = "d" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: ∅ + │ ├── then_keyword_loc: ∅ + │ ├── statements: + │ │ @ StatementsNode (location: (7,0)-(7,18)) + │ │ ├── flags: ∅ + │ │ └── body: (length: 1) + │ │ └── @ DefNode (location: (7,0)-(7,18)) + │ │ ├── flags: newline + │ │ ├── name: :a + │ │ ├── name_loc: (7,4)-(7,5) = "a" + │ │ ├── receiver: ∅ + │ │ ├── parameters: ∅ + │ │ ├── body: + │ │ │ @ StatementsNode (location: (7,8)-(7,18)) + │ │ │ ├── flags: ∅ + │ │ │ └── body: (length: 1) + │ │ │ └── @ RescueModifierNode (location: (7,8)-(7,18)) + │ │ │ ├── flags: ∅ + │ │ │ ├── expression: + │ │ │ │ @ CallNode (location: (7,8)-(7,9)) + │ │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ │ ├── receiver: ∅ + │ │ │ │ ├── call_operator_loc: ∅ + │ │ │ │ ├── name: :b + │ │ │ │ ├── message_loc: (7,8)-(7,9) = "b" + │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ ├── arguments: ∅ + │ │ │ │ ├── closing_loc: ∅ + │ │ │ │ ├── equal_loc: ∅ + │ │ │ │ └── block: ∅ + │ │ │ ├── keyword_loc: (7,10)-(7,16) = "rescue" + │ │ │ └── rescue_expression: + │ │ │ @ CallNode (location: (7,17)-(7,18)) + │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :c + │ │ │ ├── message_loc: (7,17)-(7,18) = "c" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ ├── equal_loc: ∅ + │ │ │ └── block: ∅ + │ │ ├── locals: [] + │ │ ├── def_keyword_loc: (7,0)-(7,3) = "def" + │ │ ├── operator_loc: ∅ + │ │ ├── lparen_loc: ∅ + │ │ ├── rparen_loc: ∅ + │ │ ├── equal_loc: (7,6)-(7,7) = "=" + │ │ └── end_keyword_loc: ∅ + │ ├── subsequent: ∅ + │ └── end_keyword_loc: ∅ + ├── @ RescueModifierNode (location: (9,0)-(9,17)) + │ ├── flags: newline + │ ├── expression: + │ │ @ LocalVariableReadNode (location: (9,0)-(9,1)) + │ │ ├── flags: ∅ + │ │ ├── name: :a + │ │ └── depth: 0 + │ ├── keyword_loc: (9,2)-(9,8) = "rescue" + │ └── rescue_expression: + │ @ MultiWriteNode (location: (9,9)-(9,17)) + │ ├── flags: ∅ + │ ├── lefts: (length: 2) + │ │ ├── @ LocalVariableTargetNode (location: (9,9)-(9,10)) + │ │ │ ├── flags: ∅ + │ │ │ ├── name: :b + │ │ │ └── depth: 0 + │ │ └── @ LocalVariableTargetNode (location: (9,12)-(9,13)) + │ │ ├── flags: ∅ + │ │ ├── name: :c + │ │ └── depth: 0 + │ ├── rest: ∅ + │ ├── rights: (length: 0) + │ ├── lparen_loc: ∅ + │ ├── rparen_loc: ∅ + │ ├── operator_loc: (9,14)-(9,15) = "=" + │ └── value: + │ @ IntegerNode (location: (9,16)-(9,17)) + │ ├── flags: static_literal, decimal + │ └── value: 1 + ├── @ RescueModifierNode (location: (11,0)-(11,18)) + │ ├── flags: newline + │ ├── expression: + │ │ @ LocalVariableReadNode (location: (11,0)-(11,1)) + │ │ ├── flags: ∅ + │ │ ├── name: :a + │ │ └── depth: 0 + │ ├── keyword_loc: (11,2)-(11,8) = "rescue" + │ └── rescue_expression: + │ @ MultiWriteNode (location: (11,9)-(11,18)) + │ ├── flags: ∅ + │ ├── lefts: (length: 0) + │ ├── rest: + │ │ @ SplatNode (location: (11,9)-(11,11)) + │ │ ├── flags: ∅ + │ │ ├── operator_loc: (11,9)-(11,10) = "*" + │ │ └── expression: + │ │ @ LocalVariableTargetNode (location: (11,10)-(11,11)) + │ │ ├── flags: ∅ + │ │ ├── name: :b + │ │ └── depth: 0 + │ ├── rights: (length: 1) + │ │ └── @ LocalVariableTargetNode (location: (11,13)-(11,14)) + │ │ ├── flags: ∅ + │ │ ├── name: :c + │ │ └── depth: 0 + │ ├── lparen_loc: ∅ + │ ├── rparen_loc: ∅ + │ ├── operator_loc: (11,15)-(11,16) = "=" + │ └── value: + │ @ IntegerNode (location: (11,17)-(11,18)) + │ ├── flags: static_literal, decimal + │ └── value: 1 + ├── @ RescueModifierNode (location: (13,0)-(13,18)) + │ ├── flags: newline + │ ├── expression: + │ │ @ LocalVariableReadNode (location: (13,0)-(13,1)) + │ │ ├── flags: ∅ + │ │ ├── name: :a + │ │ └── depth: 0 + │ ├── keyword_loc: (13,2)-(13,8) = "rescue" + │ └── rescue_expression: + │ @ MultiWriteNode (location: (13,9)-(13,18)) + │ ├── flags: ∅ + │ ├── lefts: (length: 1) + │ │ └── @ LocalVariableTargetNode (location: (13,9)-(13,10)) + │ │ ├── flags: ∅ + │ │ ├── name: :b + │ │ └── depth: 0 + │ ├── rest: + │ │ @ SplatNode (location: (13,12)-(13,14)) + │ │ ├── flags: ∅ + │ │ ├── operator_loc: (13,12)-(13,13) = "*" + │ │ └── expression: + │ │ @ LocalVariableTargetNode (location: (13,13)-(13,14)) + │ │ ├── flags: ∅ + │ │ ├── name: :c + │ │ └── depth: 0 + │ ├── rights: (length: 0) + │ ├── lparen_loc: ∅ + │ ├── rparen_loc: ∅ + │ ├── operator_loc: (13,15)-(13,16) = "=" + │ └── value: + │ @ IntegerNode (location: (13,17)-(13,18)) + │ ├── flags: static_literal, decimal + │ └── value: 1 + ├── @ RescueModifierNode (location: (15,0)-(15,19)) + │ ├── flags: newline + │ ├── expression: + │ │ @ LocalVariableReadNode (location: (15,0)-(15,1)) + │ │ ├── flags: ∅ + │ │ ├── name: :a + │ │ └── depth: 0 + │ ├── keyword_loc: (15,2)-(15,8) = "rescue" + │ └── rescue_expression: + │ @ MultiWriteNode (location: (15,9)-(15,19)) + │ ├── flags: ∅ + │ ├── lefts: (length: 2) + │ │ ├── @ CallTargetNode (location: (15,9)-(15,12)) + │ │ │ ├── flags: ∅ + │ │ │ ├── receiver: + │ │ │ │ @ LocalVariableReadNode (location: (15,9)-(15,10)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ ├── name: :b + │ │ │ │ └── depth: 0 + │ │ │ ├── call_operator_loc: (15,10)-(15,11) = "." + │ │ │ ├── name: :c= + │ │ │ └── message_loc: (15,11)-(15,12) = "c" + │ │ └── @ LocalVariableTargetNode (location: (15,14)-(15,15)) + │ │ ├── flags: ∅ + │ │ ├── name: :d + │ │ └── depth: 0 + │ ├── rest: ∅ + │ ├── rights: (length: 0) + │ ├── lparen_loc: ∅ + │ ├── rparen_loc: ∅ + │ ├── operator_loc: (15,16)-(15,17) = "=" + │ └── value: + │ @ IntegerNode (location: (15,18)-(15,19)) + │ ├── flags: static_literal, decimal + │ └── value: 1 + ├── @ RescueModifierNode (location: (17,0)-(17,20)) + │ ├── flags: newline + │ ├── expression: + │ │ @ LocalVariableReadNode (location: (17,0)-(17,1)) + │ │ ├── flags: ∅ + │ │ ├── name: :a + │ │ └── depth: 0 + │ ├── keyword_loc: (17,2)-(17,8) = "rescue" + │ └── rescue_expression: + │ @ MultiWriteNode (location: (17,9)-(17,20)) + │ ├── flags: ∅ + │ ├── lefts: (length: 2) + │ │ ├── @ IndexTargetNode (location: (17,9)-(17,13)) + │ │ │ ├── flags: attribute_write + │ │ │ ├── receiver: + │ │ │ │ @ LocalVariableReadNode (location: (17,9)-(17,10)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ ├── name: :b + │ │ │ │ └── depth: 0 + │ │ │ ├── opening_loc: (17,10)-(17,11) = "[" + │ │ │ ├── arguments: + │ │ │ │ @ ArgumentsNode (location: (17,11)-(17,12)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ └── arguments: (length: 1) + │ │ │ │ └── @ IntegerNode (location: (17,11)-(17,12)) + │ │ │ │ ├── flags: static_literal, decimal + │ │ │ │ └── value: 0 + │ │ │ ├── closing_loc: (17,12)-(17,13) = "]" + │ │ │ └── block: ∅ + │ │ └── @ LocalVariableTargetNode (location: (17,15)-(17,16)) + │ │ ├── flags: ∅ + │ │ ├── name: :c + │ │ └── depth: 0 + │ ├── rest: ∅ + │ ├── rights: (length: 0) + │ ├── lparen_loc: ∅ + │ ├── rparen_loc: ∅ + │ ├── operator_loc: (17,17)-(17,18) = "=" + │ └── value: + │ @ IntegerNode (location: (17,19)-(17,20)) + │ ├── flags: static_literal, decimal + │ └── value: 1 + ├── @ RescueModifierNode (location: (19,0)-(19,20)) + │ ├── flags: newline + │ ├── expression: + │ │ @ LocalVariableReadNode (location: (19,0)-(19,1)) + │ │ ├── flags: ∅ + │ │ ├── name: :a + │ │ └── depth: 0 + │ ├── keyword_loc: (19,2)-(19,8) = "rescue" + │ └── rescue_expression: + │ @ MultiWriteNode (location: (19,9)-(19,20)) + │ ├── flags: ∅ + │ ├── lefts: (length: 2) + │ │ ├── @ LocalVariableTargetNode (location: (19,9)-(19,10)) + │ │ │ ├── flags: ∅ + │ │ │ ├── name: :b + │ │ │ └── depth: 0 + │ │ └── @ LocalVariableTargetNode (location: (19,12)-(19,13)) + │ │ ├── flags: ∅ + │ │ ├── name: :c + │ │ └── depth: 0 + │ ├── rest: ∅ + │ ├── rights: (length: 0) + │ ├── lparen_loc: ∅ + │ ├── rparen_loc: ∅ + │ ├── operator_loc: (19,14)-(19,15) = "=" + │ └── value: + │ @ ArrayNode (location: (19,16)-(19,20)) + │ ├── flags: static_literal + │ ├── elements: (length: 2) + │ │ ├── @ IntegerNode (location: (19,16)-(19,17)) + │ │ │ ├── flags: static_literal, decimal + │ │ │ └── value: 1 + │ │ └── @ IntegerNode (location: (19,19)-(19,20)) + │ │ ├── flags: static_literal, decimal + │ │ └── value: 2 + │ ├── opening_loc: ∅ + │ └── closing_loc: ∅ + ├── @ RescueModifierNode (location: (21,0)-(21,18)) + │ ├── flags: newline + │ ├── expression: + │ │ @ LocalVariableReadNode (location: (21,0)-(21,1)) + │ │ ├── flags: ∅ + │ │ ├── name: :a + │ │ └── depth: 0 + │ ├── keyword_loc: (21,2)-(21,8) = "rescue" + │ └── rescue_expression: + │ @ MultiWriteNode (location: (21,9)-(21,18)) + │ ├── flags: ∅ + │ ├── lefts: (length: 2) + │ │ ├── @ LocalVariableTargetNode (location: (21,9)-(21,10)) + │ │ │ ├── flags: ∅ + │ │ │ ├── name: :b + │ │ │ └── depth: 0 + │ │ └── @ LocalVariableTargetNode (location: (21,12)-(21,13)) + │ │ ├── flags: ∅ + │ │ ├── name: :c + │ │ └── depth: 0 + │ ├── rest: ∅ + │ ├── rights: (length: 0) + │ ├── lparen_loc: ∅ + │ ├── rparen_loc: ∅ + │ ├── operator_loc: (21,14)-(21,15) = "=" + │ └── value: + │ @ ArrayNode (location: (21,16)-(21,18)) + │ ├── flags: contains_splat + │ ├── elements: (length: 1) + │ │ └── @ SplatNode (location: (21,16)-(21,18)) + │ │ ├── flags: ∅ + │ │ ├── operator_loc: (21,16)-(21,17) = "*" + │ │ └── expression: + │ │ @ LocalVariableReadNode (location: (21,17)-(21,18)) + │ │ ├── flags: ∅ + │ │ ├── name: :d + │ │ └── depth: 0 + │ ├── opening_loc: ∅ + │ └── closing_loc: ∅ + └── @ MultiWriteNode (location: (23,0)-(23,24)) ├── flags: newline - ├── if_keyword_loc: (7,19)-(7,21) = "if" - ├── predicate: - │ @ CallNode (location: (7,22)-(7,23)) - │ ├── flags: variable_call, ignore_visibility - │ ├── receiver: ∅ - │ ├── call_operator_loc: ∅ - │ ├── name: :d - │ ├── message_loc: (7,22)-(7,23) = "d" - │ ├── opening_loc: ∅ - │ ├── arguments: ∅ - │ ├── closing_loc: ∅ - │ ├── equal_loc: ∅ - │ └── block: ∅ - ├── then_keyword_loc: ∅ - ├── statements: - │ @ StatementsNode (location: (7,0)-(7,18)) - │ ├── flags: ∅ - │ └── body: (length: 1) - │ └── @ DefNode (location: (7,0)-(7,18)) - │ ├── flags: newline - │ ├── name: :a - │ ├── name_loc: (7,4)-(7,5) = "a" - │ ├── receiver: ∅ - │ ├── parameters: ∅ - │ ├── body: - │ │ @ StatementsNode (location: (7,8)-(7,18)) - │ │ ├── flags: ∅ - │ │ └── body: (length: 1) - │ │ └── @ RescueModifierNode (location: (7,8)-(7,18)) - │ │ ├── flags: ∅ - │ │ ├── expression: - │ │ │ @ CallNode (location: (7,8)-(7,9)) - │ │ │ ├── flags: variable_call, ignore_visibility - │ │ │ ├── receiver: ∅ - │ │ │ ├── call_operator_loc: ∅ - │ │ │ ├── name: :b - │ │ │ ├── message_loc: (7,8)-(7,9) = "b" - │ │ │ ├── opening_loc: ∅ - │ │ │ ├── arguments: ∅ - │ │ │ ├── closing_loc: ∅ - │ │ │ ├── equal_loc: ∅ - │ │ │ └── block: ∅ - │ │ ├── keyword_loc: (7,10)-(7,16) = "rescue" - │ │ └── rescue_expression: - │ │ @ CallNode (location: (7,17)-(7,18)) - │ │ ├── flags: variable_call, ignore_visibility - │ │ ├── receiver: ∅ - │ │ ├── call_operator_loc: ∅ - │ │ ├── name: :c - │ │ ├── message_loc: (7,17)-(7,18) = "c" - │ │ ├── opening_loc: ∅ - │ │ ├── arguments: ∅ - │ │ ├── closing_loc: ∅ - │ │ ├── equal_loc: ∅ - │ │ └── block: ∅ - │ ├── locals: [] - │ ├── def_keyword_loc: (7,0)-(7,3) = "def" - │ ├── operator_loc: ∅ - │ ├── lparen_loc: ∅ - │ ├── rparen_loc: ∅ - │ ├── equal_loc: (7,6)-(7,7) = "=" - │ └── end_keyword_loc: ∅ - ├── subsequent: ∅ - └── end_keyword_loc: ∅ + ├── lefts: (length: 2) + │ ├── @ LocalVariableTargetNode (location: (23,0)-(23,1)) + │ │ ├── flags: ∅ + │ │ ├── name: :x + │ │ └── depth: 0 + │ └── @ LocalVariableTargetNode (location: (23,3)-(23,4)) + │ ├── flags: ∅ + │ ├── name: :y + │ └── depth: 0 + ├── rest: ∅ + ├── rights: (length: 0) + ├── lparen_loc: ∅ + ├── rparen_loc: ∅ + ├── operator_loc: (23,5)-(23,6) = "=" + └── value: + @ RescueModifierNode (location: (23,7)-(23,24)) + ├── flags: ∅ + ├── expression: + │ @ LocalVariableReadNode (location: (23,7)-(23,8)) + │ ├── flags: ∅ + │ ├── name: :a + │ └── depth: 0 + ├── keyword_loc: (23,9)-(23,15) = "rescue" + └── rescue_expression: + @ MultiWriteNode (location: (23,16)-(23,24)) + ├── flags: ∅ + ├── lefts: (length: 2) + │ ├── @ LocalVariableTargetNode (location: (23,16)-(23,17)) + │ │ ├── flags: ∅ + │ │ ├── name: :b + │ │ └── depth: 0 + │ └── @ LocalVariableTargetNode (location: (23,19)-(23,20)) + │ ├── flags: ∅ + │ ├── name: :c + │ └── depth: 0 + ├── rest: ∅ + ├── rights: (length: 0) + ├── lparen_loc: ∅ + ├── rparen_loc: ∅ + ├── operator_loc: (23,21)-(23,22) = "=" + └── value: + @ IntegerNode (location: (23,23)-(23,24)) + ├── flags: static_literal, decimal + └── value: 1 diff --git a/src/prism.c b/src/prism.c index a2e04ed106..5353cc6950 100644 --- a/src/prism.c +++ b/src/prism.c @@ -12734,6 +12734,11 @@ expect1_opening(pm_parser_t *parser, pm_token_type_t type, pm_diagnostic_id_t di #define PM_PARSE_ACCEPTS_LABEL ((uint8_t) 0x2) #define PM_PARSE_ACCEPTS_DO_BLOCK ((uint8_t) 0x4) #define PM_PARSE_IN_ENDLESS_DEF ((uint8_t) 0x8) +#define PM_PARSE_ACCEPTS_MULTIPLE_ASSIGNMENT ((uint8_t) 0x10) + +/** Return true if a multiple assignment may begin in the current context. */ +#define PM_PARSE_MULTIPLE_ASSIGNMENT_P(binding_power_, flags_) \ + ((binding_power_) == PM_BINDING_POWER_STATEMENT || ((flags_) & PM_PARSE_ACCEPTS_MULTIPLE_ASSIGNMENT)) static pm_node_t * parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, uint8_t flags, pm_diagnostic_id_t diag_id, uint16_t depth); @@ -19247,7 +19252,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, u parser_lex(parser); pm_node_t *node = UP(pm_class_variable_read_node_create(parser, &parser->previous)); - if (binding_power == PM_BINDING_POWER_STATEMENT && match1(parser, PM_TOKEN_COMMA)) { + if (PM_PARSE_MULTIPLE_ASSIGNMENT_P(binding_power, flags) && match1(parser, PM_TOKEN_COMMA)) { node = parse_targets_validate(parser, node, PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1)); } @@ -19272,7 +19277,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, u pm_node_t *node = UP(pm_constant_read_node_create(parser, &parser->previous)); - if ((binding_power == PM_BINDING_POWER_STATEMENT) && match1(parser, PM_TOKEN_COMMA)) { + if (PM_PARSE_MULTIPLE_ASSIGNMENT_P(binding_power, flags) && match1(parser, PM_TOKEN_COMMA)) { // If we get here, then we have a comma immediately following a // constant, so we're going to parse this as a multiple assignment. node = parse_targets_validate(parser, node, PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1)); @@ -19287,7 +19292,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, u expect1(parser, PM_TOKEN_CONSTANT, PM_ERR_CONSTANT_PATH_COLON_COLON_CONSTANT); pm_node_t *node = UP(pm_constant_path_node_create(parser, NULL, &delimiter, &parser->previous)); - if ((binding_power == PM_BINDING_POWER_STATEMENT) && match1(parser, PM_TOKEN_COMMA)) { + if (PM_PARSE_MULTIPLE_ASSIGNMENT_P(binding_power, flags) && match1(parser, PM_TOKEN_COMMA)) { node = parse_targets_validate(parser, node, PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1)); } @@ -19326,7 +19331,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, u parser_lex(parser); pm_node_t *node = UP(pm_numbered_reference_read_node_create(parser, &parser->previous)); - if (binding_power == PM_BINDING_POWER_STATEMENT && match1(parser, PM_TOKEN_COMMA)) { + if (PM_PARSE_MULTIPLE_ASSIGNMENT_P(binding_power, flags) && match1(parser, PM_TOKEN_COMMA)) { node = parse_targets_validate(parser, node, PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1)); } @@ -19336,7 +19341,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, u parser_lex(parser); pm_node_t *node = UP(pm_global_variable_read_node_create(parser, &parser->previous)); - if (binding_power == PM_BINDING_POWER_STATEMENT && match1(parser, PM_TOKEN_COMMA)) { + if (PM_PARSE_MULTIPLE_ASSIGNMENT_P(binding_power, flags) && match1(parser, PM_TOKEN_COMMA)) { node = parse_targets_validate(parser, node, PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1)); } @@ -19346,7 +19351,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, u parser_lex(parser); pm_node_t *node = UP(pm_back_reference_read_node_create(parser, &parser->previous)); - if (binding_power == PM_BINDING_POWER_STATEMENT && match1(parser, PM_TOKEN_COMMA)) { + if (PM_PARSE_MULTIPLE_ASSIGNMENT_P(binding_power, flags) && match1(parser, PM_TOKEN_COMMA)) { node = parse_targets_validate(parser, node, PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1)); } @@ -19420,7 +19425,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, u } } - if ((binding_power == PM_BINDING_POWER_STATEMENT) && match1(parser, PM_TOKEN_COMMA)) { + if (PM_PARSE_MULTIPLE_ASSIGNMENT_P(binding_power, flags) && match1(parser, PM_TOKEN_COMMA)) { node = parse_targets_validate(parser, node, PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1)); } @@ -19543,7 +19548,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, u parser_lex(parser); pm_node_t *node = UP(pm_instance_variable_read_node_create(parser, &parser->previous)); - if (binding_power == PM_BINDING_POWER_STATEMENT && match1(parser, PM_TOKEN_COMMA)) { + if (PM_PARSE_MULTIPLE_ASSIGNMENT_P(binding_power, flags) && match1(parser, PM_TOKEN_COMMA)) { node = parse_targets_validate(parser, node, PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1)); } @@ -20355,7 +20360,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, u // * operators at the beginning of expressions are only valid in the // context of a multiple assignment. We enforce that here. We'll // still lex past it though and create a missing node place. - if (binding_power != PM_BINDING_POWER_STATEMENT) { + if (!PM_PARSE_MULTIPLE_ASSIGNMENT_P(binding_power, flags)) { pm_parser_err_prefix(parser, diag_id); return UP(pm_error_recovery_node_create(parser, PM_TOKEN_START(parser, &parser->previous), PM_TOKEN_LENGTH(&parser->previous))); } @@ -20664,7 +20669,7 @@ parse_assignment_value_local(pm_parser_t *parser, const pm_node_t *node) { static pm_node_t * parse_assignment_values(pm_parser_t *parser, pm_binding_power_t previous_binding_power, pm_binding_power_t binding_power, uint8_t flags, pm_diagnostic_id_t diag_id, uint16_t depth) { bool permitted = true; - if (previous_binding_power != PM_BINDING_POWER_STATEMENT && match1(parser, PM_TOKEN_USTAR)) permitted = false; + if (!PM_PARSE_MULTIPLE_ASSIGNMENT_P(previous_binding_power, flags) && match1(parser, PM_TOKEN_USTAR)) permitted = false; pm_node_t *value = parse_starred_expression(parser, binding_power, (uint8_t) ((flags & PM_PARSE_ACCEPTS_DO_BLOCK) | (previous_binding_power == PM_BINDING_POWER_ASSIGNMENT ? (flags & PM_PARSE_ACCEPTS_COMMAND_CALL) : (previous_binding_power < PM_BINDING_POWER_MODIFIER ? PM_PARSE_ACCEPTS_COMMAND_CALL : 0))), diag_id, (uint16_t) (depth + 1)); if (!permitted) pm_parser_err_node(parser, value, PM_ERR_UNEXPECTED_MULTI_WRITE); @@ -20675,7 +20680,7 @@ parse_assignment_values(pm_parser_t *parser, pm_binding_power_t previous_binding // Block calls (command call + do block, e.g., `foo bar do end`) cannot // be followed by a comma to form a multi-value RHS because each element // of a multi-value assignment must be an `arg`, not a `block_call`. - if (previous_binding_power == PM_BINDING_POWER_STATEMENT && !pm_block_call_p(value) && (PM_NODE_TYPE_P(value, PM_SPLAT_NODE) || match1(parser, PM_TOKEN_COMMA))) { + if (PM_PARSE_MULTIPLE_ASSIGNMENT_P(previous_binding_power, flags) && !pm_block_call_p(value) && (PM_NODE_TYPE_P(value, PM_SPLAT_NODE) || match1(parser, PM_TOKEN_COMMA))) { single_value = false; pm_array_node_t *array = pm_array_node_create(parser, NULL); @@ -20721,7 +20726,12 @@ parse_assignment_values(pm_parser_t *parser, pm_binding_power_t previous_binding } } - pm_node_t *right = parse_expression(parser, pm_binding_powers[PM_TOKEN_KEYWORD_RESCUE_MODIFIER].right, (uint8_t) ((flags & PM_PARSE_ACCEPTS_DO_BLOCK) | (accepts_command_call_inner ? PM_PARSE_ACCEPTS_COMMAND_CALL : 0)), PM_ERR_RESCUE_MODIFIER_VALUE, (uint16_t) (depth + 1)); + uint8_t rescue_flags = (uint8_t) ((flags & PM_PARSE_ACCEPTS_DO_BLOCK) | (accepts_command_call_inner ? PM_PARSE_ACCEPTS_COMMAND_CALL : 0)); + if (binding_power == (PM_BINDING_POWER_MULTI_ASSIGNMENT + 1)) { + rescue_flags |= PM_PARSE_ACCEPTS_MULTIPLE_ASSIGNMENT; + } + + pm_node_t *right = parse_expression(parser, pm_binding_powers[PM_TOKEN_KEYWORD_RESCUE_MODIFIER].right, rescue_flags, PM_ERR_RESCUE_MODIFIER_VALUE, (uint16_t) (depth + 1)); context_pop(parser); return UP(pm_rescue_modifier_node_create(parser, value, &rescue, right)); @@ -21013,7 +21023,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); pm_node_t *value = parse_assignment_values(parser, previous_binding_power, PM_NODE_TYPE_P(node, PM_MULTI_TARGET_NODE) ? PM_BINDING_POWER_MULTI_ASSIGNMENT + 1 : binding_power, flags, PM_ERR_EXPECT_EXPRESSION_AFTER_EQUAL, (uint16_t) (depth + 1)); - if (PM_NODE_TYPE_P(node, PM_MULTI_TARGET_NODE) && previous_binding_power != PM_BINDING_POWER_STATEMENT) { + if (PM_NODE_TYPE_P(node, PM_MULTI_TARGET_NODE) && !PM_PARSE_MULTIPLE_ASSIGNMENT_P(previous_binding_power, flags)) { pm_parser_err_node(parser, node, PM_ERR_UNEXPECTED_MULTI_WRITE); } @@ -21644,7 +21654,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_call_node_t *call = pm_call_node_call_create(parser, node, &operator, &message, &arguments); if ( - (previous_binding_power == PM_BINDING_POWER_STATEMENT) && + PM_PARSE_MULTIPLE_ASSIGNMENT_P(previous_binding_power, flags) && arguments.arguments == NULL && arguments.opening_loc.length == 0 && match1(parser, PM_TOKEN_COMMA) @@ -21760,7 +21770,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t } // If this is followed by a comma then it is a multiple assignment. - if (previous_binding_power == PM_BINDING_POWER_STATEMENT && match1(parser, PM_TOKEN_COMMA)) { + if (PM_PARSE_MULTIPLE_ASSIGNMENT_P(previous_binding_power, flags) && match1(parser, PM_TOKEN_COMMA)) { return parse_targets_validate(parser, path, PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1)); } @@ -21780,7 +21790,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_call_node_t *call = pm_call_node_call_create(parser, node, &delimiter, &message, &arguments); // If this is followed by a comma then it is a multiple assignment. - if (previous_binding_power == PM_BINDING_POWER_STATEMENT && match1(parser, PM_TOKEN_COMMA)) { + if (PM_PARSE_MULTIPLE_ASSIGNMENT_P(previous_binding_power, flags) && match1(parser, PM_TOKEN_COMMA)) { return parse_targets_validate(parser, UP(call), PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1)); } @@ -21805,7 +21815,9 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); accept1(parser, PM_TOKEN_NEWLINE); - pm_node_t *value = parse_expression(parser, binding_power, (uint8_t) ((flags & PM_PARSE_ACCEPTS_DO_BLOCK) | PM_PARSE_ACCEPTS_COMMAND_CALL), PM_ERR_RESCUE_MODIFIER_VALUE, (uint16_t) (depth + 1)); + // Keep the right binding power so statement modifiers stay outside + // the value, but allow the value to be a multiple assignment. + pm_node_t *value = parse_expression(parser, binding_power, (uint8_t) ((flags & PM_PARSE_ACCEPTS_DO_BLOCK) | PM_PARSE_ACCEPTS_COMMAND_CALL | PM_PARSE_ACCEPTS_MULTIPLE_ASSIGNMENT), PM_ERR_RESCUE_MODIFIER_VALUE, (uint16_t) (depth + 1)); context_pop(parser); return UP(pm_rescue_modifier_node_create(parser, node, &token, value)); @@ -21827,7 +21839,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t // If we have a comma after the closing bracket then this is a multiple // assignment and we should parse the targets. - if (previous_binding_power == PM_BINDING_POWER_STATEMENT && match1(parser, PM_TOKEN_COMMA)) { + if (PM_PARSE_MULTIPLE_ASSIGNMENT_P(previous_binding_power, flags) && match1(parser, PM_TOKEN_COMMA)) { pm_call_node_t *aref = pm_call_node_aref_create(parser, node, &arguments); return parse_targets_validate(parser, UP(aref), PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1)); } diff --git a/test/prism/errors/rescue_modifier_multiple_assignment_in_single_assignment.txt b/test/prism/errors/rescue_modifier_multiple_assignment_in_single_assignment.txt new file mode 100644 index 0000000000..f103c660b6 --- /dev/null +++ b/test/prism/errors/rescue_modifier_multiple_assignment_in_single_assignment.txt @@ -0,0 +1,4 @@ +x = a rescue b, c = 1 + ^ unexpected ',', expecting end-of-input + ^ unexpected ',', ignoring it + diff --git a/test/prism/errors/rescue_modifier_multiple_assignment_without_value.txt b/test/prism/errors/rescue_modifier_multiple_assignment_without_value.txt new file mode 100644 index 0000000000..49708efeb7 --- /dev/null +++ b/test/prism/errors/rescue_modifier_multiple_assignment_without_value.txt @@ -0,0 +1,3 @@ +a rescue b, c + ^~~~ unexpected write target + diff --git a/test/prism/fixtures/rescue_modifier.txt b/test/prism/fixtures/rescue_modifier.txt index def9e2dbed..74d68ea659 100644 --- a/test/prism/fixtures/rescue_modifier.txt +++ b/test/prism/fixtures/rescue_modifier.txt @@ -5,3 +5,19 @@ a = b rescue c if d a, = b rescue c if d def a = b rescue c if d + +a rescue b, c = 1 + +a rescue *b, c = 1 + +a rescue b, *c = 1 + +a rescue b.c, d = 1 + +a rescue b[0], c = 1 + +a rescue b, c = 1, 2 + +a rescue b, c = *d + +x, y = a rescue b, c = 1