mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 10:18:11 +00:00
LibJS: Make class definition evaluation more spec like in ordering
This commit is contained in:
parent
d3ef08217b
commit
1245512c50
6 changed files with 258 additions and 131 deletions
|
@ -1158,16 +1158,90 @@ Value SuperExpression::execute(Interpreter&, GlobalObject&) const
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|
||||||
Value ClassMethod::execute(Interpreter& interpreter, GlobalObject& global_object) const
|
Value ClassElement::execute(Interpreter&, GlobalObject&) const
|
||||||
{
|
{
|
||||||
InterpreterNodeScope node_scope { interpreter, *this };
|
// Note: The semantics of class element are handled in class_element_evaluation
|
||||||
return m_function->execute(interpreter, global_object);
|
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
|
Value ClassExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const
|
||||||
|
@ -1208,14 +1282,70 @@ ThrowCompletionOr<Value> ClassExpression::class_definition_evaluation(Interprete
|
||||||
auto* environment = vm.lexical_environment();
|
auto* environment = vm.lexical_environment();
|
||||||
VERIFY(environment);
|
VERIFY(environment);
|
||||||
auto* class_scope = new_declarative_environment(*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 = [&] {
|
ArmedScopeGuard restore_environment = [&] {
|
||||||
vm.running_execution_context().lexical_environment = environment;
|
vm.running_execution_context().lexical_environment = environment;
|
||||||
};
|
};
|
||||||
vm.running_execution_context().lexical_environment = class_scope;
|
|
||||||
|
|
||||||
|
if (!binding_name.is_null())
|
||||||
|
MUST(class_scope->create_immutable_binding(global_object, binding_name, true));
|
||||||
|
|
||||||
|
// FIXME: Add classPrivateEnvironment
|
||||||
|
|
||||||
|
// FIXME: Append names to private environment
|
||||||
|
|
||||||
|
auto* proto_parent = vm.current_realm()->global_object().object_prototype();
|
||||||
|
|
||||||
|
auto* constructor_parent = vm.current_realm()->global_object().function_prototype();
|
||||||
|
|
||||||
|
if (!m_super_class.is_null()) {
|
||||||
|
vm.running_execution_context().lexical_environment = class_scope;
|
||||||
|
|
||||||
|
// Note: Since our execute does evaluation and GetValue in once we must check for a valid reference first
|
||||||
|
|
||||||
|
Value super_class;
|
||||||
|
|
||||||
|
auto reference = m_super_class->to_reference(interpreter, global_object);
|
||||||
|
if (auto* exception = interpreter.exception())
|
||||||
|
return throw_completion(exception->value());
|
||||||
|
|
||||||
|
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 (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());
|
||||||
|
|
||||||
|
if (super_class_prototype.is_null())
|
||||||
|
proto_parent = nullptr;
|
||||||
|
else
|
||||||
|
proto_parent = &super_class_prototype.as_object();
|
||||||
|
|
||||||
|
constructor_parent = &super_class.as_object();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* prototype = Object::create(global_object, proto_parent);
|
||||||
|
VERIFY(prototype);
|
||||||
|
|
||||||
|
vm.running_execution_context().lexical_environment = class_scope;
|
||||||
|
// FIXME: Activate the class private environment
|
||||||
|
|
||||||
|
// FIXME: Step 14.a is done in the parser. But maybe it shouldn't?
|
||||||
Value class_constructor_value = m_constructor->execute(interpreter, global_object);
|
Value class_constructor_value = m_constructor->execute(interpreter, global_object);
|
||||||
if (auto* exception = interpreter.exception())
|
if (auto* exception = interpreter.exception())
|
||||||
return throw_completion(exception->value());
|
return throw_completion(exception->value());
|
||||||
|
@ -1224,109 +1354,57 @@ ThrowCompletionOr<Value> ClassExpression::class_definition_evaluation(Interprete
|
||||||
|
|
||||||
VERIFY(class_constructor_value.is_function() && is<ECMAScriptFunctionObject>(class_constructor_value.as_function()));
|
VERIFY(class_constructor_value.is_function() && is<ECMAScriptFunctionObject>(class_constructor_value.as_function()));
|
||||||
auto* class_constructor = static_cast<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->set_is_class_constructor();
|
||||||
Value super_constructor = js_undefined();
|
class_constructor->define_direct_property(vm.names.prototype, prototype, Attribute::Writable);
|
||||||
if (!m_super_class.is_null()) {
|
TRY(class_constructor->internal_set_prototype_of(constructor_parent));
|
||||||
super_constructor = m_super_class->execute(interpreter, global_object);
|
|
||||||
if (auto* exception = interpreter.exception())
|
|
||||||
return throw_completion(exception->value());
|
|
||||||
|
|
||||||
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());
|
|
||||||
|
|
||||||
|
if (!m_super_class.is_null())
|
||||||
class_constructor->set_constructor_kind(ECMAScriptFunctionObject::ConstructorKind::Derived);
|
class_constructor->set_constructor_kind(ECMAScriptFunctionObject::ConstructorKind::Derived);
|
||||||
|
|
||||||
Object* super_constructor_prototype = nullptr;
|
prototype->define_direct_property(vm.names.constructor, class_constructor, Attribute::Writable | Attribute::Configurable);
|
||||||
if (!super_constructor.is_null()) {
|
|
||||||
auto super_constructor_prototype_value = TRY(super_constructor.as_object().get(vm.names.prototype));
|
|
||||||
|
|
||||||
if (!super_constructor_prototype_value.is_object() && !super_constructor_prototype_value.is_null())
|
Vector<ClassElement::ClassFieldDefinition> instance_fields;
|
||||||
return interpreter.vm().throw_completion<TypeError>(global_object, ErrorType::ClassExtendsValueInvalidPrototype, super_constructor_prototype_value.to_string_without_side_effects());
|
Vector<ClassElement::ClassFieldDefinition> static_fields;
|
||||||
|
|
||||||
if (super_constructor_prototype_value.is_object())
|
// FIXME: Do things with private methods, and fields here
|
||||||
super_constructor_prototype = &super_constructor_prototype_value.as_object();
|
for (auto const& element : m_elements) {
|
||||||
}
|
// Note: All ClassElementEvaluation start with evaluating the name (or we fake it).
|
||||||
auto* prototype = Object::create(global_object, super_constructor_prototype);
|
auto element_value = TRY(element.class_element_evaluation(interpreter, global_object, element.is_static() ? *class_constructor : *prototype));
|
||||||
|
// FIXME: If element is a private element
|
||||||
|
|
||||||
prototype->define_direct_property(vm.names.constructor, class_constructor, 0);
|
if (auto* class_field_definition_ptr = element_value.get_pointer<ClassElement::ClassFieldDefinition>()) {
|
||||||
if (auto* exception = interpreter.exception())
|
if (element.is_static())
|
||||||
return throw_completion(exception->value());
|
static_fields.append(move(*class_field_definition_ptr));
|
||||||
class_constructor->define_direct_property(vm.names.prototype, prototype, Attribute::Writable);
|
else
|
||||||
if (auto* exception = interpreter.exception())
|
instance_fields.append(move(*class_field_definition_ptr));
|
||||||
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 (!class_prototype.is_object())
|
|
||||||
return interpreter.vm().throw_completion<TypeError>(global_object, ErrorType::NotAnObject, "Class prototype");
|
|
||||||
|
|
||||||
for (auto const& method : m_methods) {
|
|
||||||
auto method_value = method.execute(interpreter, global_object);
|
|
||||||
if (auto* exception = interpreter.exception())
|
|
||||||
return throw_completion(exception->value());
|
|
||||||
|
|
||||||
auto& method_function = static_cast<ECMAScriptFunctionObject&>(method_value.as_function());
|
|
||||||
|
|
||||||
auto key = method.key().execute(interpreter, global_object);
|
|
||||||
if (auto* exception = interpreter.exception())
|
|
||||||
return throw_completion(exception->value());
|
|
||||||
|
|
||||||
auto property_key = TRY(key.to_property_key(global_object));
|
|
||||||
|
|
||||||
auto& target = method.is_static() ? *class_constructor : class_prototype.as_object();
|
|
||||||
method_function.set_home_object(&target);
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& field : m_fields) {
|
|
||||||
auto key = field.key().execute(interpreter, global_object);
|
|
||||||
if (auto* exception = interpreter.exception())
|
|
||||||
return throw_completion(exception->value());
|
|
||||||
|
|
||||||
auto property_key = TRY(key.to_property_key(global_object));
|
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.is_static()) {
|
// FIXME: Else if element is class static block
|
||||||
Value field_value = js_undefined();
|
|
||||||
if (initializer)
|
|
||||||
field_value = TRY(interpreter.vm().call(*initializer, class_constructor_value));
|
|
||||||
|
|
||||||
TRY(class_constructor->create_data_property_or_throw(property_key, field_value));
|
|
||||||
} else {
|
|
||||||
class_constructor->add_field(property_key, initializer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vm.running_execution_context().lexical_environment = environment;
|
vm.running_execution_context().lexical_environment = environment;
|
||||||
restore_environment.disarm();
|
restore_environment.disarm();
|
||||||
|
|
||||||
if (!binding_name.is_null())
|
if (!binding_name.is_null())
|
||||||
MUST(class_scope->initialize_binding(global_object, binding_name, class_constructor));
|
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);
|
return Value(class_constructor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1558,14 +1636,9 @@ void ClassExpression::dump(int indent) const
|
||||||
}
|
}
|
||||||
|
|
||||||
print_indent(indent);
|
print_indent(indent);
|
||||||
outln("(Methods)");
|
outln("(Elements)");
|
||||||
for (auto& method : m_methods)
|
for (auto& method : m_elements)
|
||||||
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
|
||||||
|
@ -1592,7 +1665,7 @@ void ClassMethod::dump(int indent) const
|
||||||
outln("Kind: {}", kind_string);
|
outln("Kind: {}", kind_string);
|
||||||
|
|
||||||
print_indent(indent);
|
print_indent(indent);
|
||||||
outln("Static: {}", m_is_static);
|
outln("Static: {}", is_static());
|
||||||
|
|
||||||
print_indent(indent);
|
print_indent(indent);
|
||||||
outln("(Function)");
|
outln("(Function)");
|
||||||
|
@ -1607,7 +1680,7 @@ void ClassField::dump(int indent) const
|
||||||
m_key->dump(indent + 1);
|
m_key->dump(indent + 1);
|
||||||
|
|
||||||
print_indent(indent);
|
print_indent(indent);
|
||||||
outln("Static: {}", m_is_static);
|
outln("Static: {}", is_static());
|
||||||
|
|
||||||
if (m_initializer) {
|
if (m_initializer) {
|
||||||
print_indent(indent);
|
print_indent(indent);
|
||||||
|
|
|
@ -1000,7 +1000,37 @@ private:
|
||||||
mutable Optional<EnvironmentCoordinate> m_cached_environment_coordinate;
|
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:
|
public:
|
||||||
enum class Kind {
|
enum class Kind {
|
||||||
Method,
|
Method,
|
||||||
|
@ -1009,50 +1039,47 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
ClassMethod(SourceRange source_range, NonnullRefPtr<Expression> key, NonnullRefPtr<FunctionExpression> function, Kind kind, bool is_static)
|
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_key(move(key))
|
||||||
, m_function(move(function))
|
, m_function(move(function))
|
||||||
, m_kind(kind)
|
, m_kind(kind)
|
||||||
, m_is_static(is_static)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
Expression const& key() const { return *m_key; }
|
Expression const& key() const { return *m_key; }
|
||||||
Kind kind() const { return m_kind; }
|
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 void dump(int indent) const override;
|
||||||
|
virtual ThrowCompletionOr<ClassValue> class_element_evaluation(Interpreter&, GlobalObject&, Object& home_object) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NonnullRefPtr<Expression> m_key;
|
NonnullRefPtr<Expression> m_key;
|
||||||
NonnullRefPtr<FunctionExpression> m_function;
|
NonnullRefPtr<FunctionExpression> m_function;
|
||||||
Kind m_kind;
|
Kind m_kind;
|
||||||
bool m_is_static;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class ClassField final : public ASTNode {
|
class ClassField final : public ClassElement {
|
||||||
public:
|
public:
|
||||||
ClassField(SourceRange source_range, NonnullRefPtr<Expression> key, RefPtr<Expression> init, bool is_static)
|
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_key(move(key))
|
||||||
, m_initializer(move(init))
|
, m_initializer(move(init))
|
||||||
, m_is_static(is_static)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
Expression const& key() const { return *m_key; }
|
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> const& initializer() const { return m_initializer; }
|
||||||
RefPtr<Expression>& initializer() { 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 void dump(int indent) const override;
|
||||||
|
virtual ThrowCompletionOr<ClassValue> class_element_evaluation(Interpreter& interpreter, GlobalObject& object, Object& home_object) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NonnullRefPtr<Expression> m_key;
|
NonnullRefPtr<Expression> m_key;
|
||||||
RefPtr<Expression> m_initializer;
|
RefPtr<Expression> m_initializer;
|
||||||
bool m_is_static;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class SuperExpression final : public Expression {
|
class SuperExpression final : public Expression {
|
||||||
|
@ -1070,13 +1097,12 @@ 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, NonnullRefPtrVector<ClassField> fields)
|
ClassExpression(SourceRange source_range, String name, RefPtr<FunctionExpression> constructor, RefPtr<Expression> super_class, NonnullRefPtrVector<ClassElement> elements)
|
||||||
: 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_elements(move(elements))
|
||||||
, m_fields(move(fields))
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1096,8 +1122,7 @@ private:
|
||||||
String m_name;
|
String m_name;
|
||||||
RefPtr<FunctionExpression> m_constructor;
|
RefPtr<FunctionExpression> m_constructor;
|
||||||
RefPtr<Expression> m_super_class;
|
RefPtr<Expression> m_super_class;
|
||||||
NonnullRefPtrVector<ClassMethod> m_methods;
|
NonnullRefPtrVector<ClassElement> m_elements;
|
||||||
NonnullRefPtrVector<ClassField> m_fields;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class ClassDeclaration final : public Declaration {
|
class ClassDeclaration final : public Declaration {
|
||||||
|
|
|
@ -842,8 +842,7 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
|
||||||
|
|
||||||
consume(TokenType::Class);
|
consume(TokenType::Class);
|
||||||
|
|
||||||
NonnullRefPtrVector<ClassMethod> methods;
|
NonnullRefPtrVector<ClassElement> elements;
|
||||||
NonnullRefPtrVector<ClassField> fields;
|
|
||||||
RefPtr<Expression> super_class;
|
RefPtr<Expression> super_class;
|
||||||
RefPtr<FunctionExpression> constructor;
|
RefPtr<FunctionExpression> constructor;
|
||||||
|
|
||||||
|
@ -989,7 +988,7 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
|
||||||
if (is_constructor) {
|
if (is_constructor) {
|
||||||
constructor = move(function);
|
constructor = move(function);
|
||||||
} else if (!property_key.is_null()) {
|
} 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 {
|
} else {
|
||||||
syntax_error("No key for class method");
|
syntax_error("No key for class method");
|
||||||
}
|
}
|
||||||
|
@ -1013,8 +1012,7 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
|
||||||
initializer = parse_expression(2);
|
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();
|
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()
|
Parser::PrimaryExpressionParseResult Parser::parse_primary_expression()
|
||||||
|
|
|
@ -275,7 +275,9 @@ void ECMAScriptFunctionObject::visit_edges(Visitor& visitor)
|
||||||
visitor.visit(m_home_object);
|
visitor.visit(m_home_object);
|
||||||
|
|
||||||
for (auto& field : m_fields) {
|
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);
|
visitor.visit(field.initializer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,14 +59,14 @@ public:
|
||||||
void set_home_object(Object* home_object) { m_home_object = home_object; }
|
void set_home_object(Object* home_object) { m_home_object = home_object; }
|
||||||
|
|
||||||
struct InstanceField {
|
struct InstanceField {
|
||||||
StringOrSymbol name;
|
PropertyName name;
|
||||||
ECMAScriptFunctionObject* initializer { nullptr };
|
ECMAScriptFunctionObject* initializer { nullptr };
|
||||||
|
|
||||||
void define_field(VM& vm, Object& receiver) const;
|
void define_field(VM& vm, Object& receiver) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
Vector<InstanceField> const& fields() const { return m_fields; }
|
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)
|
// 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; }
|
||||||
|
|
|
@ -170,3 +170,32 @@ test("issue #7045, super constructor call from child class in catch {}", () => {
|
||||||
const c = new Child();
|
const c = new Child();
|
||||||
expect(c.x).toBe("Error in Child constructor");
|
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();
|
||||||
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue