summaryrefslogtreecommitdiff
path: root/Userland/Libraries
diff options
context:
space:
mode:
authordavidot <davidot@serenityos.org>2021-10-20 21:29:47 +0200
committerLinus Groh <mail@linusgroh.de>2021-10-20 23:19:17 +0100
commit6b2accce317e1226feaf627ddd5fbca100eb7265 (patch)
tree3a3e8a589bdb17ba630109ec3d2b4268076a6b48 /Userland/Libraries
parent1245512c508d88e2a025d9d4e58297f76afd34bb (diff)
downloadserenity-6b2accce317e1226feaf627ddd5fbca100eb7265.zip
LibJS: Add static initializers to classes
Diffstat (limited to 'Userland/Libraries')
-rw-r--r--Userland/Libraries/LibJS/AST.cpp55
-rw-r--r--Userland/Libraries/LibJS/AST.h21
-rw-r--r--Userland/Libraries/LibJS/Parser.cpp23
-rw-r--r--Userland/Libraries/LibJS/Tests/classes/class-static-initializers.js48
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);
+});