fix(runtime): any-typed Headers/collection-handle .keys()/.entries()/.values() returned empty#6095
Conversation
….values() returned empty
An any-typed `.keys()`/`.entries()`/`.values()` call folds to the array-only
`Expr::Array{Keys,Entries,Values}` (perry-hir PerryTS#597), whose runtime helpers read
the receiver as a heap `ArrayHeader`. A Web `Headers` (and `FormData` /
`URLSearchParams`) instance is a fetch-band registry *handle*, not an
`ArrayHeader`, so `js_array_{keys,entries,values}_iter_obj` read the handle id
as an array length and yielded an empty iterator — while `.has()`/`.get()`
(dynamic dispatch) and the computed `headers["keys"]()` form worked.
Impact: a large esbuild-bundled client SDK builds request headers, wraps them
as `{ values: Headers }`, and merges via `yield* wrapper.values.entries()`.
Because `wrapper.values` is any-typed, `.entries()` folded to `ArrayEntries` →
empty → every header was silently dropped and the request failed its
`validateHeaders` auth check.
`collection_iter_obj_for_receiver` already routes Map/Set receivers to their
iterators; route fetch-band handles through `js_native_call_method` →
`js_headers_{keys,entries,values}` too. Non-collection fetch handles
(Response/Request/Blob) and genuine plain objects still fall through to the
empty-array path. Adds an e2e regression test.
|
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 (2)
📝 WalkthroughWalkthroughAdds a fetch-handle-aware branch in ChangesFetch Handle Iterator Fix
Estimated code review effort: 2 (Simple) | ~15 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 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 |
An any-typed
.keys()/.entries()/.values()call folds to the array-onlyExpr::Array{Keys,Entries,Values}(perry-hir #597, so any-typed real arrays iterate through the index-based for-of lowering). The runtime helpers behind those (js_array_{keys,entries,values}_iter_obj) read the receiver as a heapArrayHeader.A Web
Headers(andFormData/URLSearchParams) instance is a fetch-band registry handle, not anArrayHeader. So an any-typed iterator method on such a handle read the handle id as an array length and yielded an empty iterator — while.has()/.get()(dynamic dispatch) and the computedheaders["keys"]()form worked correctly.Impact
A large esbuild-bundled CLI app builds request headers, wraps them as
{ values: Headers }, and merges them viayield* wrapper.values.entries(). Becausewrapper.valuesis any-typed,.entries()folded toArrayEntries-> empty -> every header (including auth) was silently dropped, and the request failed its SDKvalidateHeaderscheck ("Could not resolve authentication method"). With this fix the credential reaches the wire (verified: the compiled app opens a TLS connection to the API and sends the authenticated request).Fix
collection_iter_obj_for_receiver(perry-runtime/src/array/iter_object.rs) already routesMap/Setreceivers to their iterators. Route fetch-band handles throughjs_native_call_method->js_headers_{keys,entries,values}(which return a materialized, iterable array) as well. Non-collection fetch handles (Response/Request/Blob) and genuine plain objects still fall through to the empty-array path.Test
crates/perry/tests/issue_any_typed_collection_handle_iterator.rs— an any-typedHeaders(type erased through an object property) iterates via.keys()/.entries()/.values()andyield*delegation, byte-for-byte matching Node.Summary by CodeRabbit
Bug Fixes
Headers,FormData, andURLSearchParamswhen accessed through erased or generic object references..keys(),.values(), and.entries()now return proper iterable results instead of falling back to empty iteration in these cases.yield*with these collection iterators.Tests
Headerswhen used through ananyboundary.