diff options
author | davidot <davidot@serenityos.org> | 2021-10-20 21:29:47 +0200 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2021-10-20 23:19:17 +0100 |
commit | 6b2accce317e1226feaf627ddd5fbca100eb7265 (patch) | |
tree | 3a3e8a589bdb17ba630109ec3d2b4268076a6b48 /Userland/Libraries | |
parent | 1245512c508d88e2a025d9d4e58297f76afd34bb (diff) | |
download | serenity-6b2accce317e1226feaf627ddd5fbca100eb7265.zip |
LibJS: Add static initializers to classes
Diffstat (limited to 'Userland/Libraries')
-rw-r--r-- | Userland/Libraries/LibJS/AST.cpp | 55 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/AST.h | 21 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Parser.cpp | 23 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Tests/classes/class-static-initializers.js | 48 |
4 files changed, 134 insertions, 13 deletions
diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp index f74a1d2ea3..806157e4b1 100644 --- a/Userland/Libraries/LibJS/AST.cpp +++ b/Userland/Libraries/LibJS/AST.cpp @@ -1244,6 +1244,18 @@ ThrowCompletionOr<ClassElement::ClassValue> ClassField::class_element_evaluation }; } +// 15.7.11 Runtime Semantics: ClassStaticBlockDefinitionEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-classstaticblockdefinitionevaluation +ThrowCompletionOr<ClassElement::ClassValue> StaticInitializer::class_element_evaluation(Interpreter& interpreter, GlobalObject& global_object, Object& home_object) const +{ + auto* lexical_environment = interpreter.vm().running_execution_context().lexical_environment; + + // Note: The function bodyFunction is never directly accessible to ECMAScript code. + auto* body_function = ECMAScriptFunctionObject::create(global_object, "", *m_function_body, {}, 0, lexical_environment, FunctionKind::Regular, true, false, m_contains_direct_call_to_eval, false); + body_function->set_home_object(&home_object); + + return ClassValue { normal_completion(body_function) }; +} + Value ClassExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const { InterpreterNodeScope node_scope { interpreter, *this }; @@ -1364,8 +1376,10 @@ ThrowCompletionOr<Value> ClassExpression::class_definition_evaluation(Interprete prototype->define_direct_property(vm.names.constructor, class_constructor, Attribute::Writable | Attribute::Configurable); + using StaticElement = Variant<ClassElement::ClassFieldDefinition, ECMAScriptFunctionObject*>; + Vector<ClassElement::ClassFieldDefinition> instance_fields; - Vector<ClassElement::ClassFieldDefinition> static_fields; + Vector<StaticElement> static_elements; // FIXME: Do things with private methods, and fields here for (auto const& element : m_elements) { @@ -1375,12 +1389,16 @@ ThrowCompletionOr<Value> ClassExpression::class_definition_evaluation(Interprete if (auto* class_field_definition_ptr = element_value.get_pointer<ClassElement::ClassFieldDefinition>()) { if (element.is_static()) - static_fields.append(move(*class_field_definition_ptr)); + static_elements.append(move(*class_field_definition_ptr)); else instance_fields.append(move(*class_field_definition_ptr)); + } else if (element.class_element_kind() == ClassElement::ElementKind::StaticInitializer) { + // We use Completion to hold the ClassStaticBlockDefinition Record. + VERIFY(element_value.has<Completion>() && element_value.get<Completion>().has_value()); + auto element_object = element_value.get<Completion>().value(); + VERIFY(is<ECMAScriptFunctionObject>(element_object.as_object())); + static_elements.append(static_cast<ECMAScriptFunctionObject*>(&element_object.as_object())); } - - // FIXME: Else if element is class static block } vm.running_execution_context().lexical_environment = environment; @@ -1393,18 +1411,23 @@ ThrowCompletionOr<Value> ClassExpression::class_definition_evaluation(Interprete 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 + for (auto& element : static_elements) { + TRY(element.visit( + [&](ClassElement::ClassFieldDefinition const& field) -> ThrowCompletionOr<void> { + Value field_value = js_undefined(); + if (field.initializer) + field_value = TRY(call(global_object, field.initializer, class_constructor_value)); - TRY(class_constructor->create_data_property_or_throw(field.name, field_value)); + TRY(class_constructor->create_data_property_or_throw(field.name, field_value)); + return {}; + }, + [&](ECMAScriptFunctionObject* static_block_function) -> ThrowCompletionOr<void> { + // We discard any value returned here. + TRY(call(global_object, static_block_function, class_constructor_value)); + return {}; + })); } - // FIXME: Run static initializers - return Value(class_constructor); } @@ -1689,6 +1712,12 @@ void ClassField::dump(int indent) const } } +void StaticInitializer::dump(int indent) const +{ + ASTNode::dump(indent); + m_function_body->dump(indent + 1); +} + void StringLiteral::dump(int indent) const { print_indent(indent); diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h index be7806a4f0..774e34df74 100644 --- a/Userland/Libraries/LibJS/AST.h +++ b/Userland/Libraries/LibJS/AST.h @@ -1013,6 +1013,7 @@ public: enum class ElementKind { Method, Field, + StaticInitializer, }; virtual ElementKind class_element_kind() const = 0; @@ -1023,6 +1024,7 @@ public: ECMAScriptFunctionObject* initializer { nullptr }; }; + // We use the Completion also as a ClassStaticBlockDefinition Record. using ClassValue = Variant<ClassFieldDefinition, Completion>; virtual ThrowCompletionOr<ClassValue> class_element_evaluation(Interpreter&, GlobalObject&, Object& home_object) const = 0; @@ -1082,6 +1084,25 @@ private: RefPtr<Expression> m_initializer; }; +class StaticInitializer final : public ClassElement { +public: + StaticInitializer(SourceRange source_range, NonnullRefPtr<FunctionBody> function_body, bool contains_direct_call_to_eval) + : ClassElement(source_range, true) + , m_function_body(move(function_body)) + , m_contains_direct_call_to_eval(contains_direct_call_to_eval) + { + } + + virtual ElementKind class_element_kind() const override { return ElementKind::StaticInitializer; } + virtual ThrowCompletionOr<ClassValue> class_element_evaluation(Interpreter&, GlobalObject&, Object& home_object) const override; + + virtual void dump(int indent) const override; + +private: + NonnullRefPtr<FunctionBody> m_function_body; + bool m_contains_direct_call_to_eval { false }; +}; + class SuperExpression final : public Expression { public: explicit SuperExpression(SourceRange source_range) diff --git a/Userland/Libraries/LibJS/Parser.cpp b/Userland/Libraries/LibJS/Parser.cpp index 06e866f284..03fecfd13b 100644 --- a/Userland/Libraries/LibJS/Parser.cpp +++ b/Userland/Libraries/LibJS/Parser.cpp @@ -97,6 +97,11 @@ public: return scope_pusher; } + static ScopePusher static_init_block_scope(Parser& parser, ScopeNode& node) + { + return ScopePusher(parser, &node, true); + } + void add_declaration(NonnullRefPtr<Declaration> declaration) { if (declaration->is_lexical_declaration()) { @@ -957,6 +962,24 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_ break; } property_key = create_ast_node<StringLiteral>({ m_state.current_token.filename(), rule_start.position(), position() }, name); + } else if (match(TokenType::CurlyOpen) && is_static) { + auto static_start = push_start(); + consume(TokenType::CurlyOpen); + + auto static_init_block = create_ast_node<FunctionBody>({ m_state.current_token.filename(), rule_start.position(), position() }); + + TemporaryChange break_context_rollback(m_state.in_break_context, false); + TemporaryChange continue_context_rollback(m_state.in_continue_context, false); + TemporaryChange function_context_rollback(m_state.in_function_context, false); + TemporaryChange generator_function_context_rollback(m_state.in_generator_function_context, false); + TemporaryChange in_class_field_initializer_rollback(m_state.in_class_field_initializer, true); + + ScopePusher static_init_scope = ScopePusher::static_init_block_scope(*this, *static_init_block); + parse_statement_list(static_init_block); + + consume(TokenType::CurlyClose); + elements.append(create_ast_node<StaticInitializer>({ m_state.current_token.filename(), static_start.position(), position() }, move(static_init_block), static_init_scope.contains_direct_call_to_eval())); + continue; } else { expected("property key"); } diff --git a/Userland/Libraries/LibJS/Tests/classes/class-static-initializers.js b/Userland/Libraries/LibJS/Tests/classes/class-static-initializers.js new file mode 100644 index 0000000000..feb7afe76c --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/classes/class-static-initializers.js @@ -0,0 +1,48 @@ +test("basic functionality", () => { + var called = false; + class A { + static { + expect(called).toBeFalse(); + expect(this.name).toBe("A"); + called = true; + } + } + + expect(called).toBeTrue(); + new A(); + expect(called).toBeTrue(); +}); + +test("called in order", () => { + var i = 0; + class A { + static { + expect(i).toBe(0); + i++; + } + + static method() { + return 2; + } + + static { + expect(i).toBe(1); + i++; + } + } + + expect(i).toBe(2); + new A(); + expect(i).toBe(2); +}); + +test("correct this", () => { + var thisValue = null; + class A { + static { + thisValue = this; + } + } + + expect(thisValue).toBe(A); +}); |