Repository: ruby/prism
Pull request #2502 (2024-02-26 13:46:22)

Fix multi write with modifier rescue
Fixes https://github.com/ruby/prism/issues/2500


Patch:
diff --git a/src/prism.c b/src/prism.c
index 864dd49f..fecc2533 100644
--- a/src/prism.c
+++ b/src/prism.c
@@ -17051,10 +17051,10 @@ parse_assignment_value(pm_parser_t *parser, pm_binding_power_t previous_binding_
 static inline pm_node_t *
 parse_assignment_values(pm_parser_t *parser, pm_binding_power_t previous_binding_power, pm_binding_power_t binding_power, bool accepts_command_call, pm_diagnostic_id_t diag_id) {
     pm_node_t *value = parse_starred_expression(parser, binding_power, previous_binding_power == PM_BINDING_POWER_ASSIGNMENT ? accepts_command_call : previous_binding_power < PM_BINDING_POWER_MATCH, diag_id);
+    bool single_value = true;
 
-    bool is_single_value = true;
     if (previous_binding_power == PM_BINDING_POWER_STATEMENT && (PM_NODE_TYPE_P(value, PM_SPLAT_NODE) || match1(parser, PM_TOKEN_COMMA))) {
-        is_single_value = false;
+        single_value = false;
         pm_token_t opening = not_provided(parser);
         pm_array_node_t *array = pm_array_node_create(parser, &opening);
 
@@ -17068,16 +17068,18 @@ parse_assignment_values(pm_parser_t *parser, pm_binding_power_t previous_binding
         }
     }
 
-    // Contradicting binding powers, the right-hand-side value of the assignment allows the `rescue` modifier.
-    if (is_single_value && match1(parser, PM_TOKEN_KEYWORD_RESCUE_MODIFIER)) {
+    // Contradicting binding powers, the right-hand-side value of the assignment
+    // allows the `rescue` modifier.
+    if ((single_value || (binding_power == (PM_BINDING_POWER_MULTI_ASSIGNMENT + 1))) && match1(parser, PM_TOKEN_KEYWORD_RESCUE_MODIFIER)) {
         pm_token_t rescue = parser->current;
         parser_lex(parser);
 
         bool accepts_command_call_inner = false;
 
-        // RHS can accept command call iff the value is a call with arguments but without paranthesis.
+        // RHS can accept command call iff the value is a call with arguments
+        // but without paranthesis.
         if (PM_NODE_TYPE_P(value, PM_CALL_NODE)) {
-            pm_call_node_t *call_node = (pm_call_node_t *)value;
+            pm_call_node_t *call_node = (pm_call_node_t *) value;
             if ((call_node->arguments != NULL) && (call_node->opening_loc.start == NULL)) {
                 accepts_command_call_inner = true;
             }


Test patch:
diff --git a/test/prism/fixtures/multi_write.txt b/test/prism/fixtures/multi_write.txt
new file mode 100644
index 00000000..edbcafb9
--- /dev/null
+++ b/test/prism/fixtures/multi_write.txt
@@ -0,0 +1,4 @@
+foo = 1 rescue nil
+foo, bar = 1 rescue nil
+foo = 1, 2 rescue nil
+foo, bar = 1, 2 rescue nil
diff --git a/test/prism/ruby_parser_test.rb b/test/prism/ruby_parser_test.rb
index a71d05e7..89150b2f 100644
--- a/test/prism/ruby_parser_test.rb
+++ b/test/prism/ruby_parser_test.rb
@@ -73,6 +73,7 @@ module Prism
       alias.txt
       method_calls.txt
       methods.txt
+      multi_write.txt
       not.txt
       patterns.txt
       regex.txt
diff --git a/test/prism/snapshots/multi_write.txt b/test/prism/snapshots/multi_write.txt
new file mode 100644
index 00000000..d313801f
--- /dev/null
+++ b/test/prism/snapshots/multi_write.txt
@@ -0,0 +1,93 @@
+@ ProgramNode (location: (1,0)-(4,26))
+├── locals: [:foo, :bar]
+└── statements:
+    @ StatementsNode (location: (1,0)-(4,26))
+    └── body: (length: 4)
+        ├── @ LocalVariableWriteNode (location: (1,0)-(1,18))
+        │   ├── name: :foo
+        │   ├── depth: 0
+        │   ├── name_loc: (1,0)-(1,3) = "foo"
+        │   ├── value:
+        │   │   @ RescueModifierNode (location: (1,6)-(1,18))
+        │   │   ├── expression:
+        │   │   │   @ IntegerNode (location: (1,6)-(1,7))
+        │   │   │   ├── flags: decimal
+        │   │   │   └── value: 1
+        │   │   ├── keyword_loc: (1,8)-(1,14) = "rescue"
+        │   │   └── rescue_expression:
+        │   │       @ NilNode (location: (1,15)-(1,18))
+        │   └── operator_loc: (1,4)-(1,5) = "="
+        ├── @ MultiWriteNode (location: (2,0)-(2,23))
+        │   ├── lefts: (length: 2)
+        │   │   ├── @ LocalVariableTargetNode (location: (2,0)-(2,3))
+        │   │   │   ├── name: :foo
+        │   │   │   └── depth: 0
+        │   │   └── @ LocalVariableTargetNode (location: (2,5)-(2,8))
+        │   │       ├── name: :bar
+        │   │       └── depth: 0
+        │   ├── rest: ∅
+        │   ├── rights: (length: 0)
+        │   ├── lparen_loc: ∅
+        │   ├── rparen_loc: ∅
+        │   ├── operator_loc: (2,9)-(2,10) = "="
+        │   └── value:
+        │       @ RescueModifierNode (location: (2,11)-(2,23))
+        │       ├── expression:
+        │       │   @ IntegerNode (location: (2,11)-(2,12))
+        │       │   ├── flags: decimal
+        │       │   └── value: 1
+        │       ├── keyword_loc: (2,13)-(2,19) = "rescue"
+        │       └── rescue_expression:
+        │           @ NilNode (location: (2,20)-(2,23))
+        ├── @ RescueModifierNode (location: (3,0)-(3,21))
+        │   ├── expression:
+        │   │   @ LocalVariableWriteNode (location: (3,0)-(3,10))
+        │   │   ├── name: :foo
+        │   │   ├── depth: 0
+        │   │   ├── name_loc: (3,0)-(3,3) = "foo"
+        │   │   ├── value:
+        │   │   │   @ ArrayNode (location: (3,6)-(3,10))
+        │   │   │   ├── flags: ∅
+        │   │   │   ├── elements: (length: 2)
+        │   │   │   │   ├── @ IntegerNode (location: (3,6)-(3,7))
+        │   │   │   │   │   ├── flags: decimal
+        │   │   │   │   │   └── value: 1
+        │   │   │   │   └── @ IntegerNode (location: (3,9)-(3,10))
+        │   │   │   │       ├── flags: decimal
+        │   │   │   │       └── value: 2
+        │   │   │   ├── opening_loc: ∅
+        │   │   │   └── closing_loc: ∅
+        │   │   └── operator_loc: (3,4)-(3,5) = "="
+        │   ├── keyword_loc: (3,11)-(3,17) = "rescue"
+        │   └── rescue_expression:
+        │       @ NilNode (location: (3,18)-(3,21))
+        └── @ MultiWriteNode (location: (4,0)-(4,26))
+            ├── lefts: (length: 2)
+            │   ├── @ LocalVariableTargetNode (location: (4,0)-(4,3))
+            │   │   ├── name: :foo
+            │   │   └── depth: 0
+            │   └── @ LocalVariableTargetNode (location: (4,5)-(4,8))
+            │       ├── name: :bar
+            │       └── depth: 0
+            ├── rest: ∅
+            ├── rights: (length: 0)
+            ├── lparen_loc: ∅
+            ├── rparen_loc: ∅
+            ├── operator_loc: (4,9)-(4,10) = "="
+            └── value:
+                @ RescueModifierNode (location: (4,11)-(4,26))
+                ├── expression:
+                │   @ ArrayNode (location: (4,11)-(4,15))
+                │   ├── flags: ∅
+                │   ├── elements: (length: 2)
+                │   │   ├── @ IntegerNode (location: (4,11)-(4,12))
+                │   │   │   ├── flags: decimal
+                │   │   │   └── value: 1
+                │   │   └── @ IntegerNode (location: (4,14)-(4,15))
+                │   │       ├── flags: decimal
+                │   │       └── value: 2
+                │   ├── opening_loc: ∅
+                │   └── closing_loc: ∅
+                ├── keyword_loc: (4,16)-(4,22) = "rescue"
+                └── rescue_expression:
+                    @ NilNode (location: (4,23)-(4,26))