fix(hir): dispatch Array-mutator-named methods on any-typed receivers by runtime shape (#5139) - #5194
Conversation
… by runtime shape (#5139) react-dom/server's `renderToStaticMarkup` returned an empty string. The Fizz renderer accumulates HTML by calling `destination.push(chunk)` on a plain-object sink `{ push(chunk) { result += chunk } }` that flows through several `any`-typed params before `writeChunk(destination, …)` invokes it. HIR lowering eagerly turned `recv.push(x)` — where `recv` is statically `any` — into the array-only fast path (`Expr::ArrayPush` / the `array.push_single` native arm). That reads the receiver's header as an `ArrayHeader`, so the plain object's bytes were interpreted as array length/capacity: `push` returned a bogus numeric length and the user closure never ran, dropping every chunk (SSR yielded ""). The two lowering sites (`local_array_methods.rs` for bare-ident receivers, `array_only_methods.rs` for the fallback) now defer the mutator names that a plain object can legitimately own as a closure property — push/pop/shift/unshift/ splice/sort/reverse/concat — to the runtime `js_native_call_method` dispatch when the receiver's static type is unknown/`any`. That dispatcher already selects by runtime shape: a real array hits the dense `js_array_*` helpers (growth still resolves via the #233 forwarding pointer), while a plain object with an own callable of that name invokes it with `this` bound to the receiver. Positively array-typed receivers keep the inline fast path unchanged, and the read-only iteration folds (map/filter/entries/…) on `any` receivers are untouched. Verified against the real react-dom@18.3.1 package compiled via `perry.compilePackages`: the issue's repro now prints `<ul id="L"><li>item-1</li><li>item-2</li><li>item-3</li></ul>`, matching Node. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Caution Review failedPull request was closed or merged during review 📝 WalkthroughWalkthroughTwo guards are added to the HIR lowering passes ( ChangesArray-mutator dispatch fix for any/unknown receivers
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related issues
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Summary
Fixes #5139 —
react-dom/server'srenderToStaticMarkupreturned an empty string instead of the rendered HTML when react-dom is compiled natively viaperry.compilePackages.Root cause
The bug was in HIR lowering, not in react-dom. The Fizz SSR renderer accumulates output by calling
destination.push(chunk)on a plain-object sink:HIR lowering eagerly rewrote
recv.push(x)— whererecvis staticallyany— into the array-only fast path (Expr::ArrayPush/ thearray.push_singlenative arm). That path reads the receiver's header as anArrayHeader, so the plain object's bytes were interpreted as array length/capacity:pushreturned a bogus numeric length and the user closure never ran, dropping every chunk — SSR yielded"".This affected any object owning a method whose name collides with an
Array.prototypemutator (push/pop/shift/unshift/splice/sort/reverse/concat) when invoked through anany-typed receiver.Fix
The two lowering sites —
local_array_methods.rs(bare-ident receivers) andarray_only_methods.rs(the fallback for arbitrary expressions) — now defer those mutator names to the runtimejs_native_call_methoddispatch when the receiver's static type is unknown/any. That dispatcher already selects by runtime shape:js_array_*helpers (growth still resolves via the Array.push from inside an async function silently caps at 16 elements when the array is a function parameter #233 forwarding pointer, so realloc across a call boundary is preserved);thisbound to the receiver (viatry_object_arraylike_mutator's own-user-method gate + the generic own-field scan).Positively array-typed receivers keep the inline fast path unchanged, and the read-only iteration folds (
map/filter/entries/…) onanyreceivers are untouched.Verification
react-dom@18.3.1package compiled viaperry.compilePackages. The issue's repro now prints, matching Node:crates/perry/tests/issue_5139_object_arraylike_method_dispatch.rs:pushclosure runs when invoked throughany-typed params (the reduced Fizz flush loop);any-typed real arrays keepArray.prototypesemantics, including 1000-push growth across a function boundary,unshift/push/reverse/splice, comparatorsort, and in-placepop/shift.perryintegration suite +perry-runtimeunit tests (1035) pass, plusperry-hir/perry-codegentests — no regressions.No version bump or CHANGELOG entry (left to maintainer at merge).
🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
#5139affectingreact-dom/server'srenderToStaticMarkup.Tests