Skip to content

fix(runtime): any-typed Headers/collection-handle .keys()/.entries()/.values() returned empty#6095

Merged
proggeramlug merged 1 commit into
PerryTS:mainfrom
proggeramlug:fix/any-typed-collection-handle-iterator
Jul 7, 2026
Merged

fix(runtime): any-typed Headers/collection-handle .keys()/.entries()/.values() returned empty#6095
proggeramlug merged 1 commit into
PerryTS:mainfrom
proggeramlug:fix/any-typed-collection-handle-iterator

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

An any-typed .keys()/.entries()/.values() call folds to the array-only Expr::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 heap ArrayHeader.

A Web Headers (and FormData / URLSearchParams) instance is a fetch-band registry handle, not an ArrayHeader. 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 computed headers["keys"]() form worked correctly.

Impact

A large esbuild-bundled CLI app builds request headers, wraps them as { values: Headers }, and merges them via yield* wrapper.values.entries(). Because wrapper.values is any-typed, .entries() folded to ArrayEntries -> empty -> every header (including auth) was silently dropped, and the request failed its SDK validateHeaders check ("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 routes Map/Set receivers to their iterators. Route fetch-band handles through js_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-typed Headers (type erased through an object property) iterates via .keys()/.entries()/.values() and yield* delegation, byte-for-byte matching Node.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed iterator behavior for Headers, FormData, and URLSearchParams when 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.
    • Improved support for delegation patterns like yield* with these collection iterators.
  • Tests

    • Added coverage for iterator behavior on Headers when used through an any boundary.

….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.
@coderabbitai

coderabbitai Bot commented Jul 6, 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: 9357c001-a3b4-4508-9cc8-30e6d2f33cdc

📥 Commits

Reviewing files that changed from the base of the PR and between 49867d5 and 77c3d71.

📒 Files selected for processing (2)
  • crates/perry-runtime/src/array/iter_object.rs
  • crates/perry/tests/issue_any_typed_collection_handle_iterator.rs

📝 Walkthrough

Walkthrough

Adds a fetch-handle-aware branch in collection_iter_obj_for_receiver that dispatches .keys()/.values()/.entries() calls on non-heap fetch handles (Headers/FormData/URLSearchParams) through js_native_call_method, falling back to existing collection iterator logic otherwise. A regression test validates Headers iteration under type erasure.

Changes

Fetch Handle Iterator Fix

Layer / File(s) Summary
Fetch-handle iterator dispatch
crates/perry-runtime/src/array/iter_object.rs
Adds address-band detection for fetch handles and routes .keys()/.values()/.entries() calls through js_native_call_method, falling back to prior Map/Set logic when not applicable.
Regression test for Headers iteration
crates/perry/tests/issue_any_typed_collection_handle_iterator.rs
Adds test helpers and a regression test compiling/running TypeScript that erases Headers via any and verifies .keys(), .values(), .entries(), yield* delegation, and .has() results.

Estimated code review effort: 2 (Simple) | ~15 minutes

Possibly related PRs

  • PerryTS/perry#5442: Addresses incorrect routing of Response.headers fetch-handle objects for iterator methods, preventing wrong/empty results.
  • PerryTS/perry#5549: Modifies js_get_iterator's handle-band guard for fetch-handle-backed iterables, closely related to the iterator resolution fixed here.
  • PerryTS/perry#5964: Extends the same js_native_call_method dispatch layer used here to support URLSearchParams under type erasure.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is specific and accurately summarizes the runtime fix for empty any-typed fetch-handle iterators.
Description check ✅ Passed The description covers the summary, root cause, fix, and test, though it doesn't follow the template headings exactly.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

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

@proggeramlug proggeramlug merged commit 5f9f1bf into PerryTS:main Jul 7, 2026
23 of 25 checks passed
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