Skip to content

fix(runtime): built-ins/RegExp (non-lookbehind) test262 parity - #4809

Merged
proggeramlug merged 1 commit into
mainfrom
regexp-parity
Jun 8, 2026
Merged

fix(runtime): built-ins/RegExp (non-lookbehind) test262 parity#4809
proggeramlug merged 1 commit into
mainfrom
regexp-parity

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Summary

Raises built-ins/RegExp test262 parity from 71.0% → 92.9% (421 → 551 pass, +130) measured against the branch's merge-base, with zero regressions validated across a broad built-ins + language shard.

Validation (merge-base dcfdfd8f3 vs branch, same pinned test262, jobs=40)

Suite Baseline Branch
built-ins/RegExp 421 pass / 71.0% 551 pass / 92.9%
broad shard built-ins+language (1/12, strided) 1999 pass 2013 pass

Failure-set diff on the broad shard: 0 tests went pass→fail (zero regressions); 14 newly-passing (13 RegExp + 1 Proxy bonus from the constructor/source fixes).

Root causes fixed (grouped)

  1. RegExp.prototype accessor getterssource, flags, global, ignoreCase, multiline, dotAll, sticky, unicode, unicodeSets, hasIndices are now real accessor descriptors on RegExp.prototype (brand-checked native getters; set: undefined, enumerable: false, configurable: true). Reflection (getOwnPropertyDescriptor(RegExp.prototype, "source").get) and .call(this) work, and instances inherit them. New module object/regex_proto_thunks.rs. The flags getter is spec-generic (assembles d g i m s u v y from Get+ToBoolean on the receiver); a Symbol/non-object this throws TypeError.
  2. source escapingEscapeRegExpPattern: empty → (?:), /\/ outside a char class, line terminators escaped.
  3. Sticky yexec/test consult and advance lastIndex and anchor the match for sticky (previously only global was stateful); test routes through exec for stateful regexes.
  4. lastIndex semantics — now stores an arbitrary value (re.lastIndex = obj round-trips) with ToLength applied on read, per spec, instead of a coerced u32.
  5. exec/test argument ToString — String wrappers / numbers / objects (incl. a throwing toString) are coerced via new js_jsvalue_to_string_coerce (undefined"undefined", null"null", objects dispatch toString).
  6. Brand-checked exec/test/toString prototype methods.
  7. Full RegExp constructor (js_regexp_construct) — RegExp pattern (copy / flag override), undefined/null/object pattern (ToString), object flags (ToStringSyntaxError). Fixes garbage output from a RegExp pattern being read as a string, and a HIR fold that silently dropped object-literal flags.
  8. Empty character classes[] (never matches) and [^] (any) translate to [^\s\S] / [\s\S] (the regex crate rejects them).
  9. RegExp instances walk RegExp.prototype for inherited members (re.constructor, reflective method reads).

Lookbehind (out of scope)

18 built-ins/RegExp tests use lookbehind ((?<=…)/(?<!…)). The Rust regex crate doesn't support it; these are served by the pre-existing fancy-regex fallback and were not targeted here (skip=0 — none are among the remaining failures).

Remaining (not addressed — deep object-model or crate limits)

  • lastIndex as a full own property (hasOwnProperty/non-writable attr), expando storage on the non-ObjectHeader regex, and call-vs-new identity (RegExp(re) returns re) — require object-model changes.
  • u-mode strict early errors (lone ]/{, alpha identity escapes, incomplete quantifiers) — skipped to avoid false-positives on valid \p{…}/\u{…} patterns (zero-regression mandate).
  • regex-crate limits: lone surrogates, b{9007199254740991}, JS \s vs Unicode \s (U+0085).
  • propertyIsEnumerable over inherited accessor descriptors (attr plumbing); isPrototypeOf for built-in instances (pre-existing, also affects Map/Set).
  • prototype/source/value* tests rely on eval(), which Perry (AOT) cannot evaluate.

Files

regex.rs, regex/grammar.rs, regex/match_all.rs, object/regex_proto_thunks.rs (new), object/global_this.rs, object/field_get_set.rs, object/native_call_method.rs, object/mod.rs, value/to_string.rs, value/mod.rs; codegen: expr/instance_misc1.rs, expr/logical_collections.rs, lower_call/builtin.rs, runtime_decls/strings.rs, hir lower/expr_new.rs.

Raises built-ins/RegExp test262 parity 70.7% -> ~93% (419 -> 551 pass),
zero regressions. Root causes addressed:

- RegExp.prototype accessor getters: `source`/`flags`/`global`/`ignoreCase`/
  `multiline`/`dotAll`/`sticky`/`unicode`/`unicodeSets`/`hasIndices` are now
  real accessor descriptors on RegExp.prototype (brand-checked native getters,
  `set:undefined`, enumerable:false, configurable:true), so reflection
  (`getOwnPropertyDescriptor(RegExp.prototype,"source").get`) and
  `.call(this)` work and instances inherit them. New `regex_proto_thunks.rs`.
  The `flags` getter is spec-generic (reads d/g/i/m/s/u/v/y off the receiver
  via Get+ToBoolean); Symbol `this` throws TypeError.
- `source` now applies EscapeRegExpPattern (empty -> "(?:)", `/`->`\/` outside
  a class, line terminators escaped).
- exec/test: honor `lastIndex` + anchoring for the sticky `y` flag (not just
  global); `test` routes through `exec` for stateful regexes. `lastIndex` now
  stores an arbitrary value (ToLength on read) per spec, not a u32.
- exec/test ToString-coerce their argument (String wrappers, numbers, objects
  with throwing toString) via new `js_jsvalue_to_string_coerce`
  (undefined->"undefined", null->"null").
- Real brand-checked `exec`/`test`/`toString` prototype methods.
- Full RegExp constructor (`js_regexp_construct`): RegExp pattern (copy / flag
  override), undefined/null/object pattern (ToString), object flags
  (ToString -> SyntaxError). Fixes garbage from a regexp pattern being read as
  a string, and a HIR fold that dropped object-literal flags.
- Empty character classes `[]` (never matches) and `[^]` (any) translate to
  `[^\s\S]` / `[\s\S]` (the `regex` crate rejects them).
- instances walk RegExp.prototype for inherited members (`re.constructor`).

Lookbehind is out of scope (the `regex` crate lacks it; `fancy-regex` covers
some cases via the existing fallback) and was not targeted. Remaining failures
are deep object-model items (lastIndex as a full own property, expando storage
on the non-ObjectHeader regex, call-vs-new identity), `u`-mode strict early
errors, and `regex`-crate limits (lone surrogates, huge quantifiers, JS `\s`
vs Unicode `\s`), plus eval-based source tests Perry cannot AOT-evaluate.

Files: regex.rs, regex/grammar.rs, regex/match_all.rs,
object/regex_proto_thunks.rs (new), object/global_this.rs,
object/field_get_set.rs, object/native_call_method.rs, value/to_string.rs,
value/mod.rs, and codegen (instance_misc1.rs, logical_collections.rs,
lower_call/builtin.rs, runtime_decls/strings.rs, hir lower/expr_new.rs).
@proggeramlug
proggeramlug merged commit c1e47cb into main Jun 8, 2026
1 check passed
@proggeramlug
proggeramlug deleted the regexp-parity branch June 8, 2026 16:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant