Skip to content

fix(class): class extends Array sizes the instance and supports fill#5499

Merged
proggeramlug merged 1 commit into
mainfrom
fix/class-extends-array
Jun 21, 2026
Merged

fix(class): class extends Array sizes the instance and supports fill#5499
proggeramlug merged 1 commit into
mainfrom
fix/class-extends-array

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Problem

class X extends Array { constructor(n){ super(n); this.fill(0) } } threw TypeError: value is not a function at this.fill. perry models a subclass instance as a plain object, not a real exotic Array (ArrayHeader has only {length, capacity} — no class_id slot), so super(n) left the instance length-less with no Array methods: length=0, isArray=false, fill/push/map all undefined. (lru-cache's ZeroArray is the motivating shape — a fixed-size zero-initialised indexed buffer.)

Fix

The super() lowering for an Array parent (expr/this_super_call.rs) now calls a runtime js_array_subclass_init(this, n) that:

  • sets a visible length = ToLength(n) own property, and
  • installs the Array surface the instance relies on (currently fill) as a method delegating to the existing generic array-like impl js_array_fill_generic — which operates on the receiver's own length + indexed properties.

Indexed get/set already worked as ordinary object properties. This mirrors the EventEmitter subclass-init pattern (js_event_emitter_subclass_init). Additional Array methods can be added to the install list as needed.

Test

crates/perry/tests/issue_array_subclass_super_init.rsclass Zero extends Array with super(n); this.fill(0): asserts length, zero-initialised slots, indexed write, fill with a non-zero value, and typeof fill === 'function'.

Summary by CodeRabbit

  • New Features
    • Improved initialization for source-compiled classes that extend the built-in Array, including correct handling of super(n, ...) to set the instance length.
    • Ensured the instance fill method is installed and works correctly after super(n, ...), with subclass field initialization applied at the right time.
  • Bug Fixes
    • Prevented incorrect control flow for Array-parent super(...) lowering so it no longer falls through to unrelated paths.
  • Tests
    • Added an end-to-end regression test for Array subclasses using super(n) followed by this.fill(0), verifying length, indexed behavior, and independent instances.

@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds support for class X extends Array subclass constructors in Perry. A new runtime FFI function js_array_subclass_init sets length and installs a fill method on the subclass receiver. The codegen gains a dedicated super() lowering path for Array parents that emits this FFI call. A regression test validates the full behavior end-to-end.

Changes

Array subclass super() initialization

Layer / File(s) Summary
Runtime FFI: js_array_subclass_init and ns_array_fill
crates/perry-codegen/src/runtime_decls/stdlib_ffi.rs, crates/perry-runtime/src/node_stream_constructors.rs
Declares js_array_subclass_init in the stdlib FFI table and implements it: validates this, floors a non-negative n into len, writes length, and installs ns_array_fill as the fill method. ns_array_fill delegates to js_array_fill_generic.
Codegen: lower_array_super_init helper and wiring
crates/perry-codegen/src/expr/write_barrier.rs, crates/perry-codegen/src/expr/mod.rs, crates/perry-codegen/src/expr/this_super_call.rs
Adds lower_array_super_init that emits a js_array_subclass_init call using the lowered first arg and current this. Re-exports the function via mod.rs and adjusts imports in this_super_call.rs.
SuperCall integration for Array parents
crates/perry-codegen/src/expr/this_super_call.rs
Adds a new early-exit branch in Expr::SuperCall lowering: when parent_name == "Array", it calls lower_array_super_init, applies field initializers with SelfOnly, and returns immediately to skip the later default handling.
Regression test
crates/perry/tests/issue_array_subclass_super_init.rs
Compiles a TypeScript Array subclass and runs the binary, asserting correct length, fill(0) without throw, indexed reads/writes, typeof fill, and independent second-instance fill.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • PerryTS/perry#5357: Both PRs modify crates/perry-codegen/src/expr/this_super_call.rs's Expr::SuperCall lowering logic to route super() differently for specific parent families via new dedicated handlers.

Poem

🐇 Hop hop, arrays spring to life anew,
super(n) sets the length right on cue,
fill finds its home on each subclass too,
No more thrown errors — the slots all fill through,
A regression test seals what the rabbit pursued! 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main changes: fixing Array subclass initialization and adding fill() method support.
Description check ✅ Passed The PR description is well-structured with Problem, Fix, and Test sections, clearly explaining the issue, solution, and verification approach.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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/class-extends-array

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/perry-runtime/src/node_stream_constructors.rs`:
- Around line 466-473: The ToLength implementation in the block starting with
`let nv = JSValue::from_bits(n.to_bits())` is incomplete. Currently it treats
all non-finite values as 0.0, but the ToLength specification requires Infinity
to clamp to 2^53-1 (the maximum safe integer length) instead of 0.0.
Additionally, positive finite values that exceed the maximum safe length should
also be clamped to 2^53-1 rather than used directly. Replace the simple
conditional logic with proper handling that checks if n is Infinity and returns
2^53-1, checks if the floored result exceeds 2^53-1 and clamps it, and only
returns 0.0 for undefined, NaN, or negative values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 68fb8a43-1a18-4f9e-a342-53a70b496f25

📥 Commits

Reviewing files that changed from the base of the PR and between 190f574 and 6abd8bf.

📒 Files selected for processing (6)
  • crates/perry-codegen/src/expr/mod.rs
  • crates/perry-codegen/src/expr/this_super_call.rs
  • crates/perry-codegen/src/expr/write_barrier.rs
  • crates/perry-codegen/src/runtime_decls/stdlib_ffi.rs
  • crates/perry-runtime/src/node_stream_constructors.rs
  • crates/perry/tests/issue_array_subclass_super_init.rs

Comment thread crates/perry-runtime/src/node_stream_constructors.rs
@proggeramlug proggeramlug force-pushed the fix/class-extends-array branch 2 times, most recently from ace1907 to e3c487d Compare June 21, 2026 06:31
`class X extends Array { constructor(n){ super(n); this.fill(0) } }` threw
`TypeError: value is not a function` at `this.fill`. perry models a subclass
instance as a plain object, not a real exotic Array (ArrayHeader has no class_id
slot), so `super(n)` left it length-less with no Array methods.

The super() lowering for an Array parent now calls a runtime
js_array_subclass_init(this, n) that sets a visible `length = ToLength(n)` and
installs the Array surface the instance relies on (currently `fill`), delegating
to the generic array-like impl (js_array_fill_generic, which operates on the
receiver's own length + indexed properties). Indexed get/set already work as
ordinary object properties. Mirrors the EventEmitter subclass-init pattern;
additional Array methods can be added as bundles need them. Adds an e2e test.
@proggeramlug

Copy link
Copy Markdown
Contributor Author

Addressed: js_array_subclass_init now applies proper ToLengthundefined/NaN/<= 0 → 0, +Infinity and values past 2^53-1 clamp to 2^53-1, otherwise floor(n).

@proggeramlug proggeramlug force-pushed the fix/class-extends-array branch from e3c487d to 1a5f1fa Compare June 21, 2026 06:36

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/perry-runtime/src/node_stream_constructors.rs`:
- Line 482: The `fill` method registration on line 482 uses
`cast1(ns_array_fill)` which only accepts a single value parameter and hardcodes
the start and end parameters as zeros when calling `js_array_fill_generic`,
causing the optional range arguments to be ignored. Replace `cast1` with an
appropriate multi-parameter wrapper (such as `cast3`) that properly accepts and
forwards the value, start, and end parameters to `js_array_fill_generic` instead
of hardcoding them, so that `subclass.fill(value, start, end)` respects the
optional range arguments as per Array.prototype.fill semantics.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 96f4a9f5-add1-4d5f-b3ea-2d031da58bcf

📥 Commits

Reviewing files that changed from the base of the PR and between e3c487d and 1a5f1fa.

📒 Files selected for processing (6)
  • crates/perry-codegen/src/expr/mod.rs
  • crates/perry-codegen/src/expr/this_super_call.rs
  • crates/perry-codegen/src/expr/write_barrier.rs
  • crates/perry-codegen/src/runtime_decls/stdlib_ffi.rs
  • crates/perry-runtime/src/node_stream_constructors.rs
  • crates/perry/tests/issue_array_subclass_super_init.rs
🚧 Files skipped from review as they are similar to previous changes (5)
  • crates/perry-codegen/src/runtime_decls/stdlib_ffi.rs
  • crates/perry/tests/issue_array_subclass_super_init.rs
  • crates/perry-codegen/src/expr/this_super_call.rs
  • crates/perry-codegen/src/expr/write_barrier.rs
  • crates/perry-codegen/src/expr/mod.rs

let length_key = crate::string::js_string_from_bytes(b"length".as_ptr(), 6);
js_object_set_field_by_name(obj, length_key, len);
crate::closure::js_register_closure_arity(ns_array_fill as *const u8, 1);
let methods: [(&str, StubFn); 1] = [("fill", super::cast1(ns_array_fill))];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Expectation: the generic fill helper accepts value/start/end inputs, and the
# normal Array.fill wrapper, if present, threads optional start/end through it.
rg -nP -C4 '\b(js_array_fill_generic|ns_array_fill|array_fill)\s*\('

# Expectation: installed native method wrappers have a cast/helper that can pass
# optional call arguments while preserving JS function length metadata if needed.
rg -nP -C4 '\bcast[0-9]\(|js_register_closure_arity\(.*fill'

Repository: PerryTS/perry

Length of output: 47309


Thread fill's optional range arguments through this wrapper.

ns_array_fill is registered with arity 1 and wrapped using cast1, accepting only a value parameter. It hardcodes has_start=0, start=0.0, has_end=0, end=0.0 when calling js_array_fill_generic, which means subclass.fill(value, start, end) fills the entire receiver rather than the specified slice. To match Array.prototype.fill semantics, accept and forward the optional start and end parameters.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-runtime/src/node_stream_constructors.rs` at line 482, The `fill`
method registration on line 482 uses `cast1(ns_array_fill)` which only accepts a
single value parameter and hardcodes the start and end parameters as zeros when
calling `js_array_fill_generic`, causing the optional range arguments to be
ignored. Replace `cast1` with an appropriate multi-parameter wrapper (such as
`cast3`) that properly accepts and forwards the value, start, and end parameters
to `js_array_fill_generic` instead of hardcoding them, so that
`subclass.fill(value, start, end)` respects the optional range arguments as per
Array.prototype.fill semantics.

@proggeramlug proggeramlug merged commit ad36446 into main Jun 21, 2026
15 checks passed
@proggeramlug proggeramlug deleted the fix/class-extends-array branch June 21, 2026 06:46
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