From b10cd037b32a4bfdbe62d4f6b010c1a14972b768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 5 Jul 2026 16:45:33 +0200 Subject: [PATCH] fix(hir): emit computed-key static fields that have no initializer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A computed-name static class field with no initializer (`static [expr];`) was dropped entirely during lowering — `build_interleaved_static_init_stmts` bailed with `let Some(init) = &sf.init else { return }` before it ever looked at the key. That skipped two observable behaviors required by ClassDefinitionEvaluation: 1. The key expression's side effects never ran. 2. `CreateDataProperty(F, key, undefined)` never happened, so the field was not defined — and, critically, a key that evaluates to `"prototype"` never reached the runtime `"prototype"`-is-a-TypeError check in `js_class_register_static_symbol` (the constructor's `prototype` slot is non-configurable, so a static field of that name throws). Now a field is skipped only when it is BOTH un-initialized AND has no computed key (a plain named uninit static slot — unchanged behavior). A computed-key field is always emitted, with `undefined` substituted for a missing initializer, so its `ClassStaticSymbolSet` runs the key and defines the property (or throws for `"prototype"`). Fixes the test262 class-elements cases: - fields-computed-name-static-propname-prototype - fields-computed-name-static-computed-var-propname-prototype - static-field-declaration (statements + expressions) - static-classelementname-abrupt-completion --- crates/perry-hir/src/lower_decl/static_init.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/crates/perry-hir/src/lower_decl/static_init.rs b/crates/perry-hir/src/lower_decl/static_init.rs index 1a2e9ede58..d3656e0859 100644 --- a/crates/perry-hir/src/lower_decl/static_init.rs +++ b/crates/perry-hir/src/lower_decl/static_init.rs @@ -34,9 +34,18 @@ pub(crate) fn build_interleaved_static_init_stmts( static_methods: &[Function], ) -> Vec { let emit_field = |out: &mut Vec, sf: &ClassField| { - let Some(init) = &sf.init else { return }; + // A COMPUTED-key static field with no initializer still performs + // CreateDataProperty(F, key, undefined) at class-eval time, so it must + // be emitted (value = undefined) rather than dropped: (1) the key + // expression has observable side effects, and (2) a key that evaluates + // to "prototype" is a TypeError (test262 fields-computed-name-static- + // *propname-prototype). A NON-computed uninit static field is a plain + // named slot and keeps the pre-existing "skip when no init" behavior. + if sf.init.is_none() && sf.key_expr.is_none() { + return; + } // `this` in a static field initializer is the class constructor. - let mut init_value = init.clone(); + let mut init_value = sf.init.clone().unwrap_or(Expr::Undefined); crate::analysis::substitute_lexical_this_in_expr( &mut init_value, &Expr::ClassRef(class_name.to_string()),