diff --git a/core/ast/src/function/class.rs b/core/ast/src/function/class.rs index 1d554ae6117..587f0668b7f 100644 --- a/core/ast/src/function/class.rs +++ b/core/ast/src/function/class.rs @@ -389,7 +389,7 @@ pub enum ClassElement { /// A private static field definition, only accessible from static methods and fields inside the /// class declaration. - PrivateStaticFieldDefinition(PrivateName, Option), + PrivateStaticFieldDefinition(PrivateFieldDefinition), /// A static block, where a class can have initialization logic for its static fields. StaticBlock(StaticBlockBody), @@ -406,7 +406,7 @@ pub enum ClassElement { #[derive(Clone, Debug, PartialEq)] pub struct ClassFieldDefinition { pub(crate) name: PropertyName, - pub(crate) field: Option, + pub(crate) initializer: Option, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scope: Scope, @@ -416,10 +416,10 @@ impl ClassFieldDefinition { /// Creates a new class field definition. #[inline] #[must_use] - pub fn new(name: PropertyName, field: Option) -> Self { + pub fn new(name: PropertyName, initializer: Option) -> Self { Self { name, - field, + initializer, scope: Scope::default(), } } @@ -431,11 +431,11 @@ impl ClassFieldDefinition { &self.name } - /// Returns the field of the class field definition. + /// Returns the initializer of the class field definition. #[inline] #[must_use] - pub const fn field(&self) -> Option<&Expression> { - self.field.as_ref() + pub const fn initializer(&self) -> Option<&Expression> { + self.initializer.as_ref() } /// Returns the scope of the class field definition. @@ -457,7 +457,7 @@ impl ClassFieldDefinition { #[derive(Clone, Debug, PartialEq)] pub struct PrivateFieldDefinition { pub(crate) name: PrivateName, - pub(crate) field: Option, + pub(crate) initializer: Option, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scope: Scope, @@ -467,10 +467,10 @@ impl PrivateFieldDefinition { /// Creates a new private field definition. #[inline] #[must_use] - pub fn new(name: PrivateName, field: Option) -> Self { + pub fn new(name: PrivateName, initializer: Option) -> Self { Self { name, - field, + initializer, scope: Scope::default(), } } @@ -482,11 +482,11 @@ impl PrivateFieldDefinition { &self.name } - /// Returns the field of the private field definition. + /// Returns the initializer of the private field definition. #[inline] #[must_use] - pub const fn field(&self) -> Option<&Expression> { - self.field.as_ref() + pub const fn initializer(&self) -> Option<&Expression> { + self.initializer.as_ref() } /// Returns the scope of the private field definition. @@ -502,7 +502,7 @@ impl ToIndentedString for ClassElement { let indentation = " ".repeat(indent_n + 1); match self { Self::MethodDefinition(m) => m.to_indented_string(interner, indent_n), - Self::FieldDefinition(field) => match &field.field { + Self::FieldDefinition(field) => match &field.initializer { Some(expr) => { format!( "{indentation}{} = {};\n", @@ -517,7 +517,7 @@ impl ToIndentedString for ClassElement { ) } }, - Self::StaticFieldDefinition(field) => match &field.field { + Self::StaticFieldDefinition(field) => match &field.initializer { Some(expr) => { format!( "{indentation}static {} = {};\n", @@ -532,8 +532,9 @@ impl ToIndentedString for ClassElement { ) } }, - Self::PrivateFieldDefinition(PrivateFieldDefinition { name, field, .. }) => match field - { + Self::PrivateFieldDefinition(PrivateFieldDefinition { + name, initializer, .. + }) => match initializer { Some(expr) => { format!( "{indentation}#{} = {};\n", @@ -548,7 +549,11 @@ impl ToIndentedString for ClassElement { ) } }, - Self::PrivateStaticFieldDefinition(name, field) => match field { + Self::PrivateStaticFieldDefinition(PrivateFieldDefinition { + name, + initializer, + .. + }) => match initializer { Some(expr) => { format!( "{indentation}static #{} = {};\n", @@ -593,16 +598,22 @@ impl VisitWith for ClassElement { } Self::FieldDefinition(field) | Self::StaticFieldDefinition(field) => { visitor.visit_property_name(&field.name)?; - if let Some(expr) = &field.field { + if let Some(expr) = &field.initializer { visitor.visit_expression(expr) } else { ControlFlow::Continue(()) } } - Self::PrivateFieldDefinition(PrivateFieldDefinition { name, field, .. }) - | Self::PrivateStaticFieldDefinition(name, field) => { + Self::PrivateFieldDefinition(PrivateFieldDefinition { + name, initializer, .. + }) + | Self::PrivateStaticFieldDefinition(PrivateFieldDefinition { + name, + initializer, + .. + }) => { visitor.visit_private_name(name)?; - if let Some(expr) = field { + if let Some(expr) = initializer { visitor.visit_expression(expr) } else { ControlFlow::Continue(()) @@ -631,16 +642,22 @@ impl VisitWith for ClassElement { } Self::FieldDefinition(field) | Self::StaticFieldDefinition(field) => { visitor.visit_property_name_mut(&mut field.name)?; - if let Some(expr) = &mut field.field { + if let Some(expr) = &mut field.initializer { visitor.visit_expression_mut(expr) } else { ControlFlow::Continue(()) } } - Self::PrivateFieldDefinition(PrivateFieldDefinition { name, field, .. }) - | Self::PrivateStaticFieldDefinition(name, field) => { + Self::PrivateFieldDefinition(PrivateFieldDefinition { + name, initializer, .. + }) + | Self::PrivateStaticFieldDefinition(PrivateFieldDefinition { + name, + initializer, + .. + }) => { visitor.visit_private_name_mut(name)?; - if let Some(expr) = field { + if let Some(expr) = initializer { visitor.visit_expression_mut(expr) } else { ControlFlow::Continue(()) diff --git a/core/ast/src/operations.rs b/core/ast/src/operations.rs index 45dee229113..ca5802e598a 100644 --- a/core/ast/src/operations.rs +++ b/core/ast/src/operations.rs @@ -1180,7 +1180,9 @@ impl<'ast> Visitor<'ast> for AllPrivateIdentifiersValidVisitor { } } ClassElement::PrivateFieldDefinition(PrivateFieldDefinition { name, .. }) - | ClassElement::PrivateStaticFieldDefinition(name, _) => { + | ClassElement::PrivateStaticFieldDefinition(PrivateFieldDefinition { + name, .. + }) => { names.push(name.description()); } _ => {} @@ -1205,13 +1207,19 @@ impl<'ast> Visitor<'ast> for AllPrivateIdentifiersValidVisitor { ClassElement::FieldDefinition(field) | ClassElement::StaticFieldDefinition(field) => { visitor.visit(&field.name)?; - if let Some(expression) = &field.field { + if let Some(expression) = &field.initializer { visitor.visit(expression)?; } } - ClassElement::PrivateFieldDefinition(PrivateFieldDefinition { field, .. }) - | ClassElement::PrivateStaticFieldDefinition(_, field) => { - if let Some(expression) = field { + ClassElement::PrivateFieldDefinition(PrivateFieldDefinition { + initializer, + .. + }) + | ClassElement::PrivateStaticFieldDefinition(PrivateFieldDefinition { + initializer, + .. + }) => { + if let Some(expression) = initializer { visitor.visit(expression)?; } } @@ -1241,7 +1249,9 @@ impl<'ast> Visitor<'ast> for AllPrivateIdentifiersValidVisitor { } } ClassElement::PrivateFieldDefinition(PrivateFieldDefinition { name, .. }) - | ClassElement::PrivateStaticFieldDefinition(name, _) => { + | ClassElement::PrivateStaticFieldDefinition(PrivateFieldDefinition { + name, .. + }) => { names.push(name.description()); } _ => {} @@ -1266,13 +1276,19 @@ impl<'ast> Visitor<'ast> for AllPrivateIdentifiersValidVisitor { ClassElement::FieldDefinition(field) | ClassElement::StaticFieldDefinition(field) => { visitor.visit(&field.name)?; - if let Some(expression) = &field.field { + if let Some(expression) = &field.initializer { visitor.visit(expression)?; } } - ClassElement::PrivateFieldDefinition(PrivateFieldDefinition { field, .. }) - | ClassElement::PrivateStaticFieldDefinition(_, field) => { - if let Some(expression) = field { + ClassElement::PrivateFieldDefinition(PrivateFieldDefinition { + initializer, + .. + }) + | ClassElement::PrivateStaticFieldDefinition(PrivateFieldDefinition { + initializer, + .. + }) => { + if let Some(expression) = initializer { visitor.visit(expression)?; } } diff --git a/core/ast/src/scope_analyzer.rs b/core/ast/src/scope_analyzer.rs index 354c7dc0d4d..5e378fe6117 100644 --- a/core/ast/src/scope_analyzer.rs +++ b/core/ast/src/scope_analyzer.rs @@ -434,19 +434,14 @@ impl<'ast> VisitorMut<'ast> for BindingEscapeAnalyzer<'_> { ), ClassElement::FieldDefinition(field) | ClassElement::StaticFieldDefinition(field) => { self.visit_property_name_mut(&mut field.name)?; - if let Some(e) = &mut field.field { + if let Some(e) = &mut field.initializer { self.visit_expression_mut(e)?; } ControlFlow::Continue(()) } - ClassElement::PrivateFieldDefinition(field) => { - if let Some(e) = &mut field.field { - self.visit_expression_mut(e)?; - } - ControlFlow::Continue(()) - } - ClassElement::PrivateStaticFieldDefinition(_, e) => { - if let Some(e) = e { + ClassElement::PrivateFieldDefinition(field) + | ClassElement::PrivateStaticFieldDefinition(field) => { + if let Some(e) = &mut field.initializer { self.visit_expression_mut(e)?; } ControlFlow::Continue(()) @@ -816,29 +811,24 @@ impl<'ast> VisitorMut<'ast> for BindingCollectorVisitor<'_> { self.visit_property_name_mut(&mut field.name)?; let mut scope = Scope::new(self.scope.clone(), true); std::mem::swap(&mut self.scope, &mut scope); - if let Some(e) = &mut field.field { + if let Some(e) = &mut field.initializer { self.visit_expression_mut(e)?; } std::mem::swap(&mut self.scope, &mut scope); field.scope = scope; ControlFlow::Continue(()) } - ClassElement::PrivateFieldDefinition(field) => { + ClassElement::PrivateFieldDefinition(field) + | ClassElement::PrivateStaticFieldDefinition(field) => { let mut scope = Scope::new(self.scope.clone(), true); std::mem::swap(&mut self.scope, &mut scope); - if let Some(e) = &mut field.field { + if let Some(e) = &mut field.initializer { self.visit_expression_mut(e)?; } std::mem::swap(&mut self.scope, &mut scope); field.scope = scope; ControlFlow::Continue(()) } - ClassElement::PrivateStaticFieldDefinition(_, e) => { - if let Some(e) = e { - self.visit_expression_mut(e)?; - } - ControlFlow::Continue(()) - } ClassElement::StaticBlock(node) => { let strict = node.body.strict(); self.visit_function_like( @@ -1423,28 +1413,23 @@ impl<'ast> VisitorMut<'ast> for ScopeIndexVisitor { let index = self.index; self.index += 1; field.scope.set_index(self.index); - if let Some(e) = &mut field.field { + if let Some(e) = &mut field.initializer { self.visit_expression_mut(e)?; } self.index = index; ControlFlow::Continue(()) } - ClassElement::PrivateFieldDefinition(field) => { + ClassElement::PrivateFieldDefinition(field) + | ClassElement::PrivateStaticFieldDefinition(field) => { let index = self.index; self.index += 1; field.scope.set_index(self.index); - if let Some(e) = &mut field.field { + if let Some(e) = &mut field.initializer { self.visit_expression_mut(e)?; } self.index = index; ControlFlow::Continue(()) } - ClassElement::PrivateStaticFieldDefinition(_, e) => { - if let Some(e) = e { - self.visit_expression_mut(e)?; - } - ControlFlow::Continue(()) - } ClassElement::StaticBlock(node) => { let contains_direct_eval = contains(node.statements(), ContainsSymbol::DirectEval); self.visit_function_like( diff --git a/core/engine/src/bytecompiler/class.rs b/core/engine/src/bytecompiler/class.rs index 1e360be4920..26e666b57fa 100644 --- a/core/engine/src/bytecompiler/class.rs +++ b/core/engine/src/bytecompiler/class.rs @@ -22,7 +22,12 @@ enum StaticElement { StaticBlock(Gc), // A static class field with it's function code, an optional name index and the information if the function is an anonymous function. - StaticField((Gc, Option, bool)), + StaticField { + code: Gc, + name_index: Option, + is_anonymous_function: bool, + is_private: bool, + }, } /// Describes the complete specification of a class. @@ -160,16 +165,12 @@ impl ByteCompiler<'_> { self.emit_u32(index); } } - ClassElement::PrivateFieldDefinition(field) => { + ClassElement::PrivateFieldDefinition(field) + | ClassElement::PrivateStaticFieldDefinition(field) => { count += 1; let index = self.get_or_insert_private_name(*field.name()); self.emit_u32(index); } - ClassElement::PrivateStaticFieldDefinition(name, _) => { - count += 1; - let index = self.get_or_insert_private_name(*name); - self.emit_u32(index); - } _ => {} } } @@ -294,7 +295,7 @@ impl ByteCompiler<'_> { // Function environment field_compiler.code_block_flags |= CodeBlockFlags::HAS_FUNCTION_SCOPE; let _ = field_compiler.push_scope(field.scope()); - let is_anonymous_function = if let Some(node) = &field.field() { + let is_anonymous_function = if let Some(node) = &field.initializer() { field_compiler.compile_expr(node, true); node.is_anonymous_function_definition() } else { @@ -329,7 +330,7 @@ impl ByteCompiler<'_> { ); field_compiler.code_block_flags |= CodeBlockFlags::HAS_FUNCTION_SCOPE; let _ = field_compiler.push_scope(field.scope()); - if let Some(node) = field.field() { + if let Some(node) = field.initializer() { field_compiler.compile_expr(node, true); } else { field_compiler.emit_opcode(Opcode::PushUndefined); @@ -371,7 +372,7 @@ impl ByteCompiler<'_> { ); field_compiler.code_block_flags |= CodeBlockFlags::HAS_FUNCTION_SCOPE; let _ = field_compiler.push_scope(field.scope()); - let is_anonymous_function = if let Some(node) = &field.field() { + let is_anonymous_function = if let Some(node) = &field.initializer() { field_compiler.compile_expr(node, true); node.is_anonymous_function_definition() } else { @@ -385,21 +386,48 @@ impl ByteCompiler<'_> { let code = field_compiler.finish(); let code = Gc::new(code); - static_elements.push(StaticElement::StaticField(( + static_elements.push(StaticElement::StaticField { code, name_index, is_anonymous_function, - ))); + is_private: false, + }); } - ClassElement::PrivateStaticFieldDefinition(name, field) => { - self.emit_opcode(Opcode::Dup); - if let Some(node) = field { - self.compile_expr(node, true); + ClassElement::PrivateStaticFieldDefinition(field) => { + let name_index = self.get_or_insert_private_name(*field.name()); + let mut field_compiler = ByteCompiler::new( + class_name.clone(), + true, + self.json_parse, + self.variable_scope.clone(), + self.lexical_scope.clone(), + false, + false, + self.interner, + self.in_with, + ); + field_compiler.code_block_flags |= CodeBlockFlags::HAS_FUNCTION_SCOPE; + let _ = field_compiler.push_scope(field.scope()); + let is_anonymous_function = if let Some(node) = &field.initializer() { + field_compiler.compile_expr(node, true); + node.is_anonymous_function_definition() } else { - self.emit_opcode(Opcode::PushUndefined); - } - let index = self.get_or_insert_private_name(*name); - self.emit_with_varying_operand(Opcode::DefinePrivateField, index); + field_compiler.emit_opcode(Opcode::PushUndefined); + false + }; + field_compiler.emit_opcode(Opcode::SetReturnValue); + + field_compiler.code_block_flags |= CodeBlockFlags::IN_CLASS_FIELD_INITIALIZER; + + let code = field_compiler.finish(); + let code = Gc::new(code); + + static_elements.push(StaticElement::StaticField { + code, + name_index: Some(name_index), + is_anonymous_function, + is_private: true, + }); } ClassElement::StaticBlock(block) => { let mut compiler = ByteCompiler::new( @@ -447,7 +475,12 @@ impl ByteCompiler<'_> { self.emit_with_varying_operand(Opcode::Call, 0); self.emit_opcode(Opcode::Pop); } - StaticElement::StaticField((code, name_index, is_anonymous_function)) => { + StaticElement::StaticField { + code, + name_index, + is_anonymous_function, + is_private, + } => { self.emit_opcode(Opcode::Dup); self.emit_opcode(Opcode::Dup); let index = self.push_function_to_constants(code); @@ -455,8 +488,16 @@ impl ByteCompiler<'_> { self.emit_opcode(Opcode::SetHomeObject); self.emit_with_varying_operand(Opcode::Call, 0); if let Some(name_index) = name_index { - self.emit_with_varying_operand(Opcode::DefineOwnPropertyByName, name_index); + if is_private { + self.emit_with_varying_operand(Opcode::DefinePrivateField, name_index); + } else { + self.emit_with_varying_operand( + Opcode::DefineOwnPropertyByName, + name_index, + ); + } } else { + // Assume the name is not private. Private names cannot be dynamically computed. self.emit(Opcode::RotateLeft, &[Operand::U8(5)]); if is_anonymous_function { self.emit_opcode(Opcode::Dup); diff --git a/core/engine/src/tests/class.rs b/core/engine/src/tests/class.rs index 3e44d6b6d8c..158e41dd12e 100644 --- a/core/engine/src/tests/class.rs +++ b/core/engine/src/tests/class.rs @@ -45,3 +45,30 @@ fn class_superclass_from_regex_error() { "superclass must be a constructor", )]); } + +// https://github.com/boa-dev/boa/issues/3055 +#[test] +fn class_can_access_super_from_static_initializer() { + run_test_actions([ + TestAction::run(indoc! {r#" + class a { + static field = "super field"; + } + + class b extends a { + static #field = super.field; + static get field() { + return this.#field; + } + } + + class c extends a { + static field = super.field; + } + + "#}), + TestAction::assert_eq("a.field", js_str!("super field")), + TestAction::assert_eq("b.field", js_str!("super field")), + TestAction::assert_eq("c.field", js_str!("super field")), + ]); +} diff --git a/core/parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs b/core/parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs index d878d77d96f..cc992dd786a 100644 --- a/core/parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs +++ b/core/parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs @@ -416,7 +416,7 @@ where } } function::ClassElement::PrivateFieldDefinition(field) => { - if let Some(node) = field.field() { + if let Some(node) = field.initializer() { if contains(node, ContainsSymbol::SuperCall) { return Err(Error::lex(LexError::Syntax( "invalid super usage".into(), @@ -434,8 +434,8 @@ where )); } } - function::ClassElement::PrivateStaticFieldDefinition(name, init) => { - if let Some(node) = init { + function::ClassElement::PrivateStaticFieldDefinition(field) => { + if let Some(node) = field.initializer() { if contains(node, ContainsSymbol::SuperCall) { return Err(Error::lex(LexError::Syntax( "invalid super usage".into(), @@ -444,7 +444,7 @@ where } } if private_elements_names - .insert(name.description(), PrivateElement::StaticValue) + .insert(field.name().description(), PrivateElement::StaticValue) .is_some() { return Err(Error::general( @@ -455,7 +455,7 @@ where } function::ClassElement::FieldDefinition(field) | function::ClassElement::StaticFieldDefinition(field) => { - if let Some(field) = field.field() { + if let Some(field) = field.initializer() { if contains(field, ContainsSymbol::SuperCall) { return Err(Error::lex(LexError::Syntax( "invalid super usage".into(), @@ -1094,15 +1094,11 @@ where .as_slice(), ); rhs.set_anonymous_function_definition_name(&Identifier::new(function_name)); + let field = PrivateFieldDefinition::new(PrivateName::new(name), Some(rhs)); if r#static { - function::ClassElement::PrivateStaticFieldDefinition( - PrivateName::new(name), - Some(rhs), - ) + function::ClassElement::PrivateStaticFieldDefinition(field) } else { - function::ClassElement::PrivateFieldDefinition( - PrivateFieldDefinition::new(PrivateName::new(name), Some(rhs)), - ) + function::ClassElement::PrivateFieldDefinition(field) } } TokenKind::Punctuator(Punctuator::OpenParen) => { @@ -1141,15 +1137,11 @@ where } _ => { cursor.expect_semicolon("expected semicolon", interner)?; + let field = PrivateFieldDefinition::new(PrivateName::new(name), None); if r#static { - function::ClassElement::PrivateStaticFieldDefinition( - PrivateName::new(name), - None, - ) + function::ClassElement::PrivateStaticFieldDefinition(field) } else { - function::ClassElement::PrivateFieldDefinition( - PrivateFieldDefinition::new(PrivateName::new(name), None), - ) + function::ClassElement::PrivateFieldDefinition(field) } } } @@ -1274,7 +1266,7 @@ where // It is a Syntax Error if Initializer is present and ContainsArguments of Initializer is true. function::ClassElement::FieldDefinition(field) | function::ClassElement::StaticFieldDefinition(field) => { - if let Some(field) = field.field() { + if let Some(field) = field.initializer() { if contains_arguments(field) { return Err(Error::general( "'arguments' not allowed in class field definition", @@ -1283,8 +1275,9 @@ where } } } - function::ClassElement::PrivateFieldDefinition(field) => { - if let Some(node) = field.field() { + function::ClassElement::PrivateFieldDefinition(field) + | function::ClassElement::PrivateStaticFieldDefinition(field) => { + if let Some(node) = field.initializer() { if contains_arguments(node) { return Err(Error::general( "'arguments' not allowed in class field definition", @@ -1293,14 +1286,6 @@ where } } } - function::ClassElement::PrivateStaticFieldDefinition(_, Some(node)) => { - if contains_arguments(node) { - return Err(Error::general( - "'arguments' not allowed in class field definition", - position, - )); - } - } _ => {} }