From b903fb81b44ad125f67cca06e742012f820c382c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Tue, 22 Sep 2026 16:24:40 +0200 Subject: [PATCH 1/2] fix(hir): register inline class computed methods before new --- changelog.d/10839-inline-class-iterator.md | 4 +++ .../perry-hir/src/lower/expr_new/non_ident.rs | 27 ++++++++++++------- .../tests/issue_5128_user_symbol_iterator.rs | 6 ++--- 3 files changed, 25 insertions(+), 12 deletions(-) create mode 100644 changelog.d/10839-inline-class-iterator.md diff --git a/changelog.d/10839-inline-class-iterator.md b/changelog.d/10839-inline-class-iterator.md new file mode 100644 index 0000000000..f8d2500307 --- /dev/null +++ b/changelog.d/10839-inline-class-iterator.md @@ -0,0 +1,4 @@ +Fixed runtime iteration of an inline anonymous class expression constructed +with `new (class { ... })()`. Computed methods such as generator +`[Symbol.iterator]` are now registered before the instance is constructed, +matching ordinary class expressions. diff --git a/crates/perry-hir/src/lower/expr_new/non_ident.rs b/crates/perry-hir/src/lower/expr_new/non_ident.rs index 01604f126a..800dbf4fab 100644 --- a/crates/perry-hir/src/lower/expr_new/non_ident.rs +++ b/crates/perry-hir/src/lower/expr_new/non_ident.rs @@ -9,7 +9,7 @@ use anyhow::Result; use swc_ecma_ast as ast; use crate::ir::Expr; -use crate::lower_decl::lower_class_from_ast; +use crate::lower_decl::{lower_class_from_ast, prepare_ordered_class_computed_names}; use crate::lower_types::extract_ts_type_with_ctx; use super::super::{lower_expr, LoweringContext}; @@ -165,6 +165,12 @@ pub(crate) fn lower_new_non_ident( // link instead and needs no registration — which is why the user-parent // form of this shape already worked. let parent_expr = class.extends_expr.clone(); + // This arm bypasses `lower_class_expr`, which normally evaluates and + // registers computed member keys at class-definition time. Without + // that prelude, an inline `new (class { *[Symbol.iterator]() {} })()` + // constructs an instance before its iterator method is registered. + let (computed_name_evaluations, _) = + prepare_ordered_class_computed_names(&class_expr.class.body, &class, &synthetic_name); ctx.pending_classes.push(class); let mut args: Vec = new_expr .args @@ -217,16 +223,19 @@ pub(crate) fn lower_new_non_ident( // The `Sequence` yields its LAST element, so the `new` site still sees // the constructed instance — the registration is pure side effect, // ordered before it. - let Some(parent_expr) = parent_expr else { - return Ok(construct); - }; - return Ok(Expr::Sequence(vec![ - Expr::RegisterClassParentDynamic { + let mut definition_steps = Vec::new(); + if let Some(parent_expr) = parent_expr { + definition_steps.push(Expr::RegisterClassParentDynamic { class_name: synthetic_name, parent_expr, - }, - construct, - ])); + }); + } + definition_steps.extend(computed_name_evaluations); + if definition_steps.is_empty() { + return Ok(construct); + } + definition_steps.push(construct); + return Ok(Expr::Sequence(definition_steps)); } let callee = Box::new(lower_expr(ctx, callee_expr)?); diff --git a/crates/perry/tests/issue_5128_user_symbol_iterator.rs b/crates/perry/tests/issue_5128_user_symbol_iterator.rs index 1e43f89882..d5ee6a808b 100644 --- a/crates/perry/tests/issue_5128_user_symbol_iterator.rs +++ b/crates/perry/tests/issue_5128_user_symbol_iterator.rs @@ -88,9 +88,9 @@ console.log(it.next().value, it.next().value, it.next().done); ); } -/// The same fix must apply to class *expressions* — `lower_class_from_ast` -/// mirrors `lower_class_decl`, so `new (class { *[Symbol.iterator]() {…} })()` -/// and a named class-expression binding are iterable for every runtime consumer. +/// The same fix must apply to class *expressions*. A direct `new (class +/// { *[Symbol.iterator]() {…} })()` uses a separate lowering arm that must +/// register its computed method before constructing the instance (#10839). #[test] fn class_expression_generator_symbol_iterator_is_iterable() { let dir = tempfile::tempdir().expect("tempdir"); From d5363bb96a2ddfd568cfdb29db6c09e1d01092e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Tue, 22 Sep 2026 16:25:06 +0200 Subject: [PATCH 2/2] chore: name changelog fragment for PR 10993 --- ...39-inline-class-iterator.md => 10993-inline-class-iterator.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{10839-inline-class-iterator.md => 10993-inline-class-iterator.md} (100%) diff --git a/changelog.d/10839-inline-class-iterator.md b/changelog.d/10993-inline-class-iterator.md similarity index 100% rename from changelog.d/10839-inline-class-iterator.md rename to changelog.d/10993-inline-class-iterator.md