Repository: ruby/prism
Pull request #2643 (2024-03-28 19:48:53)

Allow writing to keywords with named captures if they are already locals


Patch:
diff --git a/src/prism.c b/src/prism.c
index 1cfcf704..db71a9c3 100644
--- a/src/prism.c
+++ b/src/prism.c
@@ -18107,9 +18107,6 @@ parse_regular_expression_named_capture(pm_parser_t *parser, const uint8_t *sourc
         cursor += width;
     }
 
-    // Finally, validate that the identifier is not a keywor.
-    if (pm_local_is_keyword((const char *) source, length)) return false;
-
     return cursor == source + length;
 }
 
@@ -18170,16 +18167,19 @@ parse_regular_expression_named_captures(pm_parser_t *parser, const pm_string_t *
                 if (pm_constant_id_list_includes(&names, name)) continue;
                 pm_constant_id_list_append(&names, name);
 
-                // Here we lazily create the MatchWriteNode since we know we're
-                // about to add a target.
-                if (match == NULL) match = pm_match_write_node_create(parser, call);
-
-                // First, find the depth of the local that is being assigned.
                 int depth;
                 if ((depth = pm_parser_local_depth_constant_id(parser, name)) == -1) {
+                    // If the identifier is not already a local, then we'll add
+                    // it to the local table unless it's a keyword.
+                    if (pm_local_is_keyword((const char *) source, length)) continue;
+
                     pm_parser_local_add(parser, name);
                 }
 
+                // Here we lazily create the MatchWriteNode since we know we're
+                // about to add a target.
+                if (match == NULL) match = pm_match_write_node_create(parser, call);
+
                 // Next, create the local variable target and add it to the
                 // list of targets for the match.
                 pm_node_t *target = (pm_node_t *) pm_local_variable_target_node_create_values(parser, &location, name, depth == -1 ? 0 : (uint32_t) depth);


Test patch:
diff --git a/test/prism/fixtures/regex.txt b/test/prism/fixtures/regex.txt
index 1010ffed..18200e5c 100644
--- a/test/prism/fixtures/regex.txt
+++ b/test/prism/fixtures/regex.txt
@@ -41,4 +41,6 @@ tap { /(?<a>)/ =~ to_s }
 
 /(?<foo>)/ =~ ""
 /(?<Foo>)/ =~ ""
+
 /(?<nil>)/ =~ ""
+def foo(nil:) = /(?<nil>)/ =~ ""
diff --git a/test/prism/snapshots/regex.txt b/test/prism/snapshots/regex.txt
index 44657260..ef576b8b 100644
--- a/test/prism/snapshots/regex.txt
+++ b/test/prism/snapshots/regex.txt
@@ -1,8 +1,8 @@
-@ ProgramNode (location: (1,0)-(44,16))
+@ ProgramNode (location: (1,0)-(46,32))
 ├── locals: [:foo, :ab, :abc, :a]
 └── statements:
-    @ StatementsNode (location: (1,0)-(44,16))
-    └── body: (length: 24)
+    @ StatementsNode (location: (1,0)-(46,32))
+    └── body: (length: 25)
         ├── @ CallNode (location: (1,0)-(1,9))
         │   ├── flags: ignore_visibility
         │   ├── receiver: ∅
@@ -425,28 +425,86 @@
         │   │           └── unescaped: ""
         │   ├── closing_loc: ∅
         │   └── block: ∅
-        └── @ CallNode (location: (44,0)-(44,16))
-            ├── flags: ∅
-            ├── receiver:
-            │   @ RegularExpressionNode (location: (44,0)-(44,10))
-            │   ├── flags: forced_us_ascii_encoding
-            │   ├── opening_loc: (44,0)-(44,1) = "/"
-            │   ├── content_loc: (44,1)-(44,9) = "(?<nil>)"
-            │   ├── closing_loc: (44,9)-(44,10) = "/"
-            │   └── unescaped: "(?<nil>)"
-            ├── call_operator_loc: ∅
-            ├── name: :=~
-            ├── message_loc: (44,11)-(44,13) = "=~"
-            ├── opening_loc: ∅
-            ├── arguments:
-            │   @ ArgumentsNode (location: (44,14)-(44,16))
-            │   ├── flags: ∅
-            │   └── arguments: (length: 1)
-            │       └── @ StringNode (location: (44,14)-(44,16))
-            │           ├── flags: ∅
-            │           ├── opening_loc: (44,14)-(44,15) = "\""
-            │           ├── content_loc: (44,15)-(44,15) = ""
-            │           ├── closing_loc: (44,15)-(44,16) = "\""
-            │           └── unescaped: ""
-            ├── closing_loc: ∅
-            └── block: ∅
+        ├── @ CallNode (location: (45,0)-(45,16))
+        │   ├── flags: ∅
+        │   ├── receiver:
+        │   │   @ RegularExpressionNode (location: (45,0)-(45,10))
+        │   │   ├── flags: forced_us_ascii_encoding
+        │   │   ├── opening_loc: (45,0)-(45,1) = "/"
+        │   │   ├── content_loc: (45,1)-(45,9) = "(?<nil>)"
+        │   │   ├── closing_loc: (45,9)-(45,10) = "/"
+        │   │   └── unescaped: "(?<nil>)"
+        │   ├── call_operator_loc: ∅
+        │   ├── name: :=~
+        │   ├── message_loc: (45,11)-(45,13) = "=~"
+        │   ├── opening_loc: ∅
+        │   ├── arguments:
+        │   │   @ ArgumentsNode (location: (45,14)-(45,16))
+        │   │   ├── flags: ∅
+        │   │   └── arguments: (length: 1)
+        │   │       └── @ StringNode (location: (45,14)-(45,16))
+        │   │           ├── flags: ∅
+        │   │           ├── opening_loc: (45,14)-(45,15) = "\""
+        │   │           ├── content_loc: (45,15)-(45,15) = ""
+        │   │           ├── closing_loc: (45,15)-(45,16) = "\""
+        │   │           └── unescaped: ""
+        │   ├── closing_loc: ∅
+        │   └── block: ∅
+        └── @ DefNode (location: (46,0)-(46,32))
+            ├── name: :foo
+            ├── name_loc: (46,4)-(46,7) = "foo"
+            ├── receiver: ∅
+            ├── parameters:
+            │   @ ParametersNode (location: (46,8)-(46,12))
+            │   ├── requireds: (length: 0)
+            │   ├── optionals: (length: 0)
+            │   ├── rest: ∅
+            │   ├── posts: (length: 0)
+            │   ├── keywords: (length: 1)
+            │   │   └── @ RequiredKeywordParameterNode (location: (46,8)-(46,12))
+            │   │       ├── flags: ∅
+            │   │       ├── name: :nil
+            │   │       └── name_loc: (46,8)-(46,12) = "nil:"
+            │   ├── keyword_rest: ∅
+            │   └── block: ∅
+            ├── body:
+            │   @ StatementsNode (location: (46,16)-(46,32))
+            │   └── body: (length: 1)
+            │       └── @ MatchWriteNode (location: (46,16)-(46,32))
+            │           ├── call:
+            │           │   @ CallNode (location: (46,16)-(46,32))
+            │           │   ├── flags: ∅
+            │           │   ├── receiver:
+            │           │   │   @ RegularExpressionNode (location: (46,16)-(46,26))
+            │           │   │   ├── flags: forced_us_ascii_encoding
+            │           │   │   ├── opening_loc: (46,16)-(46,17) = "/"
+            │           │   │   ├── content_loc: (46,17)-(46,25) = "(?<nil>)"
+            │           │   │   ├── closing_loc: (46,25)-(46,26) = "/"
+            │           │   │   └── unescaped: "(?<nil>)"
+            │           │   ├── call_operator_loc: ∅
+            │           │   ├── name: :=~
+            │           │   ├── message_loc: (46,27)-(46,29) = "=~"
+            │           │   ├── opening_loc: ∅
+            │           │   ├── arguments:
+            │           │   │   @ ArgumentsNode (location: (46,30)-(46,32))
+            │           │   │   ├── flags: ∅
+            │           │   │   └── arguments: (length: 1)
+            │           │   │       └── @ StringNode (location: (46,30)-(46,32))
+            │           │   │           ├── flags: ∅
+            │           │   │           ├── opening_loc: (46,30)-(46,31) = "\""
+            │           │   │           ├── content_loc: (46,31)-(46,31) = ""
+            │           │   │           ├── closing_loc: (46,31)-(46,32) = "\""
+            │           │   │           └── unescaped: ""
+            │           │   ├── closing_loc: ∅
+            │           │   └── block: ∅
+            │           └── targets: (length: 1)
+            │               └── @ LocalVariableTargetNode (location: (46,20)-(46,23))
+            │                   ├── name: :nil
+            │                   └── depth: 0
+            ├── locals: [:nil]
+            ├── def_keyword_loc: (46,0)-(46,3) = "def"
+            ├── operator_loc: ∅
+            ├── lparen_loc: (46,7)-(46,8) = "("
+            ├── rparen_loc: (46,12)-(46,13) = ")"
+            ├── equal_loc: (46,14)-(46,15) = "="
+            └── end_keyword_loc: ∅