From d45f8b1606160ab9aa373304e8e37fae5f83f50d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Tue, 1 Sep 2026 10:36:17 +0200 Subject: [PATCH 1/2] fix(hir): keep the Symbol.iterator vtable entry alongside #9226's own-key registration --- changelog.d/PENDING-9226-iterator-vtable.md | 25 +++++++++++++++++++ crates/perry-hir/src/lower_decl/class_decl.rs | 22 ++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 changelog.d/PENDING-9226-iterator-vtable.md diff --git a/changelog.d/PENDING-9226-iterator-vtable.md b/changelog.d/PENDING-9226-iterator-vtable.md new file mode 100644 index 0000000000..7338086317 --- /dev/null +++ b/changelog.d/PENDING-9226-iterator-vtable.md @@ -0,0 +1,25 @@ +**fix(hir): keep the `Symbol.iterator` vtable entry alongside #9226's own-key registration** + +`const R = class { *[Symbol.iterator]() { … } }` threw +`TypeError: value is not iterable`. + +#9226 replaced `methods.push(wrapper)` with `computed_members.push(…)` in both +the class-declaration and class-expression paths. Those are not equivalent: the +first installs an instance **vtable** entry, the second registers a **prototype +own key**. `synthesize_symbol_iterator_wrapper` exists specifically to provide +the vtable entry (#5128) — without it the class carries no `@@iterator`, so the +runtime's class-registry lookup finds nothing. + +Both registrations are required and the fix does both, so #9226's own-key +enumeration is preserved rather than traded away. + +Verified by bisect on an isolated host (dev profile, same toolchain): + +| commit | `issue_5128_user_symbol_iterator` | +|---|---| +| `015ec5fe1c` (parent) | 3 passed | +| `8b2cfe6e7b` (#9226) | 2 passed, **1 failed** | +| `8b2cfe6e7b` + this fix | **3 passed** | + +and #9226's own gap test (`test_gap_9226_class_prototype_own_keys.ts`) still +matches Node byte-for-byte with the fix applied — the own-key win is intact. diff --git a/crates/perry-hir/src/lower_decl/class_decl.rs b/crates/perry-hir/src/lower_decl/class_decl.rs index 520711adbb..55ec46f21c 100644 --- a/crates/perry-hir/src/lower_decl/class_decl.rs +++ b/crates/perry-hir/src/lower_decl/class_decl.rs @@ -776,6 +776,17 @@ pub fn lower_class_decl( let ast::PropName::Computed(computed) = &method.key else { unreachable!("@@iterator generator key must be computed"); }; + // #9226 registers the wrapper as a prototype own + // key so `Object.getOwnPropertyNames` / + // `getOwnPropertySymbols` list it. That registration + // does NOT install an instance vtable entry, and the + // wrapper exists precisely to provide one (#5128) — + // dropping it left `const C = class { *[Symbol.iterator] + // () {…} }` throwing `TypeError: value is not + // iterable`. Both registrations are required: the + // vtable entry makes the instance iterable, the + // computed member makes the key enumerable. + methods.push(wrapper.clone()); computed_members.push(ClassComputedMember { key_expr: lower_expr(ctx, &computed.expr)?, function: wrapper, @@ -1641,6 +1652,17 @@ pub fn lower_class_from_ast( let ast::PropName::Computed(computed) = &method.key else { unreachable!("@@iterator generator key must be computed"); }; + // #9226 registers the wrapper as a prototype own + // key so `Object.getOwnPropertyNames` / + // `getOwnPropertySymbols` list it. That registration + // does NOT install an instance vtable entry, and the + // wrapper exists precisely to provide one (#5128) — + // dropping it left `const C = class { *[Symbol.iterator] + // () {…} }` throwing `TypeError: value is not + // iterable`. Both registrations are required: the + // vtable entry makes the instance iterable, the + // computed member makes the key enumerable. + methods.push(wrapper.clone()); computed_members.push(ClassComputedMember { key_expr: lower_expr(ctx, &computed.expr)?, function: wrapper, From 2442f2971e879ad49d2dcdb93415dc0010afd20f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Tue, 1 Sep 2026 10:37:26 +0200 Subject: [PATCH 2/2] docs(changelog): key the fragment to PR #9372 --- .../{PENDING-9226-iterator-vtable.md => 9372-iterator-vtable.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{PENDING-9226-iterator-vtable.md => 9372-iterator-vtable.md} (100%) diff --git a/changelog.d/PENDING-9226-iterator-vtable.md b/changelog.d/9372-iterator-vtable.md similarity index 100% rename from changelog.d/PENDING-9226-iterator-vtable.md rename to changelog.d/9372-iterator-vtable.md