|
| 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