You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
runtime: complete String.prototype.matchAll iterator and validation semantics #2845
String.prototype.matchAll is implemented enough to return a materialized array of capture arrays, but it does not match Node's observable matchAll contract. Node returns an iterator, rejects non-global RegExp arguments, accepts string/non-RegExp patterns by constructing a global RegExp, starts from the RegExp's lastIndex, and each match result exposes metadata such as index, input, and groups.
This is separate from closed #317 / PR #331, which fixed the earlier hard codegen failure for basic matchAll use.
Expected Node behavior
On Node v25.9.0:
Array.from("a1b2".matchAll(/(\d)/g),m=>[m[0],m[1],m.index]);// [["1", "1", 1], ["2", "2", 3]]"a".matchAll(/a/);// TypeError: String.prototype.matchAll called with a non-global RegExp argumentArray.from("a.a".matchAll("."),m=>[m[0],m.index]);// [["a", 0], [".", 1], ["a", 2]]constre=/a/g;re.lastIndex=2;constseen=Array.from("a_a".matchAll(re),m=>m.index);// seen === [2]// re.lastIndex is still 2
Current Perry evidence
Current origin/main has a runtime helper, but it is a simplified array materializer:
crates/perry-codegen/src/lower_string_method.rs lowers str.matchAll(arg) by unboxing arg as a regex handle and calling js_string_match_all(str, re).
crates/perry-runtime/src/regex.rs::js_string_match_all() returns *mut ArrayHeader, not an iterator object.
If the receiver or regex pointer is invalid, js_string_match_all() returns an empty array instead of applying RegExpCreate semantics for string/non-RegExp arguments or throwing for invalid non-global RegExp input.
The helper uses regex.captures_iter(str_data) over the whole string, with no visible check for the g flag and no use of a RegExp lastIndex starting point.
Inner match arrays store only full match and capture-group slots. There is no visible population of Node's index, input, or groups properties.
Suggested test surface
Add parity tests for:
"a".matchAll(/a/) throwing TypeError.
Array.from("a.a".matchAll("."), m => [m[0], m.index]) matching Node's string-pattern behavior.
const re = /a/g; re.lastIndex = 2; Array.from("a_a".matchAll(re), m => m.index) starting from index 2 without mutating re.lastIndex.
match result metadata: m.index, m.input, m.groups, and capture group values.
iterator shape: typeof iter.next === "function" and results are produced lazily through .next().
Scope / non-goals
This issue is only for String.prototype.matchAll argument validation, iterator shape, start-index behavior, and match-result metadata. Broader RegExp flag execution gaps such as sticky/dotAll/unicode/indices are tracked separately by #2828.
Activity
andrewtdiz commentedon Jun 3, 2026
I’m taking this as a focused runtime compatibility cut: matchAll iterator shape, non-global RegExp validation, string-pattern behavior, lastIndex start handling, and match-result metadata. I’ll keep broader RegExp execution semantics out of scope.
andrewtdiz commentedon Jun 3, 2026
Draft PR opened for this cut: #4223
Scope covered:
String.prototype.matchAllnow returns a RegExp String Iterator object.undefinedpatterns route through global RegExp construction.index,input, and owngroupsmetadata, and the original regexlastIndexis honored without mutation.Validation is listed in the PR body.