mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 18:17:44 +00:00
LibJS: Add static initializers to classes
This commit is contained in:
parent
1245512c50
commit
6b2accce31
4 changed files with 134 additions and 13 deletions
|
@ -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) {
|
||||
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(interpreter.vm().call(*field.initializer, class_constructor_value));
|
||||
|
||||
// FIXME: Handle private static fields
|
||||
field_value = TRY(call(global_object, field.initializer, class_constructor_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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue