mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 00:57:36 +00:00
LibJS: Add support for public fields in classes
This commit is contained in:
parent
3b6a8d1d53
commit
def8b44c40
9 changed files with 265 additions and 11 deletions
|
@ -310,7 +310,8 @@ Value SuperCall::execute(Interpreter& interpreter, GlobalObject& global_object)
|
||||||
[[maybe_unused]] auto& f = this_er.function_object();
|
[[maybe_unused]] auto& f = this_er.function_object();
|
||||||
|
|
||||||
// 11. Perform ? InitializeInstanceElements(result, F).
|
// 11. Perform ? InitializeInstanceElements(result, F).
|
||||||
// FIXME: This is missing here.
|
VERIFY(result.is_object());
|
||||||
|
vm.initialize_instance_elements(result.as_object(), f);
|
||||||
|
|
||||||
// 12. Return result.
|
// 12. Return result.
|
||||||
return result;
|
return result;
|
||||||
|
@ -867,6 +868,12 @@ Value ClassMethod::execute(Interpreter& interpreter, GlobalObject& global_object
|
||||||
return m_function->execute(interpreter, global_object);
|
return m_function->execute(interpreter, global_object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Value ClassField::execute(Interpreter& interpreter, GlobalObject&) const
|
||||||
|
{
|
||||||
|
InterpreterNodeScope node_scope { interpreter, *this };
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
Value ClassExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const
|
Value ClassExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const
|
||||||
{
|
{
|
||||||
InterpreterNodeScope node_scope { interpreter, *this };
|
InterpreterNodeScope node_scope { interpreter, *this };
|
||||||
|
@ -923,7 +930,7 @@ Value ClassExpression::execute(Interpreter& interpreter, GlobalObject& global_ob
|
||||||
interpreter.vm().throw_exception<TypeError>(global_object, ErrorType::NotAnObject, "Class prototype");
|
interpreter.vm().throw_exception<TypeError>(global_object, ErrorType::NotAnObject, "Class prototype");
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
for (const auto& method : m_methods) {
|
for (auto const& method : m_methods) {
|
||||||
auto method_value = method.execute(interpreter, global_object);
|
auto method_value = method.execute(interpreter, global_object);
|
||||||
if (interpreter.exception())
|
if (interpreter.exception())
|
||||||
return {};
|
return {};
|
||||||
|
@ -960,6 +967,40 @@ Value ClassExpression::execute(Interpreter& interpreter, GlobalObject& global_ob
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto& field : m_fields) {
|
||||||
|
auto key = field.key().execute(interpreter, global_object);
|
||||||
|
if (interpreter.exception())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
auto property_key = key.to_property_key(global_object);
|
||||||
|
if (interpreter.exception())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
FunctionObject* 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 = OrdinaryFunctionObject::create(interpreter.global_object(), property_key.to_display_string(), *body, {}, 0, interpreter.lexical_environment(), FunctionKind::Regular, false);
|
||||||
|
initializer->set_home_object(field.is_static() ? class_constructor : &class_prototype.as_object());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field.is_static()) {
|
||||||
|
Value field_value = js_undefined();
|
||||||
|
if (initializer) {
|
||||||
|
field_value = interpreter.vm().call(*initializer, class_constructor_value);
|
||||||
|
if (interpreter.exception())
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
class_constructor->create_data_property_or_throw(property_key, field_value);
|
||||||
|
if (interpreter.exception())
|
||||||
|
return {};
|
||||||
|
} else {
|
||||||
|
class_constructor->add_field(property_key, initializer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return class_constructor;
|
return class_constructor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1186,6 +1227,11 @@ void ClassExpression::dump(int indent) const
|
||||||
outln("(Methods)");
|
outln("(Methods)");
|
||||||
for (auto& method : m_methods)
|
for (auto& method : m_methods)
|
||||||
method.dump(indent + 1);
|
method.dump(indent + 1);
|
||||||
|
|
||||||
|
print_indent(indent);
|
||||||
|
outln("(Fields)");
|
||||||
|
for (auto& field : m_fields)
|
||||||
|
field.dump(indent + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClassMethod::dump(int indent) const
|
void ClassMethod::dump(int indent) const
|
||||||
|
@ -1219,6 +1265,23 @@ void ClassMethod::dump(int indent) const
|
||||||
m_function->dump(indent + 1);
|
m_function->dump(indent + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ClassField::dump(int indent) const
|
||||||
|
{
|
||||||
|
ASTNode::dump(indent);
|
||||||
|
print_indent(indent);
|
||||||
|
outln("(Key)");
|
||||||
|
m_key->dump(indent + 1);
|
||||||
|
|
||||||
|
print_indent(indent);
|
||||||
|
outln("Static: {}", m_is_static);
|
||||||
|
|
||||||
|
if (m_initializer) {
|
||||||
|
print_indent(indent);
|
||||||
|
outln("(Initializer)");
|
||||||
|
m_initializer->dump(indent + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void StringLiteral::dump(int indent) const
|
void StringLiteral::dump(int indent) const
|
||||||
{
|
{
|
||||||
print_indent(indent);
|
print_indent(indent);
|
||||||
|
|
|
@ -933,6 +933,30 @@ private:
|
||||||
bool m_is_static;
|
bool m_is_static;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ClassField final : public ASTNode {
|
||||||
|
public:
|
||||||
|
ClassField(SourceRange source_range, NonnullRefPtr<Expression> key, RefPtr<Expression> init, bool is_static)
|
||||||
|
: ASTNode(source_range)
|
||||||
|
, 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 void dump(int indent) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
NonnullRefPtr<Expression> m_key;
|
||||||
|
RefPtr<Expression> m_initializer;
|
||||||
|
bool m_is_static;
|
||||||
|
};
|
||||||
|
|
||||||
class SuperExpression final : public Expression {
|
class SuperExpression final : public Expression {
|
||||||
public:
|
public:
|
||||||
explicit SuperExpression(SourceRange source_range)
|
explicit SuperExpression(SourceRange source_range)
|
||||||
|
@ -948,12 +972,13 @@ public:
|
||||||
|
|
||||||
class ClassExpression final : public Expression {
|
class ClassExpression final : public Expression {
|
||||||
public:
|
public:
|
||||||
ClassExpression(SourceRange source_range, String name, RefPtr<FunctionExpression> constructor, RefPtr<Expression> super_class, NonnullRefPtrVector<ClassMethod> methods)
|
ClassExpression(SourceRange source_range, String name, RefPtr<FunctionExpression> constructor, RefPtr<Expression> super_class, NonnullRefPtrVector<ClassMethod> methods, NonnullRefPtrVector<ClassField> fields)
|
||||||
: Expression(source_range)
|
: Expression(source_range)
|
||||||
, m_name(move(name))
|
, m_name(move(name))
|
||||||
, m_constructor(move(constructor))
|
, m_constructor(move(constructor))
|
||||||
, m_super_class(move(super_class))
|
, m_super_class(move(super_class))
|
||||||
, m_methods(move(methods))
|
, m_methods(move(methods))
|
||||||
|
, m_fields(move(fields))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -968,6 +993,7 @@ private:
|
||||||
RefPtr<FunctionExpression> m_constructor;
|
RefPtr<FunctionExpression> m_constructor;
|
||||||
RefPtr<Expression> m_super_class;
|
RefPtr<Expression> m_super_class;
|
||||||
NonnullRefPtrVector<ClassMethod> m_methods;
|
NonnullRefPtrVector<ClassMethod> m_methods;
|
||||||
|
NonnullRefPtrVector<ClassField> m_fields;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ClassDeclaration final : public Declaration {
|
class ClassDeclaration final : public Declaration {
|
||||||
|
|
|
@ -643,6 +643,7 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
|
||||||
consume(TokenType::Class);
|
consume(TokenType::Class);
|
||||||
|
|
||||||
NonnullRefPtrVector<ClassMethod> methods;
|
NonnullRefPtrVector<ClassMethod> methods;
|
||||||
|
NonnullRefPtrVector<ClassField> fields;
|
||||||
RefPtr<Expression> super_class;
|
RefPtr<Expression> super_class;
|
||||||
RefPtr<FunctionExpression> constructor;
|
RefPtr<FunctionExpression> constructor;
|
||||||
|
|
||||||
|
@ -693,8 +694,8 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
|
||||||
is_generator = true;
|
is_generator = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StringView name;
|
||||||
if (match_property_key()) {
|
if (match_property_key()) {
|
||||||
StringView name;
|
|
||||||
if (!is_generator && m_state.current_token.original_value() == "static"sv) {
|
if (!is_generator && m_state.current_token.original_value() == "static"sv) {
|
||||||
if (match(TokenType::Identifier)) {
|
if (match(TokenType::Identifier)) {
|
||||||
consume();
|
consume();
|
||||||
|
@ -707,12 +708,12 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
|
||||||
}
|
}
|
||||||
|
|
||||||
if (match(TokenType::Identifier)) {
|
if (match(TokenType::Identifier)) {
|
||||||
auto identifier_name = m_state.current_token.value();
|
auto identifier_name = m_state.current_token.original_value();
|
||||||
|
|
||||||
if (identifier_name == "get") {
|
if (identifier_name == "get"sv) {
|
||||||
method_kind = ClassMethod::Kind::Getter;
|
method_kind = ClassMethod::Kind::Getter;
|
||||||
consume();
|
consume();
|
||||||
} else if (identifier_name == "set") {
|
} else if (identifier_name == "set"sv) {
|
||||||
method_kind = ClassMethod::Kind::Setter;
|
method_kind = ClassMethod::Kind::Setter;
|
||||||
consume();
|
consume();
|
||||||
}
|
}
|
||||||
|
@ -740,7 +741,7 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
|
||||||
// It is a Syntax Error if PropName of MethodDefinition is "prototype".
|
// It is a Syntax Error if PropName of MethodDefinition is "prototype".
|
||||||
if (is_static && name == "prototype"sv)
|
if (is_static && name == "prototype"sv)
|
||||||
syntax_error("Classes may not have a static property named 'prototype'");
|
syntax_error("Classes may not have a static property named 'prototype'");
|
||||||
} else if (match(TokenType::ParenOpen) && (is_static || method_kind != ClassMethod::Kind::Method)) {
|
} else if ((match(TokenType::ParenOpen) || match(TokenType::Equals)) && (is_static || method_kind != ClassMethod::Kind::Method)) {
|
||||||
switch (method_kind) {
|
switch (method_kind) {
|
||||||
case ClassMethod::Kind::Method:
|
case ClassMethod::Kind::Method:
|
||||||
VERIFY(is_static);
|
VERIFY(is_static);
|
||||||
|
@ -762,7 +763,7 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constructor may be a StringLiteral or an Identifier.
|
// Constructor may be a StringLiteral or an Identifier.
|
||||||
if (!is_static && name == "constructor") {
|
if (!is_static && name == "constructor"sv) {
|
||||||
if (method_kind != ClassMethod::Kind::Method)
|
if (method_kind != ClassMethod::Kind::Method)
|
||||||
syntax_error("Class constructor may not be an accessor");
|
syntax_error("Class constructor may not be an accessor");
|
||||||
if (!constructor.is_null())
|
if (!constructor.is_null())
|
||||||
|
@ -792,9 +793,29 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
|
||||||
} else {
|
} else {
|
||||||
syntax_error("No key for class method");
|
syntax_error("No key for class method");
|
||||||
}
|
}
|
||||||
} else {
|
} else if (is_generator) {
|
||||||
expected("ParenOpen");
|
expected("ParenOpen");
|
||||||
consume();
|
consume();
|
||||||
|
} else if (property_key.is_null()) {
|
||||||
|
expected("property key");
|
||||||
|
consume();
|
||||||
|
} else {
|
||||||
|
if (name == "constructor"sv)
|
||||||
|
syntax_error("Class cannot have field named 'constructor'");
|
||||||
|
|
||||||
|
RefPtr<Expression> initializer;
|
||||||
|
|
||||||
|
if (match(TokenType::Equals)) {
|
||||||
|
consume();
|
||||||
|
|
||||||
|
TemporaryChange super_property_access_rollback(m_state.allow_super_property_lookup, true);
|
||||||
|
TemporaryChange field_initializer_rollback(m_state.in_class_field_initializer, true);
|
||||||
|
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));
|
||||||
|
|
||||||
|
consume_or_insert_semicolon();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -821,7 +842,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));
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
Parser::PrimaryExpressionParseResult Parser::parse_primary_expression()
|
Parser::PrimaryExpressionParseResult Parser::parse_primary_expression()
|
||||||
|
@ -1535,6 +1556,7 @@ NonnullRefPtr<AssignmentExpression> Parser::parse_assignment_expression(Assignme
|
||||||
parser.m_state.in_break_context = m_state.in_break_context;
|
parser.m_state.in_break_context = m_state.in_break_context;
|
||||||
parser.m_state.in_continue_context = m_state.in_continue_context;
|
parser.m_state.in_continue_context = m_state.in_continue_context;
|
||||||
parser.m_state.string_legacy_octal_escape_sequence_in_scope = m_state.string_legacy_octal_escape_sequence_in_scope;
|
parser.m_state.string_legacy_octal_escape_sequence_in_scope = m_state.string_legacy_octal_escape_sequence_in_scope;
|
||||||
|
parser.m_state.in_class_field_initializer = m_state.in_class_field_initializer;
|
||||||
|
|
||||||
auto result = parser.parse_binding_pattern();
|
auto result = parser.parse_binding_pattern();
|
||||||
if (parser.has_errors())
|
if (parser.has_errors())
|
||||||
|
@ -1577,6 +1599,8 @@ NonnullRefPtr<Identifier> Parser::parse_identifier()
|
||||||
{
|
{
|
||||||
auto identifier_start = position();
|
auto identifier_start = position();
|
||||||
auto token = consume_identifier();
|
auto token = consume_identifier();
|
||||||
|
if (m_state.in_class_field_initializer && token.value() == "arguments"sv)
|
||||||
|
syntax_error("'arguments' is not allowed in class field initializer");
|
||||||
return create_ast_node<Identifier>(
|
return create_ast_node<Identifier>(
|
||||||
{ m_state.current_token.filename(), identifier_start, position() },
|
{ m_state.current_token.filename(), identifier_start, position() },
|
||||||
token.value());
|
token.value());
|
||||||
|
@ -1757,6 +1781,7 @@ NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(u8 parse_options)
|
||||||
TemporaryChange super_constructor_call_rollback(m_state.allow_super_constructor_call, !!(parse_options & FunctionNodeParseOptions::AllowSuperConstructorCall));
|
TemporaryChange super_constructor_call_rollback(m_state.allow_super_constructor_call, !!(parse_options & FunctionNodeParseOptions::AllowSuperConstructorCall));
|
||||||
TemporaryChange break_context_rollback(m_state.in_break_context, false);
|
TemporaryChange break_context_rollback(m_state.in_break_context, false);
|
||||||
TemporaryChange continue_context_rollback(m_state.in_continue_context, false);
|
TemporaryChange continue_context_rollback(m_state.in_continue_context, false);
|
||||||
|
TemporaryChange class_field_initializer_rollback(m_state.in_class_field_initializer, false);
|
||||||
|
|
||||||
ScopePusher scope(*this, ScopePusher::Var, Parser::Scope::Function);
|
ScopePusher scope(*this, ScopePusher::Var, Parser::Scope::Function);
|
||||||
|
|
||||||
|
|
|
@ -258,6 +258,7 @@ private:
|
||||||
bool in_break_context { false };
|
bool in_break_context { false };
|
||||||
bool in_continue_context { false };
|
bool in_continue_context { false };
|
||||||
bool string_legacy_octal_escape_sequence_in_scope { false };
|
bool string_legacy_octal_escape_sequence_in_scope { false };
|
||||||
|
bool in_class_field_initializer { false };
|
||||||
|
|
||||||
ParserState(Lexer, Program::Type);
|
ParserState(Lexer, Program::Type);
|
||||||
};
|
};
|
||||||
|
|
|
@ -66,6 +66,23 @@ BoundFunction* FunctionObject::bind(Value bound_this_value, Vector<Value> argume
|
||||||
return heap().allocate<BoundFunction>(global_object(), global_object(), target_function, bound_this_object, move(all_bound_arguments), computed_length, constructor_prototype);
|
return heap().allocate<BoundFunction>(global_object(), global_object(), target_function, bound_this_object, move(all_bound_arguments), computed_length, constructor_prototype);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FunctionObject::add_field(StringOrSymbol property_key, FunctionObject* initializer)
|
||||||
|
{
|
||||||
|
m_fields.empend(property_key, initializer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7.3.31 DefineField ( receiver, fieldRecord ), https://tc39.es/ecma262/#sec-definefield
|
||||||
|
void FunctionObject::InstanceField::define_field(VM& vm, Object& receiver) const
|
||||||
|
{
|
||||||
|
Value init_value = js_undefined();
|
||||||
|
if (initializer) {
|
||||||
|
init_value = vm.call(*initializer, receiver.value_of());
|
||||||
|
if (vm.exception())
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
receiver.create_data_property_or_throw(name, init_value);
|
||||||
|
}
|
||||||
|
|
||||||
void FunctionObject::visit_edges(Visitor& visitor)
|
void FunctionObject::visit_edges(Visitor& visitor)
|
||||||
{
|
{
|
||||||
Object::visit_edges(visitor);
|
Object::visit_edges(visitor);
|
||||||
|
@ -75,6 +92,9 @@ void FunctionObject::visit_edges(Visitor& visitor)
|
||||||
|
|
||||||
for (auto argument : m_bound_arguments)
|
for (auto argument : m_bound_arguments)
|
||||||
visitor.visit(argument);
|
visitor.visit(argument);
|
||||||
|
|
||||||
|
for (auto& field : m_fields)
|
||||||
|
visitor.visit(field.initializer);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,17 @@ public:
|
||||||
// This is for IsSimpleParameterList (static semantics)
|
// This is for IsSimpleParameterList (static semantics)
|
||||||
bool has_simple_parameter_list() const { return m_has_simple_parameter_list; }
|
bool has_simple_parameter_list() const { return m_has_simple_parameter_list; }
|
||||||
|
|
||||||
|
// [[Fields]]
|
||||||
|
struct InstanceField {
|
||||||
|
StringOrSymbol name;
|
||||||
|
FunctionObject* initializer { nullptr };
|
||||||
|
|
||||||
|
void define_field(VM& vm, Object& receiver) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector<InstanceField> const& fields() const { return m_fields; }
|
||||||
|
void add_field(StringOrSymbol property_key, FunctionObject* initializer);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void visit_edges(Visitor&) override;
|
virtual void visit_edges(Visitor&) override;
|
||||||
|
|
||||||
|
@ -79,6 +90,7 @@ private:
|
||||||
ConstructorKind m_constructor_kind = ConstructorKind::Base;
|
ConstructorKind m_constructor_kind = ConstructorKind::Base;
|
||||||
ThisMode m_this_mode { ThisMode::Global };
|
ThisMode m_this_mode { ThisMode::Global };
|
||||||
bool m_has_simple_parameter_list { false };
|
bool m_has_simple_parameter_list { false };
|
||||||
|
Vector<InstanceField> m_fields;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -457,6 +457,16 @@ static void append_bound_and_passed_arguments(MarkedValueList& arguments, Vector
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 7.3.32 InitializeInstanceElements ( O, constructor ), https://tc39.es/ecma262/#sec-initializeinstanceelements
|
||||||
|
void VM::initialize_instance_elements(Object& object, FunctionObject& constructor)
|
||||||
|
{
|
||||||
|
for (auto& field : constructor.fields()) {
|
||||||
|
field.define_field(*this, object);
|
||||||
|
if (exception())
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Value VM::construct(FunctionObject& function, FunctionObject& new_target, Optional<MarkedValueList> arguments)
|
Value VM::construct(FunctionObject& function, FunctionObject& new_target, Optional<MarkedValueList> arguments)
|
||||||
{
|
{
|
||||||
auto& global_object = function.global_object();
|
auto& global_object = function.global_object();
|
||||||
|
@ -494,6 +504,14 @@ Value VM::construct(FunctionObject& function, FunctionObject& new_target, Option
|
||||||
|
|
||||||
// If we are a Derived constructor, |this| has not been constructed before super is called.
|
// If we are a Derived constructor, |this| has not been constructed before super is called.
|
||||||
callee_context.this_value = this_argument;
|
callee_context.this_value = this_argument;
|
||||||
|
|
||||||
|
if (function.constructor_kind() == FunctionObject::ConstructorKind::Base) {
|
||||||
|
VERIFY(this_argument.is_object());
|
||||||
|
initialize_instance_elements(this_argument.as_object(), function);
|
||||||
|
if (exception())
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
auto result = function.construct(new_target);
|
auto result = function.construct(new_target);
|
||||||
|
|
||||||
pop_execution_context();
|
pop_execution_context();
|
||||||
|
|
|
@ -267,6 +267,8 @@ public:
|
||||||
Function<void(const Promise&)> on_promise_unhandled_rejection;
|
Function<void(const Promise&)> on_promise_unhandled_rejection;
|
||||||
Function<void(const Promise&)> on_promise_rejection_handled;
|
Function<void(const Promise&)> on_promise_rejection_handled;
|
||||||
|
|
||||||
|
void initialize_instance_elements(Object& object, FunctionObject& constructor);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
VM();
|
VM();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
test("basic functionality", () => {
|
||||||
|
class A {
|
||||||
|
number = 3;
|
||||||
|
|
||||||
|
string = "foo";
|
||||||
|
|
||||||
|
uninitialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
const a = new A();
|
||||||
|
expect(a.number).toBe(3);
|
||||||
|
expect(a.string).toBe("foo");
|
||||||
|
expect(a.uninitialized).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("extended name syntax", () => {
|
||||||
|
class A {
|
||||||
|
"field with space" = 1;
|
||||||
|
|
||||||
|
12 = "twelve";
|
||||||
|
|
||||||
|
[`he${"llo"}`] = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
const a = new A();
|
||||||
|
expect(a["field with space"]).toBe(1);
|
||||||
|
expect(a[12]).toBe("twelve");
|
||||||
|
expect(a.hello).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("initializer has correct this value", () => {
|
||||||
|
class A {
|
||||||
|
this_val = this;
|
||||||
|
|
||||||
|
this_name = this.this_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
const a = new A();
|
||||||
|
expect(a.this_val).toBe(a);
|
||||||
|
expect(a.this_name).toBe(a);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("static fields", () => {
|
||||||
|
class A {
|
||||||
|
static simple = 1;
|
||||||
|
simple = 2;
|
||||||
|
|
||||||
|
static "with space" = 3;
|
||||||
|
|
||||||
|
static 24 = "two dozen";
|
||||||
|
|
||||||
|
static [`he${"llo"}`] = "friends";
|
||||||
|
|
||||||
|
static this_val = this;
|
||||||
|
static this_name = this.name;
|
||||||
|
static this_val2 = this.this_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(A.simple).toBe(1);
|
||||||
|
expect(A["with space"]).toBe(3);
|
||||||
|
expect(A[24]).toBe("two dozen");
|
||||||
|
expect(A.hello).toBe("friends");
|
||||||
|
|
||||||
|
expect(A.this_val).toBe(A);
|
||||||
|
expect(A.this_name).toBe("A");
|
||||||
|
expect(A.this_val2).toBe(A);
|
||||||
|
|
||||||
|
const a = new A();
|
||||||
|
expect(a.simple).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("with super class", () => {
|
||||||
|
class A {
|
||||||
|
super_field = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
class B extends A {
|
||||||
|
references_super_field = super.super_field;
|
||||||
|
arrow_ref_super = () => (super.super_field = 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
const b = new B();
|
||||||
|
expect(b.super_field).toBe(3);
|
||||||
|
expect(b.references_super_field).toBeUndefined();
|
||||||
|
b.arrow_ref_super();
|
||||||
|
expect(b.super_field).toBe(4);
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue