diff options
author | davidot <davidot@serenityos.org> | 2021-10-07 01:09:04 +0200 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2021-10-20 23:19:17 +0100 |
commit | 1245512c508d88e2a025d9d4e58297f76afd34bb (patch) | |
tree | 7da4b63bdddcb085fab691d8476afb9d93393651 /Userland | |
parent | d3ef08217b93d74e50256e862783fdda3a22388c (diff) | |
download | serenity-1245512c508d88e2a025d9d4e58297f76afd34bb.zip |
LibJS: Make class definition evaluation more spec like in ordering
Diffstat (limited to 'Userland')
-rw-r--r-- | Userland/Libraries/LibJS/AST.cpp | 269 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/AST.h | 59 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Parser.cpp | 10 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp | 4 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h | 4 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Tests/classes/class-inheritance.js | 29 |
6 files changed, 251 insertions, 124 deletions
diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp index c3fdfdb512..f74a1d2ea3 100644 --- a/Userland/Libraries/LibJS/AST.cpp +++ b/Userland/Libraries/LibJS/AST.cpp @@ -1158,16 +1158,90 @@ Value SuperExpression::execute(Interpreter&, GlobalObject&) const VERIFY_NOT_REACHED(); } -Value ClassMethod::execute(Interpreter& interpreter, GlobalObject& global_object) const +Value ClassElement::execute(Interpreter&, GlobalObject&) const { - InterpreterNodeScope node_scope { interpreter, *this }; - return m_function->execute(interpreter, global_object); + // Note: The semantics of class element are handled in class_element_evaluation + VERIFY_NOT_REACHED(); } -Value ClassField::execute(Interpreter& interpreter, GlobalObject&) const +static ThrowCompletionOr<PropertyName> class_key_to_property_name(Interpreter& interpreter, GlobalObject& global_object, Expression const& key) { - InterpreterNodeScope node_scope { interpreter, *this }; - return {}; + + auto prop_key = key.execute(interpreter, global_object); + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); + + if (prop_key.is_object()) + prop_key = TRY(prop_key.to_primitive(global_object, Value::PreferredType::String)); + + auto property_key = PropertyName::from_value(global_object, prop_key); + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); + return property_key; +} + +// 15.4.5 Runtime Semantics: MethodDefinitionEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-methoddefinitionevaluation +ThrowCompletionOr<ClassElement::ClassValue> ClassMethod::class_element_evaluation(Interpreter& interpreter, GlobalObject& global_object, Object& target) const +{ + auto property_key = TRY(class_key_to_property_name(interpreter, global_object, *m_key)); + + auto method_value = m_function->execute(interpreter, global_object); + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); + + auto& method_function = static_cast<ECMAScriptFunctionObject&>(method_value.as_function()); + method_function.set_home_object(&target); + + auto set_function_name = [&](String prefix) { + String property_name; + // FIXME: Handle PrivateNames as well. + if (property_key.is_symbol()) + property_name = String::formatted("[{}]", property_key.as_symbol()->description()); + else + property_name = property_key.to_string(); + update_function_name(method_value, String::formatted("{} {}", prefix, property_name)); + }; + + switch (kind()) { + case ClassMethod::Kind::Method: + TRY(target.define_property_or_throw(property_key, { .value = method_value, .writable = true, .enumerable = false, .configurable = true })); + break; + case ClassMethod::Kind::Getter: + set_function_name("get"); + TRY(target.define_property_or_throw(property_key, { .get = &method_function, .enumerable = true, .configurable = true })); + break; + case ClassMethod::Kind::Setter: + set_function_name("set"); + TRY(target.define_property_or_throw(property_key, { .set = &method_function, .enumerable = true, .configurable = true })); + break; + default: + VERIFY_NOT_REACHED(); + } + // FIXME: Return PrivateElement for private methods + + return ClassValue { normal_completion({}) }; +} + +// 15.7.10 Runtime Semantics: ClassFieldDefinitionEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-classfielddefinitionevaluation +ThrowCompletionOr<ClassElement::ClassValue> ClassField::class_element_evaluation(Interpreter& interpreter, GlobalObject& global_object, Object& target) const +{ + auto property_key = TRY(class_key_to_property_name(interpreter, global_object, *m_key)); + ECMAScriptFunctionObject* initializer = nullptr; + if (m_initializer) { + auto copy_initializer = m_initializer; + auto body = create_ast_node<ExpressionStatement>(m_initializer->source_range(), copy_initializer.release_nonnull()); + // FIXME: A potential optimization is not creating the functions here since these are never directly accessible. + auto name = property_key.is_number() ? property_key.to_string() : property_key.to_string_or_symbol().to_display_string(); + initializer = ECMAScriptFunctionObject::create(interpreter.global_object(), name, *body, {}, 0, interpreter.lexical_environment(), FunctionKind::Regular, false, false); + initializer->set_home_object(&target); + } + + return ClassValue { + ClassFieldDefinition { + property_key, + initializer, + } + }; } Value ClassExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const @@ -1208,125 +1282,129 @@ ThrowCompletionOr<Value> ClassExpression::class_definition_evaluation(Interprete auto* environment = vm.lexical_environment(); VERIFY(environment); auto* class_scope = new_declarative_environment(*environment); - if (!binding_name.is_null()) - MUST(class_scope->create_immutable_binding(global_object, binding_name, true)); + // We might not set the lexical environment but we always want to restore it eventually. ArmedScopeGuard restore_environment = [&] { vm.running_execution_context().lexical_environment = environment; }; - vm.running_execution_context().lexical_environment = class_scope; - Value class_constructor_value = m_constructor->execute(interpreter, global_object); - if (auto* exception = interpreter.exception()) - return throw_completion(exception->value()); + if (!binding_name.is_null()) + MUST(class_scope->create_immutable_binding(global_object, binding_name, true)); - update_function_name(class_constructor_value, class_name); + // FIXME: Add classPrivateEnvironment - VERIFY(class_constructor_value.is_function() && is<ECMAScriptFunctionObject>(class_constructor_value.as_function())); - auto* class_constructor = static_cast<ECMAScriptFunctionObject*>(&class_constructor_value.as_function()); - class_constructor->set_is_class_constructor(); - Value super_constructor = js_undefined(); - if (!m_super_class.is_null()) { - super_constructor = m_super_class->execute(interpreter, global_object); - if (auto* exception = interpreter.exception()) - return throw_completion(exception->value()); + // FIXME: Append names to private environment - if (!super_constructor.is_function() && !super_constructor.is_null()) - return interpreter.vm().throw_completion<TypeError>(global_object, ErrorType::ClassExtendsValueNotAConstructorOrNull, super_constructor.to_string_without_side_effects()); + auto* proto_parent = vm.current_realm()->global_object().object_prototype(); - class_constructor->set_constructor_kind(ECMAScriptFunctionObject::ConstructorKind::Derived); + auto* constructor_parent = vm.current_realm()->global_object().function_prototype(); - Object* super_constructor_prototype = nullptr; - if (!super_constructor.is_null()) { - auto super_constructor_prototype_value = TRY(super_constructor.as_object().get(vm.names.prototype)); + if (!m_super_class.is_null()) { + vm.running_execution_context().lexical_environment = class_scope; - if (!super_constructor_prototype_value.is_object() && !super_constructor_prototype_value.is_null()) - return interpreter.vm().throw_completion<TypeError>(global_object, ErrorType::ClassExtendsValueInvalidPrototype, super_constructor_prototype_value.to_string_without_side_effects()); + // Note: Since our execute does evaluation and GetValue in once we must check for a valid reference first - if (super_constructor_prototype_value.is_object()) - super_constructor_prototype = &super_constructor_prototype_value.as_object(); - } - auto* prototype = Object::create(global_object, super_constructor_prototype); + Value super_class; - prototype->define_direct_property(vm.names.constructor, class_constructor, 0); - if (auto* exception = interpreter.exception()) - return throw_completion(exception->value()); - class_constructor->define_direct_property(vm.names.prototype, prototype, Attribute::Writable); + auto reference = m_super_class->to_reference(interpreter, global_object); if (auto* exception = interpreter.exception()) return throw_completion(exception->value()); - TRY(class_constructor->internal_set_prototype_of(super_constructor.is_null() ? global_object.function_prototype() : &super_constructor.as_object())); - } - auto class_prototype = TRY(class_constructor->get(vm.names.prototype)); + if (reference.is_valid_reference()) { + super_class = reference.get_value(global_object); + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); + } else { + super_class = m_super_class->execute(interpreter, global_object); + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); + } + vm.running_execution_context().lexical_environment = environment; - if (!class_prototype.is_object()) - return interpreter.vm().throw_completion<TypeError>(global_object, ErrorType::NotAnObject, "Class prototype"); + if (super_class.is_null()) { + proto_parent = nullptr; + } else if (!super_class.is_constructor()) { + return vm.throw_completion<TypeError>(global_object, ErrorType::ClassExtendsValueNotAConstructorOrNull, super_class.to_string_without_side_effects()); + } else { + auto super_class_prototype = TRY(super_class.get(global_object, vm.names.prototype)); + if (!super_class_prototype.is_null() && !super_class_prototype.is_object()) + return vm.throw_completion<TypeError>(global_object, ErrorType::ClassExtendsValueInvalidPrototype, super_class_prototype.to_string_without_side_effects()); - for (auto const& method : m_methods) { - auto method_value = method.execute(interpreter, global_object); - if (auto* exception = interpreter.exception()) - return throw_completion(exception->value()); + if (super_class_prototype.is_null()) + proto_parent = nullptr; + else + proto_parent = &super_class_prototype.as_object(); - auto& method_function = static_cast<ECMAScriptFunctionObject&>(method_value.as_function()); + constructor_parent = &super_class.as_object(); + } + } - auto key = method.key().execute(interpreter, global_object); - if (auto* exception = interpreter.exception()) - return throw_completion(exception->value()); + auto* prototype = Object::create(global_object, proto_parent); + VERIFY(prototype); - auto property_key = TRY(key.to_property_key(global_object)); + vm.running_execution_context().lexical_environment = class_scope; + // FIXME: Activate the class private environment - auto& target = method.is_static() ? *class_constructor : class_prototype.as_object(); - method_function.set_home_object(&target); + // FIXME: Step 14.a is done in the parser. But maybe it shouldn't? + Value class_constructor_value = m_constructor->execute(interpreter, global_object); + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); - switch (method.kind()) { - case ClassMethod::Kind::Method: - TRY(target.define_property_or_throw(property_key, { .value = method_value, .writable = true, .enumerable = false, .configurable = true })); - break; - case ClassMethod::Kind::Getter: - update_function_name(method_value, String::formatted("get {}", TRY(get_function_name(global_object, key)))); - TRY(target.define_property_or_throw(property_key, { .get = &method_function, .enumerable = true, .configurable = true })); - break; - case ClassMethod::Kind::Setter: - update_function_name(method_value, String::formatted("set {}", TRY(get_function_name(global_object, key)))); - TRY(target.define_property_or_throw(property_key, { .set = &method_function, .enumerable = true, .configurable = true })); - break; - default: - VERIFY_NOT_REACHED(); - } - } + update_function_name(class_constructor_value, class_name); - for (auto& field : m_fields) { - auto key = field.key().execute(interpreter, global_object); - if (auto* exception = interpreter.exception()) - return throw_completion(exception->value()); + VERIFY(class_constructor_value.is_function() && is<ECMAScriptFunctionObject>(class_constructor_value.as_function())); + auto* class_constructor = static_cast<ECMAScriptFunctionObject*>(&class_constructor_value.as_function()); + class_constructor->set_home_object(prototype); + class_constructor->set_is_class_constructor(); + class_constructor->define_direct_property(vm.names.prototype, prototype, Attribute::Writable); + TRY(class_constructor->internal_set_prototype_of(constructor_parent)); - auto property_key = TRY(key.to_property_key(global_object)); + if (!m_super_class.is_null()) + class_constructor->set_constructor_kind(ECMAScriptFunctionObject::ConstructorKind::Derived); - ECMAScriptFunctionObject* initializer = nullptr; - if (field.initializer()) { - auto copy_initializer = field.initializer(); - auto body = create_ast_node<ExpressionStatement>(field.initializer()->source_range(), copy_initializer.release_nonnull()); - // FIXME: A potential optimization is not creating the functions here since these are never directly accessible. - initializer = ECMAScriptFunctionObject::create(interpreter.global_object(), property_key.to_display_string(), *body, {}, 0, interpreter.lexical_environment(), FunctionKind::Regular, false, false); - initializer->set_home_object(field.is_static() ? class_constructor : &class_prototype.as_object()); - } + prototype->define_direct_property(vm.names.constructor, class_constructor, Attribute::Writable | Attribute::Configurable); - if (field.is_static()) { - Value field_value = js_undefined(); - if (initializer) - field_value = TRY(interpreter.vm().call(*initializer, class_constructor_value)); + Vector<ClassElement::ClassFieldDefinition> instance_fields; + Vector<ClassElement::ClassFieldDefinition> static_fields; - TRY(class_constructor->create_data_property_or_throw(property_key, field_value)); - } else { - class_constructor->add_field(property_key, initializer); + // FIXME: Do things with private methods, and fields here + for (auto const& element : m_elements) { + // Note: All ClassElementEvaluation start with evaluating the name (or we fake it). + auto element_value = TRY(element.class_element_evaluation(interpreter, global_object, element.is_static() ? *class_constructor : *prototype)); + // FIXME: If element is a private element + + if (auto* class_field_definition_ptr = element_value.get_pointer<ClassElement::ClassFieldDefinition>()) { + if (element.is_static()) + static_fields.append(move(*class_field_definition_ptr)); + else + instance_fields.append(move(*class_field_definition_ptr)); } + + // FIXME: Else if element is class static block } vm.running_execution_context().lexical_environment = environment; restore_environment.disarm(); + if (!binding_name.is_null()) MUST(class_scope->initialize_binding(global_object, binding_name, class_constructor)); + // FIXME: Set the [[PrivateMethods]] + for (auto& field : instance_fields) + class_constructor->add_field(field.name, field.initializer); + + for (auto& field : static_fields) { + Value field_value = js_undefined(); + if (field.initializer) + field_value = TRY(interpreter.vm().call(*field.initializer, class_constructor_value)); + + // FIXME: Handle private static fields + + TRY(class_constructor->create_data_property_or_throw(field.name, field_value)); + } + + // FIXME: Run static initializers + return Value(class_constructor); } @@ -1558,14 +1636,9 @@ void ClassExpression::dump(int indent) const } print_indent(indent); - outln("(Methods)"); - for (auto& method : m_methods) + outln("(Elements)"); + for (auto& method : m_elements) method.dump(indent + 1); - - print_indent(indent); - outln("(Fields)"); - for (auto& field : m_fields) - field.dump(indent + 1); } void ClassMethod::dump(int indent) const @@ -1592,7 +1665,7 @@ void ClassMethod::dump(int indent) const outln("Kind: {}", kind_string); print_indent(indent); - outln("Static: {}", m_is_static); + outln("Static: {}", is_static()); print_indent(indent); outln("(Function)"); @@ -1607,7 +1680,7 @@ void ClassField::dump(int indent) const m_key->dump(indent + 1); print_indent(indent); - outln("Static: {}", m_is_static); + outln("Static: {}", is_static()); if (m_initializer) { print_indent(indent); diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h index d2cdb8bbaa..be7806a4f0 100644 --- a/Userland/Libraries/LibJS/AST.h +++ b/Userland/Libraries/LibJS/AST.h @@ -1000,7 +1000,37 @@ private: mutable Optional<EnvironmentCoordinate> m_cached_environment_coordinate; }; -class ClassMethod final : public ASTNode { +class ClassElement : public ASTNode { +public: + ClassElement(SourceRange source_range, bool is_static) + : ASTNode(source_range) + , m_is_static(is_static) + { + } + + virtual Value execute(Interpreter&, GlobalObject&) const override; + + enum class ElementKind { + Method, + Field, + }; + + virtual ElementKind class_element_kind() const = 0; + bool is_static() const { return m_is_static; } + + struct ClassFieldDefinition { + PropertyName name; + ECMAScriptFunctionObject* initializer { nullptr }; + }; + + using ClassValue = Variant<ClassFieldDefinition, Completion>; + virtual ThrowCompletionOr<ClassValue> class_element_evaluation(Interpreter&, GlobalObject&, Object& home_object) const = 0; + +private: + bool m_is_static { false }; +}; + +class ClassMethod final : public ClassElement { public: enum class Kind { Method, @@ -1009,50 +1039,47 @@ public: }; ClassMethod(SourceRange source_range, NonnullRefPtr<Expression> key, NonnullRefPtr<FunctionExpression> function, Kind kind, bool is_static) - : ASTNode(source_range) + : ClassElement(source_range, is_static) , m_key(move(key)) , m_function(move(function)) , m_kind(kind) - , m_is_static(is_static) { } Expression const& key() const { return *m_key; } Kind kind() const { return m_kind; } - bool is_static() const { return m_is_static; } + virtual ElementKind class_element_kind() const override { return ElementKind::Method; } - virtual Value execute(Interpreter&, GlobalObject&) const override; virtual void dump(int indent) const override; + virtual ThrowCompletionOr<ClassValue> class_element_evaluation(Interpreter&, GlobalObject&, Object& home_object) const override; private: NonnullRefPtr<Expression> m_key; NonnullRefPtr<FunctionExpression> m_function; Kind m_kind; - bool m_is_static; }; -class ClassField final : public ASTNode { +class ClassField final : public ClassElement { public: ClassField(SourceRange source_range, NonnullRefPtr<Expression> key, RefPtr<Expression> init, bool is_static) - : ASTNode(source_range) + : ClassElement(source_range, is_static) , m_key(move(key)) , m_initializer(move(init)) - , m_is_static(is_static) { } Expression const& key() const { return *m_key; } - bool is_static() const { return m_is_static; } RefPtr<Expression> const& initializer() const { return m_initializer; } RefPtr<Expression>& initializer() { return m_initializer; } - virtual Value execute(Interpreter&, GlobalObject&) const override; + virtual ElementKind class_element_kind() const override { return ElementKind::Field; } + virtual void dump(int indent) const override; + virtual ThrowCompletionOr<ClassValue> class_element_evaluation(Interpreter& interpreter, GlobalObject& object, Object& home_object) const override; private: NonnullRefPtr<Expression> m_key; RefPtr<Expression> m_initializer; - bool m_is_static; }; class SuperExpression final : public Expression { @@ -1070,13 +1097,12 @@ public: class ClassExpression final : public Expression { public: - ClassExpression(SourceRange source_range, String name, RefPtr<FunctionExpression> constructor, RefPtr<Expression> super_class, NonnullRefPtrVector<ClassMethod> methods, NonnullRefPtrVector<ClassField> fields) + ClassExpression(SourceRange source_range, String name, RefPtr<FunctionExpression> constructor, RefPtr<Expression> super_class, NonnullRefPtrVector<ClassElement> elements) : Expression(source_range) , m_name(move(name)) , m_constructor(move(constructor)) , m_super_class(move(super_class)) - , m_methods(move(methods)) - , m_fields(move(fields)) + , m_elements(move(elements)) { } @@ -1096,8 +1122,7 @@ private: String m_name; RefPtr<FunctionExpression> m_constructor; RefPtr<Expression> m_super_class; - NonnullRefPtrVector<ClassMethod> m_methods; - NonnullRefPtrVector<ClassField> m_fields; + NonnullRefPtrVector<ClassElement> m_elements; }; class ClassDeclaration final : public Declaration { diff --git a/Userland/Libraries/LibJS/Parser.cpp b/Userland/Libraries/LibJS/Parser.cpp index 4f5228b2b2..06e866f284 100644 --- a/Userland/Libraries/LibJS/Parser.cpp +++ b/Userland/Libraries/LibJS/Parser.cpp @@ -842,8 +842,7 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_ consume(TokenType::Class); - NonnullRefPtrVector<ClassMethod> methods; - NonnullRefPtrVector<ClassField> fields; + NonnullRefPtrVector<ClassElement> elements; RefPtr<Expression> super_class; RefPtr<FunctionExpression> constructor; @@ -989,7 +988,7 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_ if (is_constructor) { constructor = move(function); } else if (!property_key.is_null()) { - methods.append(create_ast_node<ClassMethod>({ m_state.current_token.filename(), rule_start.position(), position() }, property_key.release_nonnull(), move(function), method_kind, is_static)); + elements.append(create_ast_node<ClassMethod>({ m_state.current_token.filename(), rule_start.position(), position() }, property_key.release_nonnull(), move(function), method_kind, is_static)); } else { syntax_error("No key for class method"); } @@ -1013,8 +1012,7 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_ initializer = parse_expression(2); } - fields.append(create_ast_node<ClassField>({ m_state.current_token.filename(), rule_start.position(), position() }, property_key.release_nonnull(), move(initializer), is_static)); - + elements.append(create_ast_node<ClassField>({ m_state.current_token.filename(), rule_start.position(), position() }, property_key.release_nonnull(), move(initializer), is_static)); consume_or_insert_semicolon(); } } @@ -1043,7 +1041,7 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_ } } - return create_ast_node<ClassExpression>({ m_state.current_token.filename(), rule_start.position(), position() }, move(class_name), move(constructor), move(super_class), move(methods), move(fields)); + return create_ast_node<ClassExpression>({ m_state.current_token.filename(), rule_start.position(), position() }, move(class_name), move(constructor), move(super_class), move(elements)); } Parser::PrimaryExpressionParseResult Parser::parse_primary_expression() diff --git a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp index 94c03b44bd..bf1ee2a6a9 100644 --- a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp @@ -275,7 +275,9 @@ void ECMAScriptFunctionObject::visit_edges(Visitor& visitor) visitor.visit(m_home_object); for (auto& field : m_fields) { - field.name.visit_edges(visitor); + if (field.name.is_symbol()) + visitor.visit(field.name.as_symbol()); + visitor.visit(field.initializer); } } diff --git a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h index 2d44d4bc5f..29ae2767f9 100644 --- a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h +++ b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h @@ -59,14 +59,14 @@ public: void set_home_object(Object* home_object) { m_home_object = home_object; } struct InstanceField { - StringOrSymbol name; + PropertyName name; ECMAScriptFunctionObject* initializer { nullptr }; void define_field(VM& vm, Object& receiver) const; }; Vector<InstanceField> const& fields() const { return m_fields; } - void add_field(StringOrSymbol property_key, ECMAScriptFunctionObject* initializer) { m_fields.empend(property_key, initializer); } + void add_field(PropertyName property_key, ECMAScriptFunctionObject* initializer) { m_fields.empend(property_key, initializer); } // This is for IsSimpleParameterList (static semantics) bool has_simple_parameter_list() const { return m_has_simple_parameter_list; } diff --git a/Userland/Libraries/LibJS/Tests/classes/class-inheritance.js b/Userland/Libraries/LibJS/Tests/classes/class-inheritance.js index 36c6bd9f2c..336e2b729b 100644 --- a/Userland/Libraries/LibJS/Tests/classes/class-inheritance.js +++ b/Userland/Libraries/LibJS/Tests/classes/class-inheritance.js @@ -170,3 +170,32 @@ test("issue #7045, super constructor call from child class in catch {}", () => { const c = new Child(); expect(c.x).toBe("Error in Child constructor"); }); + +test("Issue #7044, super property access before super() call", () => { + class Foo { + constructor() { + super.bar; + } + } + + new Foo(); +}); + +test("Issue #8574, super property access before super() call", () => { + var hit = false; + + class Foo extends Object { + constructor() { + expect(() => { + const foo = super.bar(); + }).toThrowWithMessage(ReferenceError, "|this| has not been initialized"); + hit = true; + } + } + + // Note: We catch two exceptions here. + expect(() => { + new Foo(); + }).toThrowWithMessage(ReferenceError, "|this| has not been initialized"); + expect(hit).toBeTrue(); +}); |