fix: dynamic array-method dispatch dropped the thisArg — extracted builtin method as callback with thisArg (#6658) - #6659
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThe runtime preserves explicit ChangesBuiltin callback and receiver semantics
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant ExtractedBuiltin
participant dispatch_handle
participant dispatch_arraylike_read_method
participant Callback
ExtractedBuiltin->>dispatch_handle: invoke array method with callback and thisArg
dispatch_handle->>dispatch_arraylike_read_method: forward method and arguments
dispatch_arraylike_read_method->>Callback: invoke callback with bound thisArg
Callback-->>ExtractedBuiltin: return iteration result
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 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 |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/perry/tests/issue_6652_global_proto_inherited_members.rs (1)
1-135: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winMove to a
cargo-test-visible unit test.As per coding guidelines:
crates/*/tests/*.rs: Prefer acceptance coverage incargo-test-visible unit tests because integration suites undercrates/*/tests/*.rsdo not run on every PR.While I recall a learning suggesting that end-to-end regression tests invoking the
perrybinary viaCARGO_BIN_EXE_perryshould be kept as integration tests under this directory, the project's coding guidelines explicitly mandate prioritizingcargo-test-visible unit tests to ensure they run on every PR. Please consider moving this test to comply with the guidelines or updating the guidelines if the policy has changed.🤖 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/tests/issue_6652_global_proto_inherited_members.rs` around lines 1 - 135, The regression coverage currently lives in the external integration test file and invokes the perry binary through compile_and_run, so it is not visible to the required cargo-test unit-test path. Move object_prototype_inherited_globals_match_node and runtime_created_global_member_access_matches_node, along with their necessary helpers, into the appropriate in-crate unit-test module while preserving their assertions and end-to-end behavior.Sources: Coding guidelines, Learnings
🤖 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/object/collection_proto_thunks.rs`:
- Around line 350-367: In the GC_TYPE_OBJECT branch, root obj with a
RuntimeHandleScope before calling js_string_from_bytes, then reload the receiver
from the rooted handle after allocation before invoking own_data_field_by_name
or reading class_id. Preserve the existing toString override and class-name
result behavior.
---
Outside diff comments:
In `@crates/perry/tests/issue_6652_global_proto_inherited_members.rs`:
- Around line 1-135: The regression coverage currently lives in the external
integration test file and invokes the perry binary through compile_and_run, so
it is not visible to the required cargo-test unit-test path. Move
object_prototype_inherited_globals_match_node and
runtime_created_global_member_access_matches_node, along with their necessary
helpers, into the appropriate in-crate unit-test module while preserving their
assertions and end-to-end behavior.
🪄 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: a745ec42-3b49-4a21-b48d-59d6eb68e12a
📒 Files selected for processing (7)
crates/perry-hir/src/lower/lower_expr/arm_ident.rscrates/perry-runtime/src/object/collection_proto_thunks.rscrates/perry-runtime/src/object/native_call_method/handle_methods.rscrates/perry/tests/issue_6652_global_proto_inherited_members.rscrates/perry/tests/issue_6658_extracted_builtin_method_thisarg.rstest-parity/node-suite/globals/global-object-prototype-inherited-members.tstest-parity/node-suite/object/extracted-builtin-method-callback-thisarg.ts
…n brand-check message (PerryTS#6658) An extracted builtin method used as a forEach callback with an explicit thisArg — @babel/types' alias-expansion loop, `e5 ? e5.forEach(t4.add, t4) : t4.add(r4)` (pi-bundle.mjs:203827) — threw "TypeError: Method Set.prototype.add called on incompatible receiver" during pi-native module init; node prints "sizes: 3 1". pi bring-up wall PerryTS#7 (tracker PerryTS#6564). Root cause (NOT the ternary, NOT extraction, NOT a plain forEach-thisArg gap): the DYNAMIC method-dispatch tower (js_native_call_method's dense-array arms in native_call_method/handle_methods.rs) dropped args[1] for the whole Array.prototype callback family (forEach/map/filter/some/ every/find/findIndex/findLast/findLastIndex) and dispatched to the dense helpers, which bind the callback's `this` to undefined (the spec rule for an ABSENT thisArg). The STATIC lowering already routed explicit-thisArg calls through the this-binding js_arraylike_* engine — a receiver only reaches the dynamic tower when codegen can't prove its type (in the bundle: an object member read through a DYNAMIC key), which is why every statically-provable simplification of the shape worked and only the combined form reproduced. The extracted t4.add resolves to the brand-checking Set.prototype.add thunk (identity-preserving, PerryTS#3662), reads IMPLICIT_THIS = undefined, and throws; a lexically-capturing closure callback silently ran with the wrong `this` instead. Fix: mirror the static rule in the dynamic tower — with a thisArg present (args_len >= 2), route the nine-method family through dispatch_arraylike_read_method, whose ThisGuard binds the thisArg for each callback call (real-array receivers keep a fast element path). flatMap has no engine entry and shares the same thisArg gap on the STATIC path too; left untouched here. Also: node appends V8's NoSideEffectsToString rendering of the receiver to the brand-check TypeError ("... incompatible receiver #<Object>" / "undefined" / "[object Array]" / "#<Foo>"). The collection thunks' throw_incompatible_receiver now does the same — primitives print their ToString, default-toString objects print #<CtorName>, toString-overriding receivers print the [object Tag] fallback, errors print "Name: message" — so the message is byte-identical to node for the throwing cases. Tests: crates/perry/tests/issue_6658_extracted_builtin_method_thisarg.rs (the issue's combined shape verbatim, the five previously-ruled-out simpler shapes as anchors, the minimal dynamic-key trigger with Set.prototype.add / Map.prototype.set / Array.prototype.push extracted as callbacks, this-binding across the whole nine-method family, and eight message-identical brand-check throw cases) + node-suite parity fixture test-parity/node-suite/object/extracted-builtin-method-callback-thisarg.ts (runner-verified 100% vs node v26). perry-runtime lib suite 1426/0 (--test-threads=1); perry-codegen 209/0; perry-hir 246/0; object-module node-suite 26/26. Fixes PerryTS#6658 Claude-Session: https://claude.ai/code/session_01JuiiePQfrXhAFD9fuCygB9
fbc4038 to
338f87e
Compare
…tible-receiver rendering (CodeRabbit on PerryTS#6659) Claude-Session: https://claude.ai/code/session_01JuiiePQfrXhAFD9fuCygB9
…tible-receiver rendering (CodeRabbit on PerryTS#6659) Claude-Session: https://claude.ai/code/session_01JuiiePQfrXhAFD9fuCygB9
c253e68 to
a835ae9
Compare
|
CodeRabbit's finding fixed in c8-amended head: the receiver is now rooted in a RuntimeHandleScope across the |
…_thunks (#6659) (#6722) #6659 (fac2bbe) added a `(ptr as *const u8).sub(GC_HEADER_SIZE) as *const GcHeader` obj_type probe in collection_proto_thunks.rs without an addr-class allowlist entry, so the address-classification audit (the `lint` gate) went red on main and on every open PR branched from it. The probe is correctly magnitude-classified — it runs only inside `if is_above_handle_band(ptr) && is_valid_obj_ptr(ptr as *const u8)`, so the pointer is never a handle-band small integer at the deref. Add the standard allowlist entry (same shape as the sibling object/* probes) with a follow-up note to migrate it to `addr_class::try_read_gc_header`. Restores lint to green; no functional change. Co-authored-by: Ralph <ralph@skelpo.com>
Root cause
Pi bring-up wall #7 (tracker #6564). At pi-bundle.mjs:203827 (@babel/types' alias-expansion loop),
e5 ? e5.forEach(t4.add, t4) : t4.add(r4)threwTypeError: Method Set.prototype.add called on incompatible receiver; node printssizes: 3 1.It is NOT the ternary, NOT method extraction, and NOT a plain forEach-thisArg gap (the issue's five ruled-out shapes all work): the dynamic method-dispatch tower (
js_native_call_method→ the dense-array arms innative_call_method/handle_methods.rs) droppedargs[1]— the thisArg — for the whole Array.prototype callback family (forEach/map/filter/some/every/find/findIndex/findLast/findLastIndex) and dispatched to the dense helpers, which deliberately bind the callback'sthistoundefined(the spec rule for an absent thisArg).The static lowering (
lower_array_method.rs) already routes explicit-thisArg calls through the this-bindingjs_arraylike_*engine. A receiver only reaches the dynamic tower when codegen can't prove its type — in the bundle,e5is an object member read through a dynamic key (FLIPPED[r4]), which defeats flow typing. That is why every statically-provable simplification of the shape worked and only the combined form reproduced. The extractedt4.addresolves to the identity-preserving brand-checkingSet.prototype.addthunk (#3662), readsIMPLICIT_THIS = undefined, and throws. A lexically-capturing closure callback on the same path silently ran with the wrongthisinstead (arr.map(fn, thisArg)observedthis !== thisArg— also fixed).Fix
native_call_method/handle_methods.rs: mirror the static rule in the dynamic tower — with a thisArg present (args_len >= 2), route the nine-method family throughdispatch_arraylike_read_method, whoseThisGuardbinds the thisArg around each callback call (real-array receivers keep a fast element path inside the engine).flatMaphas no engine entry and shares the same thisArg gap on the static path too; untouched here.collection_proto_thunks.rs: node appends V8'sNoSideEffectsToStringrendering of the receiver to the brand-check TypeError (... called on incompatible receiver #<Object>/undefined/5.5/[object Array]/#<Foo>/Error: boom).throw_incompatible_receivernow renders the receiver the same way, so the throwing cases are message-identical to node. (No builtin property attributes touched — the fix(runtime,codegen): expose the Web Streams globals; resolve instanceof against a built-in held in a variable #6335 gotcha territory was left alone.)Tests
crates/perry/tests/issue_6658_extracted_builtin_method_thisarg.rs: the issue's 11-line combined shape verbatim; the five previously-ruled-out simpler shapes as regression anchors; the minimal dynamic-key trigger withSet.prototype.add,Map.prototype.set, andArray.prototype.pushextracted as callbacks; this-binding asserted across the whole nine-method family; eight brand-check throw cases with node-v26-byte-identical messages (undefined,#<Object>, cross-brand#<Set>,5.5,abc,null,#<Foo>,[object Array]).test-parity/node-suite/object/extracted-builtin-method-callback-thisarg.ts: same coverage through the parity runner — 100% vs node v26.--test-threads=1), perry-codegen lib 209/0, perry-hir lib 246/0, node-suite object module 26/26, existingtest_gap_3662_collection_brand_checkstill 100%.cargo fmt --checkclean on touched files.Validation on the real target
Full pi recompile with this fix:
HOME=$(mktemp -d) ./pi-native --version→0.0.0rc=0 and--help→ the 167-line usage rc=0, both byte-identical (stdout+stderr) tonode pi-bundle.mjs— GATE 1 of the pi bring-up reached; no wall #8 on these entry points.Stretch-goal assessment (gaxios
class extends Setlosingadd, issue family #6232): probed —class A extends Set {}+ staticnewworks, direct class-expressionnewworks; what fails is construction through a dynamic path (new exportsObj.X()/new obj.A()): the Map/Set backing init (js_map_set_subclass_init) is emitted by codegen only at staticnewsites (lower_call/new_helpers.rs::emit_native_instance_base_init), so dynamic-new never installs the backing for ANY native base (Set, Map, Event, EventEmitter, …). That is the full #6232 builtin-subclass-construction feature (class-id→native-base registry + dynamic-new runtime wiring + ctor-arg seeding), not a bounded Set-only fix in this PR's subsystem — documented on #6232 instead.Stacks on #6656 (branched from
fix/6652-global-proto-members; merge that first).Fixes #6658
https://claude.ai/code/session_01JuiiePQfrXhAFD9fuCygB9
Summary by CodeRabbit
thisArgis preserved across methods likeforEach,map,filter,some,every, and thefind*family.TypeErrormessages for incompatible receivers by including clearer, Node/V8-style receiver rendering.thisArgbinding and exact error message text for receiver-related failures.