Repository: ruby/prism
Pull request #1702 (2023-10-17 17:44:12)

Use the unescaped regexp to scan for capture groups
Fixes #1536


Patch:
diff --git a/src/prism.c b/src/prism.c
index 34c4abf9..ea22dc1b 100644
--- a/src/prism.c
+++ b/src/prism.c
@@ -5006,10 +5006,11 @@ pm_parser_local_add_token(pm_parser_t *parser, pm_token_t *token) {
 }
 
 // Add a local variable from an owned string to the current scope.
-static inline void
+static pm_constant_id_t
 pm_parser_local_add_owned(pm_parser_t *parser, const uint8_t *start, size_t length) {
     pm_constant_id_t constant_id = pm_parser_constant_id_owned(parser, start, length);
     if (constant_id != 0) pm_parser_local_add(parser, constant_id);
+    return constant_id;
 }
 
 // Add a parameter name to the current scope and check whether the name of the
@@ -14863,15 +14864,31 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
                 pm_string_list_t named_captures;
                 pm_string_list_init(&named_captures);
 
-                const pm_location_t *content_loc = &((pm_regular_expression_node_t *) node)->content_loc;
-                if (pm_regexp_named_capture_group_names(content_loc->start, (size_t) (content_loc->end - content_loc->start), &named_captures, parser->encoding_changed, &parser->encoding) && (named_captures.length > 0)) {
+                const pm_string_t *unescaped = &((pm_regular_expression_node_t *) node)->unescaped;
+                if (pm_regexp_named_capture_group_names(pm_string_source(unescaped), pm_string_length(unescaped), &named_captures, parser->encoding_changed, &parser->encoding) && (named_captures.length > 0)) {
                     pm_match_write_node_t *match = pm_match_write_node_create(parser, call);
 
                     for (size_t index = 0; index < named_captures.length; index++) {
                         pm_string_t *name = &named_captures.strings[index];
-                        assert(name->type == PM_STRING_SHARED);
+                        pm_constant_id_t local;
+
+                        if (unescaped->type == PM_STRING_SHARED) {
+                            // If the unescaped string is a slice of the source,
+                            // then we can copy the names directly. The pointers
+                            // will line up.
+                            local = pm_parser_local_add_location(parser, name->source, name->source + name->length);
+                        } else {
+                            // Otherwise, the name is a slice of the malloc-ed
+                            // owned string, in which case we need to copy it
+                            // out into a new string.
+                            size_t length = pm_string_length(name);
+
+                            void *memory = malloc(length);
+                            memcpy(memory, pm_string_source(name), length);
+
+                            local = pm_parser_local_add_owned(parser, (const uint8_t *) memory, length);
+                        }
 
-                        pm_constant_id_t local = pm_parser_local_add_location(parser, name->source, name->source + name->length);
                         pm_constant_id_list_append(&match->locals, local);
                     }
 


Test patch:
diff --git a/test/prism/fixtures/regex.txt b/test/prism/fixtures/regex.txt
index 1e917e7d..1f33b810 100644
--- a/test/prism/fixtures/regex.txt
+++ b/test/prism/fixtures/regex.txt
@@ -28,3 +28,6 @@ foo /bar/
 %r#pound#
 
 /aaa #{bbb}/o
+
+/(?<a\
+b>)/ =~ ""; ab
diff --git a/test/prism/snapshots/regex.txt b/test/prism/snapshots/regex.txt
index 5fa07265..dfc1e470 100644
--- a/test/prism/snapshots/regex.txt
+++ b/test/prism/snapshots/regex.txt
@@ -1,8 +1,8 @@
-@ ProgramNode (location: (1,0)-(30,13))
-├── locals: [:foo]
+@ ProgramNode (location: (1,0)-(33,14))
+├── locals: [:foo, :ab]
 └── statements:
-    @ StatementsNode (location: (1,0)-(30,13))
-    └── body: (length: 14)
+    @ StatementsNode (location: (1,0)-(33,14))
+    └── body: (length: 16)
         ├── @ CallNode (location: (1,0)-(1,9))
         │   ├── receiver: ∅
         │   ├── call_operator_loc: ∅
@@ -180,30 +180,60 @@
         │   ├── closing_loc: (28,8)-(28,9) = "#"
         │   ├── unescaped: "pound"
         │   └── flags: ∅
-        └── @ InterpolatedRegularExpressionNode (location: (30,0)-(30,13))
-            ├── opening_loc: (30,0)-(30,1) = "/"
-            ├── parts: (length: 2)
-            │   ├── @ StringNode (location: (30,1)-(30,5))
-            │   │   ├── flags: ∅
-            │   │   ├── opening_loc: ∅
-            │   │   ├── content_loc: (30,1)-(30,5) = "aaa "
-            │   │   ├── closing_loc: ∅
-            │   │   └── unescaped: "aaa "
-            │   └── @ EmbeddedStatementsNode (location: (30,5)-(30,11))
-            │       ├── opening_loc: (30,5)-(30,7) = "\#{"
-            │       ├── statements:
-            │       │   @ StatementsNode (location: (30,7)-(30,10))
-            │       │   └── body: (length: 1)
-            │       │       └── @ CallNode (location: (30,7)-(30,10))
-            │       │           ├── receiver: ∅
-            │       │           ├── call_operator_loc: ∅
-            │       │           ├── message_loc: (30,7)-(30,10) = "bbb"
-            │       │           ├── opening_loc: ∅
-            │       │           ├── arguments: ∅
-            │       │           ├── closing_loc: ∅
-            │       │           ├── block: ∅
-            │       │           ├── flags: variable_call
-            │       │           └── name: :bbb
-            │       └── closing_loc: (30,10)-(30,11) = "}"
-            ├── closing_loc: (30,11)-(30,13) = "/o"
-            └── flags: once
+        ├── @ InterpolatedRegularExpressionNode (location: (30,0)-(30,13))
+        │   ├── opening_loc: (30,0)-(30,1) = "/"
+        │   ├── parts: (length: 2)
+        │   │   ├── @ StringNode (location: (30,1)-(30,5))
+        │   │   │   ├── flags: ∅
+        │   │   │   ├── opening_loc: ∅
+        │   │   │   ├── content_loc: (30,1)-(30,5) = "aaa "
+        │   │   │   ├── closing_loc: ∅
+        │   │   │   └── unescaped: "aaa "
+        │   │   └── @ EmbeddedStatementsNode (location: (30,5)-(30,11))
+        │   │       ├── opening_loc: (30,5)-(30,7) = "\#{"
+        │   │       ├── statements:
+        │   │       │   @ StatementsNode (location: (30,7)-(30,10))
+        │   │       │   └── body: (length: 1)
+        │   │       │       └── @ CallNode (location: (30,7)-(30,10))
+        │   │       │           ├── receiver: ∅
+        │   │       │           ├── call_operator_loc: ∅
+        │   │       │           ├── message_loc: (30,7)-(30,10) = "bbb"
+        │   │       │           ├── opening_loc: ∅
+        │   │       │           ├── arguments: ∅
+        │   │       │           ├── closing_loc: ∅
+        │   │       │           ├── block: ∅
+        │   │       │           ├── flags: variable_call
+        │   │       │           └── name: :bbb
+        │   │       └── closing_loc: (30,10)-(30,11) = "}"
+        │   ├── closing_loc: (30,11)-(30,13) = "/o"
+        │   └── flags: once
+        ├── @ MatchWriteNode (location: (32,0)-(33,10))
+        │   ├── call:
+        │   │   @ CallNode (location: (32,0)-(33,10))
+        │   │   ├── receiver:
+        │   │   │   @ RegularExpressionNode (location: (32,0)-(33,4))
+        │   │   │   ├── opening_loc: (32,0)-(32,1) = "/"
+        │   │   │   ├── content_loc: (32,1)-(33,3) = "(?<a\\\nb>)"
+        │   │   │   ├── closing_loc: (33,3)-(33,4) = "/"
+        │   │   │   ├── unescaped: "(?<ab>)"
+        │   │   │   └── flags: ∅
+        │   │   ├── call_operator_loc: ∅
+        │   │   ├── message_loc: (33,5)-(33,7) = "=~"
+        │   │   ├── opening_loc: ∅
+        │   │   ├── arguments:
+        │   │   │   @ ArgumentsNode (location: (33,8)-(33,10))
+        │   │   │   └── arguments: (length: 1)
+        │   │   │       └── @ StringNode (location: (33,8)-(33,10))
+        │   │   │           ├── flags: ∅
+        │   │   │           ├── opening_loc: (33,8)-(33,9) = "\""
+        │   │   │           ├── content_loc: (33,9)-(33,9) = ""
+        │   │   │           ├── closing_loc: (33,9)-(33,10) = "\""
+        │   │   │           └── unescaped: ""
+        │   │   ├── closing_loc: ∅
+        │   │   ├── block: ∅
+        │   │   ├── flags: ∅
+        │   │   └── name: :=~
+        │   └── locals: [:ab]
+        └── @ LocalVariableReadNode (location: (33,12)-(33,14))
+            ├── name: :ab
+            └── depth: 0