Skip to content

Commit cdf996d

Browse files
author
Ralph Küpper
committed
docs(changelog): add fragment for #7583 (iterator-helpers class-id collision)
1 parent 1f2a497 commit cdf996d

1 file changed

Lines changed: 48 additions & 0 deletions

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
### Fixed
2+
3+
- **The entire TC39 iterator-helpers surface was dead (#7576).** `Iterator.from(x)`
4+
returned an iterator that was exhausted before its first step, and `.map` /
5+
`.filter` / `.take` / `.drop` / `.flatMap` all returned `undefined`, so any
6+
chain threw `TypeError: Cannot read properties of undefined`.
7+
8+
**Root cause: a class-id collision.** `ITERATOR_HELPER_CLASS_ID`
9+
(`crates/perry-runtime/src/iterator_helpers.rs`) and `STRING_ITERATOR_CLASS_ID`
10+
(`crates/perry-runtime/src/string/iter_object.rs`) were both `0xFFFF_0009`.
11+
The two constants were introduced independently and each carries the comment
12+
"sits just past the Set iterator id (0xFFFF0008)"; neither author checked
13+
whether the slot was taken. Every dispatch tower matches these ids in a fixed
14+
order with the String arm first, so **every** helper object was dispatched as
15+
a String iterator: `.next()` read the helper's op-kind field as a cursor index
16+
against a null backing array and answered `{ done: true }`, and every other
17+
helper method fell into that dispatcher's `_ => undefined` arm. One cause,
18+
both symptoms. The helper now takes `0xFFFF_000B` (`0xFFFF_000A` is the
19+
RegExp-string iterator).
20+
21+
**Second, independent defect on the same path.** `iterator_step` resolved the
22+
source iterator's `next` with the *inheriting* getter. Since #321 every
23+
built-in iterator inherits `.next` from its shared `%…IteratorPrototype%`
24+
singleton, and that inherited `next` is a thunk that resolves its receiver
25+
from `js_implicit_this_get()`. The lookup therefore found a callable closure
26+
for an array / Map / Set / String iterator source, took the raw-closure-call
27+
branch (which binds no `this`), and ran the thunk against a stale receiver —
28+
`done` on the first step, or `Method %IteratorPrototype%.next called on
29+
incompatible receiver`. `array/iterator.rs::js_iterator_to_array` already
30+
carried the own-field version of this fix; `iterator_step` now matches it
31+
(`js_object_get_own_field_or_undef`, which is also allocation-free) and binds
32+
`this` to the iterator per `IteratorNext`'s `Call(next, iterator)`. Both
33+
halves are load-bearing.
34+
35+
**Why it survived.** `test-files/test_gap_iterator_helpers_2874.ts` caught this
36+
and had been listed in `test-parity/known_failures.json` since 2026-07-04. It
37+
passes byte-for-byte now and the skip is removed.
38+
39+
New coverage, `cargo-test`-visible per #5960:
40+
`crates/perry-runtime/src/iterator_helpers/tests.rs` (16 tests, all driving
41+
`js_native_call_method` — the tower a compiled program reaches, since a test
42+
calling the helper dispatcher directly would have been green throughout the
43+
outage), including `iterator_class_ids_are_pairwise_distinct`, which fails on
44+
any duplicate in the iterator family. Plus
45+
`test-files/test_gap_iterator_helpers_7576.ts`, 28 lines byte-identical to
46+
`node --experimental-strip-types`, covering the issue reproducer, hand-stepped
47+
stored helpers, all six source kinds, every combinator and terminal, laziness
48+
over an unbounded generator, and spread.

0 commit comments

Comments
 (0)