Skip to content

split by a fancy-regex-only pattern never runs RegExp.prototype[@@split]: trailing "" and captured groups dropped — widened by #9427 #9438

Description

@proggeramlug

A third, independent root cause in the family of #9429 / #9430, found while fixing those two but deliberately left out of PR #9437.

When a split pattern can only be compiled by fancy-regex (look-around, backreferences), perry does not run RegExp.prototype [ @@split ] at all. The fallback walks find_iter and slices between matches, which differs from the spec in two ways:

"a,b,".split(/(?<=,)/)      // node ["a,","b,"]        perry ["a,","b,",""]
"aXbXc".split(/((?<=a)X)/)  // node ["a","X","bXc"]    perry ["a","bXc"]
  1. A trailing "" the spec never produces. @@split's loop is bounded by q < size, so a match ending at the end of the subject cannot open a final empty chunk. Slicing between matches emits one.
  2. Captured groups are not spliced into the result. @@split interleaves every capture of the separator match; the fallback discards them.

#9427 widened the reach of this

#9427 fixes multiline anchors by rewriting ^ into (?:\A|(?<=[\n\r

])) and $ into (?:\z|(?=[…])). That rewrite makes the pattern lookaround-bearing, which moves it onto the fancy-regex lane — so patterns that previously took the correct path now take this one:

"a\r\nb".split(/^/gm)   // gains a spurious LEADING "" after #9427

Both directions measured on a tree with #9427 merged. This is not an argument against #9427 — its own fixture is byte-identical to node — but it means the gap now reaches ordinary /m code rather than only explicit look-around.

A runtime test currently pins the wrong answer

fancy_lookbehind_split in the regex tests asserts the fallback's output, so it will fail when this is fixed. That assertion needs to be replaced with node's, not preserved.

Fix shape

Route the fancy lane through the same @@split algorithm the other lanes use: honour the q < size bound, and splice 1 .. captures.len() into the output after each separator match. The positional search added in PR #9437 (captures_from_pos on the full haystack) is the primitive this needs — a sliced search cannot implement @@split's q/p cursors correctly for look-around patterns, which is why the two changes are ordered this way.

Verification bar

A gap fixture demonstrated failing against a compiler built from unfixed origin/main, byte-compared to node --experimental-strip-types, covering: lookbehind and lookahead separators, with and without capture groups, matches at the start and end of the subject, an empty subject, a limit argument, and the /^/gm and /$/gm shapes that #9427 rewrites onto this lane.

Found by the differential stress-test of claude-code under perry.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions