fix(runtime): subclasses of built-ins inherit Symbol.species (#11193) - #11525
Merged
Merged
Conversation
`class X extends Array {}` read `X[Symbol.species]` as undefined: a class
ref's symbol lookup never reached the built-in constructor its chain ends in,
because a built-in parent is recorded only as a reserved class id. Walk the
chain for the stashed built-in parent value and read the symbol off it with
the original receiver, so the inherited getter answers the subclass.
With the species found, RegExp split/matchAll construct through the subclass,
which exposed that the synthesized default constructor of a class extending
an exotic built-in (RegExp, ArrayBuffer, typed arrays) never constructed the
built-in. Every dynamic construct of such a class got a plain object with no
[[RegExpMatcher]]. Emit the built-in's Construct there, as the inline `new`
and an explicit `super()` already do.
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (8)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
proggeramlug
pushed a commit
that referenced
this pull request
Sep 27, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017DJy7JpaPhRaPy7vPbfJJh
proggeramlug
force-pushed
the
claude/dreamy-davinci-hgybgs
branch
from
September 27, 2026 13:02
3adc1ac to
848f703
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The issue's own repro already matches Node on
main: #11198 addedBuffer[Symbol.species](FastBuffer) and #11338 added the accessors on the built-in constructors. The part still open, noted in #11338, was user subclasses. Forclass X extends Array {}(andMap,Set,Promise,RegExp,ArrayBuffer,Uint8Array, …),X[Symbol.species]was stillundefined. This PR makes it answerX.Finding the species made RegExp
splitandmatchAllconstruct through the subclass. That exposed an older bug: constructing a RegExp, typed-array or ArrayBuffer subclass with no constructor of its own through a value never built the built-in. So this PR also fixes that; without it,"a,b".split(new MyRegExp(","))would have started throwing.Changes
X[Symbol.species]on subclassessymbol/get.rs(class-ref arm ofjs_object_get_symbol_property_with_receiver): after the existing user-parent, class-expression-parent and function-parent steps, the lookup now reaches the built-in constructor the chain ends in. It reads the symbol there with the original receiver, so the inheritedget [Symbol.species]returns the subclass.0x7FFEtag and are excluded, soX.prototype[Symbol.species]staysundefined.object/class_registry/state.rs:builtin_parent_ctor_in_chainwalksget_parent_class_idand returns the first stashed parent value thatidentify_global_builtin_constructorrecognizes. It reuses the valuejs_register_class_parent_dynamicalready stashes, so there is no new table and no new root holder.codegen/method.rs: the synthesized standalone<Class>_constructorof a class with no constructor of its own skipped an exotic built-in base as "uncallable". That symbol is what every dynamic construct replays (new (R as any)(…),Reflect.construct, a speciesConstruct), so those got a plain object with no[[RegExpMatcher]]or buffer.js_builtin_subclass_construct(class_id, base, args)and rebindsthis, as the inlinenew R()and an explicitsuper()already do. Class fields still initialize afterwards on the real instance.lower_call/new_helpers.rs:exotic_builtin_base_in_chaincoversRegExp,ArrayBufferand the typed arrays. It shares the constructor-free chain walk withnative_instance_base_in_chain, now factored out asctorless_builtin_base.SharedArrayBufferis left out because that constructor already reaches it through the dynamic-parent super dispatch.test-files/test_gap_11193_subclass_species_inherited.ts.Still open, unchanged by this PR (same output before and after):
Arrayand typed-array species consumers (map,filter,slice, …) still return plain results for subclass instances, because their default fast paths never readconstructor.new (A as any)(3)of anArraysubclass still has length 0.flatMapon anArray-subclass instance is still "not a function".Related issue
Fixes #11193. The Buffer half landed in #11198 and the built-in accessors in #11338.
Test plan
Linux x64,
perry-devbuilds of this branch and ofmain(63e8997), made withcargo build --profile perry-dev -p perry -p perry-runtime-static -p perry-stdlib-static, run withPERRY_NO_AUTO_OPTIMIZE=1 PERRY_NO_CACHE=1, and each arm linking its own runtime archives.mainand this branch.main: every subclass row readsspecies is ctor: false typeof: undefined, then it throwstest is not a function.test_gap_11193_builtin_species_accessor.ts(from fix(runtime): install get [Symbol.species] on built-in constructors (#11193) #11338) is still byte-identical to Node.mainvs this branch: 213test-files/*.ts, every test that mentionsextends <built-in>,Symbol.,speciesorReflect.construct.http/nettests first failed to compile. Four parallel no-auto runtime rebuilds were racing, and one arm hit a stale-archive source-hash check. All three were rerun one at a time and are identical.cargo test --profile perry-dev -p perry-codegen --lib: 1755 passed.RUST_TEST_THREADS=1 cargo test --profile perry-dev -p perry-runtime --lib -- symbol class_registry species regex: 213 passed.cargo fmt --all -- --check,scripts/check_file_size.sh,scripts/addr_class_inventory.pyandscripts/gc_runtime_root_holders.pyare clean.Not run: the full gap suite, test262,
cargo test --workspace, auto-optimize builds and macOS. The local oracle was Node 22, not the pinned 26.5.1; the A/B compares the two Perry builds directly, so the Node version doesn't affect it.cargo test --workspace(left to CI)test-files/docs/src/(n/a)Checklist
fix:prefix conventionGenerated by Claude Code