diff --git a/core/engine/src/bytecompiler/class.rs b/core/engine/src/bytecompiler/class.rs index 9e7a00190f2..0031f2b4e35 100644 --- a/core/engine/src/bytecompiler/class.rs +++ b/core/engine/src/bytecompiler/class.rs @@ -207,16 +207,6 @@ impl ByteCompiler<'_> { let mut static_elements = Vec::new(); - if let Some(scope) = class.name_scope { - let binding = scope.get_identifier_reference(class_name.clone()); - let index = self.insert_binding(binding); - self.emit_binding_access( - BindingAccessOpcode::PutLexicalValue, - &index, - &class_register, - ); - } - for element in class.elements { match element { ClassElement::MethodDefinition(m) => match m.name() { @@ -505,6 +495,10 @@ impl ByteCompiler<'_> { PropertyName::Computed(name) => { let name_register = self.register_allocator.alloc(); self.compile_expr(name, &name_register); + self.bytecode.emit_to_property_key( + name_register.variable(), + name_register.variable(), + ); StaticFieldName::Register(name_register) } }; @@ -635,6 +629,19 @@ impl ByteCompiler<'_> { } } + // ClassDefinitionEvaluation initializes the class binding after evaluating + // all element names, but before executing static initializers. + // https://tc39.es/ecma262/#sec-runtime-semantics-classdefinitionevaluation + if let Some(scope) = class.name_scope { + let binding = scope.get_identifier_reference(class_name.clone()); + let index = self.insert_binding(binding); + self.emit_binding_access( + BindingAccessOpcode::PutLexicalValue, + &index, + &class_register, + ); + } + for element in static_elements { match element { StaticElement::StaticBlock(code) => { @@ -682,8 +689,6 @@ impl ByteCompiler<'_> { } StaticFieldName::Register(key) => { if is_anonymous_function { - self.bytecode - .emit_to_property_key(key.variable(), key.variable()); self.bytecode.emit_set_function_name( value.variable(), key.variable(), diff --git a/core/engine/src/tests/class.rs b/core/engine/src/tests/class.rs index 98e3845f86f..aed00713dc5 100644 --- a/core/engine/src/tests/class.rs +++ b/core/engine/src/tests/class.rs @@ -2,6 +2,99 @@ use crate::{TestAction, run_test_actions}; use boa_macros::js_str; use indoc::indoc; +// https://github.com/boa-dev/boa/issues/5635 +#[test] +fn computed_class_field_keys_follow_source_order() { + run_test_actions([ + TestAction::run(indoc! {r#" + let n = 0; + const key = { toString() { return "k" + ++n; } }; + class C { + [key]() { return 1; } + static [key] = 2; + [key] = 3; + } + const instance = new C(); + "#}), + TestAction::assert_eq("instance.k1()", 1), + TestAction::assert_eq("C.k2", 2), + TestAction::assert_eq("instance.k3", 3), + TestAction::assert_eq("n", 3), + ]); +} + +#[test] +fn computed_static_field_key_precedes_initializers() { + run_test_actions([ + TestAction::run(indoc! {r#" + const events = []; + const key = { + toString() { + events.push("key"); + return "field"; + } + }; + class C { + static first = events.push("first"); + static [key] = events.push("second"); + } + "#}), + TestAction::assert_eq("events.join(',')", js_str!("key,first,second")), + TestAction::assert_eq("C.field", 3), + ]); +} + +#[test] +fn abrupt_computed_static_field_key_prevents_initializers() { + run_test_actions([ + TestAction::run("let initialized = false;"), + TestAction::assert_opaque_error( + indoc! {r#" + class C { + static first = (initialized = true); + static [{ toString() { throw "key"; } }] = 1; + } + "#}, + js_str!("key"), + ), + TestAction::assert("!initialized"), + ]); +} + +#[test] +fn class_name_is_uninitialized_in_computed_keys() { + for source in [ + "class C { [C] = 1; }", + "class C { static [C] = 1; }", + "class C { [C]() {} }", + "(class C { [C] = 1; });", + "(class C { static [C] = 1; });", + "(class C { [C]() {} });", + ] { + run_test_actions([TestAction::assert_native_error( + source, + crate::JsNativeErrorKind::Reference, + "C is not defined", + )]); + } +} + +#[test] +fn class_name_is_initialized_before_static_initializers() { + run_test_actions([ + TestAction::run(indoc! {r#" + class C { + static self = C; + static { this.matches = C === this; } + self = C; + } + "#}), + TestAction::assert("C.self === C"), + TestAction::assert("C.matches"), + TestAction::assert("new C().self === C"), + ]); +} + #[test] fn class_field_initializer_name_static() { run_test_actions([