Skip to content

fix(proxy): Array.prototype methods on a Proxy-wrapped array (#5196)#5208

Merged
proggeramlug merged 2 commits into
mainfrom
worktree-fix-5196-proxy-array-map
Jun 15, 2026
Merged

fix(proxy): Array.prototype methods on a Proxy-wrapped array (#5196)#5208
proggeramlug merged 2 commits into
mainfrom
worktree-fix-5196-proxy-array-map

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Fixes #5196.

Problem

A generic Array.prototype method (map, reduce, forEach, filter, join, sort, …) called on a Proxy-wrapped array either threw TypeError: Cannot convert undefined or null to object or SIGSEGV'd. Individual proxy traps worked; the array-method-on-proxy path did not.

const arr = new Proxy([1, 2, 3] as number[], { get(t, k, r) { return Reflect.get(t, k, r); } });
console.log(arr.map((x) => x * 10).join(",")); // expected 10,20,30

Two distinct gaps:

  1. HIRarr.<method>(...) on a proxy local was eagerly folded into dense Expr::Array* nodes (ArrayReduce/ArrayForEach/ArrayJoin/…) that dereference the proxy id as a real ArrayHeader → SIGSEGV. map/filter/find only escaped by luck via the is_class_instance gate (a proxy local is typed Named("Proxy")), which is why the reported map case threw rather than crashed.
  2. codegen — that escaped map case lowered to Call{callee: ProxyGet} and fell through to the plain closure-call path, losing the this receiver, so the method ran with this = undefined.

Fix

  • local_array_methods.rs — skip the array fast path entirely for proxy locals (ctx.proxy_locals), so all methods route uniformly through the proxy member-call path.
  • proxy_reflect.rs + lower_call/mod.rs — new try_lower_proxy_method_call: route proxy.method(args) (non call/apply) through js_native_call_method, whose Proxy arm binds this to the proxy.
  • native_call_method.rs — the Proxy arm short-circuits the non-mutating Array methods to the spec-generic array-like engine, whose al_get/al_length already route element/length reads through the proxy get trap. This gives correct semantics and avoids the depth-guard recursion the generic Get→Call fused path would hit on a built-in method value.
  • generic.rs + array/mod.rs — extracted the array-like dispatch table as dispatch_arraylike_read_method so both the proto-chain path and the proxy path share it.

Verification

Out of scope

Spreading a proxy array [...pa] still SIGSEGVs — that's the iterator-protocol (Symbol.iterator) path, independent of array-method dispatch and untouched here. Worth a separate follow-up.

No changelog entry or version bump (maintainer folds those in at merge).

Summary by CodeRabbit

  • New Features

    • Added support for calling methods on proxy objects (e.g., proxy.method(args)).
  • Bug Fixes

    • Fixed recursion depth-guard failures when calling array-like methods on proxy-wrapped arrays, improving reliability of array method operations on proxies.

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: efe1ac89-b023-4148-85ed-c8f69901d929

📥 Commits

Reviewing files that changed from the base of the PR and between f6faf2c and 4822477.

📒 Files selected for processing (6)
  • crates/perry-codegen/src/expr/proxy_reflect.rs
  • crates/perry-codegen/src/lower_call/mod.rs
  • crates/perry-hir/src/lower/expr_call/local_array_methods.rs
  • crates/perry-runtime/src/array/generic.rs
  • crates/perry-runtime/src/array/mod.rs
  • crates/perry-runtime/src/object/native_call_method.rs
🚧 Files skipped from review as they are similar to previous changes (6)
  • crates/perry-hir/src/lower/expr_call/local_array_methods.rs
  • crates/perry-codegen/src/lower_call/mod.rs
  • crates/perry-runtime/src/array/mod.rs
  • crates/perry-runtime/src/object/native_call_method.rs
  • crates/perry-codegen/src/expr/proxy_reflect.rs
  • crates/perry-runtime/src/array/generic.rs

📝 Walkthrough

Walkthrough

Fixes Array.prototype method calls on Proxy-wrapped arrays by extracting a shared dispatch_arraylike_read_method runtime helper, adding a Proxy fast-path in js_native_call_method, inserting an HIR guard to bypass the dense array fast-path for proxy locals, and emitting js_native_call_method for ProxyGet(p, method) call sites in codegen.

Changes

Proxy array method call dispatch

Layer / File(s) Summary
dispatch_arraylike_read_method helper and re-exports
crates/perry-runtime/src/array/generic.rs, crates/perry-runtime/src/array/mod.rs
Introduces dispatch_arraylike_read_method as a public function routing non-mutating Array.prototype method names to js_arraylike_* implementations, returning None for unsupported names. try_array_proto_chain_method delegates to it; the function is re-exported from array/mod.rs.
Proxy fast-path in js_native_call_method
crates/perry-runtime/src/object/native_call_method.rs
Inside the Proxy branch of js_native_call_method, inserts an early dispatch via dispatch_arraylike_read_method with refreshed_args(), returning immediately when handled before the existing Proxy Get→Call recursion path.
HIR proxy guard and codegen lowering
crates/perry-hir/src/lower/expr_call/local_array_methods.rs, crates/perry-codegen/src/expr/proxy_reflect.rs, crates/perry-codegen/src/lower_call/mod.rs
Adds a proxy_locals guard in try_local_array_methods that returns Ok(Err(args)) to bypass the dense array fast-path for proxy-wrapped locals. Introduces try_lower_proxy_method_call in proxy_reflect.rs to emit js_native_call_method for ProxyGet(p, method) call sites, wired as an early branch in lower_call.

Sequence Diagram(s)

sequenceDiagram
  actor Source as JS Source
  participant lower_call
  participant try_lower_proxy_method_call
  participant js_native_call_method
  participant dispatch_arraylike_read_method

  Source->>lower_call: proxy.map(fn) call site
  lower_call->>try_lower_proxy_method_call: callee=ProxyGet(proxy, "map"), args=[fn]
  try_lower_proxy_method_call-->>lower_call: emits js_native_call_method(proxy, "map", args)
  lower_call->>js_native_call_method: proxy + "map" + args at runtime
  js_native_call_method->>dispatch_arraylike_read_method: object=proxy, method="map", refreshed_args
  dispatch_arraylike_read_method-->>js_native_call_method: Option<f64> result
  js_native_call_method-->>Source: mapped array value
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • PerryTS/perry#5194: Also modifies try_local_array_methods to bypass the array fast-path and defer to js_native_call_method for certain Array.prototype calls, using the same Ok(Err(args)) fallthrough pattern.

Poem

🐇 A proxy array called map one day,
But the runtime just threw it away.
Now a fast-path is set,
No recursion to fret—
The mapped values hop happily away! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main fix: enabling Array.prototype methods on Proxy-wrapped arrays.
Description check ✅ Passed The description comprehensively documents the problem, the multi-layer fix, verification results, and scope boundaries, fully meeting template requirements.
Linked Issues check ✅ Passed All code changes directly address the linked issue #5196: the fix enables Array.prototype methods to work correctly on Proxy-wrapped arrays by addressing gaps in both HIR and codegen layers.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing array method dispatch on proxies; spreading a proxy array is explicitly noted as out of scope and deferred to a separate follow-up.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ 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 worktree-fix-5196-proxy-array-map

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

Ralph Küpper added 2 commits June 15, 2026 20:25
A generic Array method (`map`, `reduce`, `forEach`, `filter`, …) called on a
Proxy-wrapped array threw `TypeError: Cannot convert undefined or null to
object` or SIGSEGV'd. Two distinct gaps:

1. HIR: `arr.<method>(...)` on a proxy local was eagerly folded to dense
   `Expr::Array*` nodes (`ArrayReduce`/`ArrayForEach`/`ArrayJoin`/…) that
   dereference the proxy id as a real `ArrayHeader`. `map`/`filter`/`find`
   only escaped by luck via the `is_class_instance` gate. Skip the array
   fast path entirely for proxy locals so the call routes through the proxy
   member-call path.

2. codegen: a fused `proxy.method(args)` (callee `ProxyGet`) for a non
   call/apply name fell through to the closure-call path, losing the `this`
   receiver. Route it through `js_native_call_method`, whose Proxy arm binds
   `this` to the proxy.

3. runtime: the Proxy arm of `js_native_call_method` now short-circuits the
   non-mutating generic Array methods to the spec-generic array-like engine
   (extracted as `dispatch_arraylike_read_method`), whose `al_get`/`al_length`
   already route element/length reads through the proxy `get` trap. This both
   gives correct semantics and avoids the depth-guard recursion the generic
   Get→Call fused path would hit on a built-in method value.
…st drift

- cargo fmt the reordered `pub use` block in array/mod.rs (lint gate).
- Add the missing `method("commander", "args", true, None)` manifest row.
  `commander::args` landed in NATIVE_MODULE_TABLE via #5137 but only a
  `property` entry existed, so the manifest-drift guard
  (every_dispatch_entry_has_manifest_counterpart) failed for ANY branch off
  current main. Pre-existing, unrelated to #5196, but it blocks the cargo-test
  gate.
@proggeramlug proggeramlug force-pushed the worktree-fix-5196-proxy-array-map branch from f6faf2c to 4822477 Compare June 15, 2026 18:38
@proggeramlug proggeramlug merged commit b3a910c into main Jun 15, 2026
15 checks passed
@proggeramlug proggeramlug deleted the worktree-fix-5196-proxy-array-map branch June 15, 2026 21:04
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.

Array.prototype.map on a Proxy-wrapped array throws 'Cannot convert undefined or null to object'

1 participant