Skip to content

fix(runtime): dispatch inherited Array statics on a subclass constructor (#7541) - #7605

Merged
proggeramlug merged 2 commits into
mainfrom
fix/7541-array-subclass-inherited-statics
Aug 7, 2026
Merged

fix(runtime): dispatch inherited Array statics on a subclass constructor (#7541)#7605
proggeramlug merged 2 commits into
mainfrom
fix/7541-array-subclass-inherited-statics

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Fixes #7541.

Stacked on #7603 (fix/7574-array-subclass-raw-paths), and not by
convenience — see Why this is stacked below. Review the top commit only.

The bug is not in spread

class MyArr extends Array {}
const sub = MyArr.from([1, 2, 3]);
console.log([...sub]);                // node: [ 1, 2, 3 ]   perry: TypeError: value is not iterable
console.log(Array.isArray([...sub])); // node: true          perry: TypeError

The issue guessed that array_from_spread_value's subclass arm
(is_array_subclass_instance / array_subclass_has_iterator_override) was
answering wrong. It is not — a directly constructed instance spreads
correctly today, and did before this PR:

typeof MyArr.from  = undefined      <-- node: function
const d = new MyArr(); d.push(1,2,3);
[...d]                              -> [ 1, 2, 3 ]   (already correct)

MyArr.from resolves to nothing. Array.from / Array.of /
Array.isArray are folded in the HIR on the literal identifier Array
(perry-hir/src/lower/expr_call/array_only_methods.rs:293), so a subclass
receiver matches no fold and the call falls through to
js_class_static_method_call, whose documented miss-fallback returns the
receiver
. MyArr.from([1,2,3]) therefore evaluates to the class ref — which
is genuinely not iterable, so the spread throws exactly where the issue saw it.
This is the CLAUDE.md "native base-class subclassing… keying any of that on a
literal extends name loses it"
weak area, on the STATIC side.

Fix

js_class_static_method_call already has arms for inherited builtin statics on
a class X extends Promise (X.all / X.resolve) and on a
class X extends Buffer (nm_static_buffer_proto_chain). Add the Array one
beside them, gated on is_array_subclass_class_id(class_id) — the same bounded
class-chain walk the instance-side dispatch already uses.

Both spec statics are already implemented constructor-aware:
array::array_from_full(c, items, mapfn, thisArg) and
array::array_of_full(c, vals) run Construct(C, …) whenever
IsConstructor(this), and is_constructor_value recognizes an INT32 class ref.
So passing the subclass receiver through as this builds a real subclass
instance
MyArr.from(x) behaves as Array.from.call(MyArr, x) does in
node, rather than degrading to a plain Array. isArray needs no receiver and
routes to js_array_is_array.

Why this is stacked on #7603 (not merely convenient)

array_from_full's element install is CreateDataPropertyOrThrow, whose
implementation branches on jsv_is_array(result) — and js_array_is_array
answers true for an Array-subclass instance. So it calls
js_array_set_f64_extend on what is physically an ObjectHeader. On main
that is precisely the #7574 corruption this PR would newly start triggering:
without #7603 the fix half-works and then writes into the object header —

isArr true        <-- constructed a MyArr
len undefined     <-- elements and `length` never landed
[]

With #7603's funnel underneath, the same code is correct. Shipping this against
main alone would trade one wrong answer for a memory-safety hazard, so it
must land after #7603.

Validation (local; CI has a deep backlog, so local is what this rests on)

  • test-files/test_gap_7541_array_subclass_inherited_statics.ts
    byte-identical to node --experimental-strip-types (v26.5.1), exit 0
    , 19
    lines. Covers the issue's exact repro plus from with a mapFn, from a Set,
    from an array-like, of, isArray, an indirect subclass
    (class Indirect extends MyArr), and the whole iteration surface on a
    static-produced instance (for…of, spread, Array.from, destructuring,
    map, indexing, length). Controls: the base Array.from/Array.of
    intrinsics and an ordinary user-class inherited static are unchanged.
  • Sabotage: with crates/perry-runtime/ reverted in full to this PR's
    base (i.e. fix(runtime): an Array subclass in a base-typed binding was read as a raw header (#7574) #7603 present, this change absent) and the test file untouched,
    the same file exits 1 with TypeError: value is not iterable — the
    reported symptom, verbatim. Restored, it exits 0, byte-identical.
  • test_gap_7574_array_subclass_declared_base_type.ts still byte-identical on
    this branch.
  • cargo test -p perry-runtime: 1853 passed, 0 failed.
  • python3 scripts/raw_handle_debt.py: 998 (baseline 998).
    python3 scripts/addr_class_inventory.py,
    python3 scripts/class_id_collisions.py, ./scripts/check_file_size.sh,
    cargo fmt --all -- --check: all clean.

Scope — deliberately NOT fixed here

No version bump (maintainer bumps at merge).

Summary by CodeRabbit

  • Bug Fixes

    • Fixed inherited Array.from, Array.of, and Array.isArray behavior for Array subclasses.
    • Array.from and Array.of now correctly create instances of the subclass, including indirect subclasses.
    • Improved support for iterable, array-like, mapped, and empty-input scenarios.
  • Tests

    • Added regression coverage for subclass inheritance, iteration, destructuring, and related array operations.
  • Chores

    • Updated the project version to 0.5.1344.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5ca33ad9-09d7-45b7-8317-86503499f64d

📥 Commits

Reviewing files that changed from the base of the PR and between 87b5d39 and 780f5aa.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • CLAUDE.md
  • Cargo.toml
  • changelog.d/7605-array-subclass-inherited-statics.md
  • crates/perry-runtime/src/object/class_registry/parent_static.rs
  • test-files/test_gap_7541_array_subclass_inherited_statics.ts

📝 Walkthrough

Walkthrough

The runtime now dispatches inherited Array.from, Array.of, and Array.isArray calls for direct and indirect Array subclasses. Regression tests cover construction, iteration, mapping, array-like inputs, and unaffected statics. Version metadata and the changelog were updated.

Changes

Array subclass inherited statics

Layer / File(s) Summary
Runtime dispatch and regression coverage
crates/perry-runtime/..., test-files/test_gap_7541_array_subclass_inherited_statics.ts
Array subclass calls now use subclass-aware from and of construction and isArray checks. Tests cover direct and indirect subclasses, multiple input types, iteration, and base or unrelated statics.
Release documentation and version metadata
changelog.d/7605-array-subclass-inherited-statics.md, Cargo.toml, CLAUDE.md
The changelog records the behavior and remaining limitations. The workspace and documented current version are updated to 0.5.1344.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested labels: bug

Suggested reviewers: thehypnoo

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/7541-array-subclass-inherited-statics

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@proggeramlug
proggeramlug force-pushed the fix/7541-array-subclass-inherited-statics branch from 6c9bfd1 to 6766285 Compare August 7, 2026 22:09
@proggeramlug
proggeramlug force-pushed the fix/7574-array-subclass-raw-paths branch from 3bbb402 to 0fc7d7e Compare August 7, 2026 22:12
@proggeramlug
proggeramlug force-pushed the fix/7541-array-subclass-inherited-statics branch from 6766285 to 40f1d47 Compare August 7, 2026 22:12
@proggeramlug
proggeramlug force-pushed the fix/7574-array-subclass-raw-paths branch from 0fc7d7e to 008e65d Compare August 7, 2026 22:23
Base automatically changed from fix/7574-array-subclass-raw-paths to main August 7, 2026 22:23
@proggeramlug
proggeramlug force-pushed the fix/7541-array-subclass-inherited-statics branch from 40f1d47 to 780f5aa Compare August 7, 2026 22:28
@proggeramlug
proggeramlug merged commit 5899a24 into main Aug 7, 2026
@proggeramlug
proggeramlug deleted the fix/7541-array-subclass-inherited-statics branch August 7, 2026 22:28
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.

spread: [...arr] on a class X extends Array instance throws "value is not iterable"

1 participant