mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-10 13:00:29 +03:00
LibJS: Initial class implementation; allow super expressions in object
literal methods; add EnvrionmentRecord fields and methods to LexicalEnvironment Adding EnvrionmentRecord's fields and methods lets us throw an exception when |this| is not initialized, which occurs when the super constructor in a derived class has not yet been called, or when |this| has already been initialized (the super constructor was already called).
This commit is contained in:
parent
a535d58cac
commit
7533fd8b02
Notes:
sideshowbarker
2024-07-19 05:19:30 +09:00
Author: https://github.com/jack-karamanian Commit: https://github.com/SerenityOS/serenity/commit/7533fd8b025 Pull-request: https://github.com/SerenityOS/serenity/pull/2533 Reviewed-by: https://github.com/linusg
@ -92,15 +92,28 @@ CallExpression::ThisAndCallee CallExpression::compute_this_and_callee(Interprete
|
||||
return { js_undefined(), m_callee->execute(interpreter, global_object) };
|
||||
}
|
||||
|
||||
if (m_callee->is_super_expression()) {
|
||||
// If we are calling super, |this| has not been initalized yet, and would not be meaningful to provide.
|
||||
auto new_target = interpreter.get_new_target();
|
||||
ASSERT(new_target.is_function());
|
||||
return { js_undefined(), new_target };
|
||||
}
|
||||
|
||||
if (m_callee->is_member_expression()) {
|
||||
auto& member_expression = static_cast<const MemberExpression&>(*m_callee);
|
||||
auto object_value = member_expression.object().execute(interpreter, global_object);
|
||||
bool is_super_property_lookup = member_expression.object().is_super_expression();
|
||||
auto lookup_target = is_super_property_lookup ? interpreter.current_environment()->get_super_base() : member_expression.object().execute(interpreter, global_object);
|
||||
if (interpreter.exception())
|
||||
return {};
|
||||
auto* this_value = object_value.to_object(interpreter, global_object);
|
||||
if (is_super_property_lookup && (lookup_target.is_null() || lookup_target.is_undefined())) {
|
||||
interpreter.throw_exception<TypeError>(ErrorType::ObjectPrototypeNullOrUndefinedOnSuperPropertyAccess, lookup_target.to_string_without_side_effects());
|
||||
return {};
|
||||
}
|
||||
|
||||
auto* this_value = is_super_property_lookup ? &interpreter.this_value(global_object).as_object() : lookup_target.to_object(interpreter, global_object);
|
||||
if (interpreter.exception())
|
||||
return {};
|
||||
auto callee = this_value->get(member_expression.computed_property_name(interpreter, global_object)).value_or(js_undefined());
|
||||
auto callee = lookup_target.to_object(interpreter, global_object)->get(member_expression.computed_property_name(interpreter, global_object)).value_or(js_undefined());
|
||||
return { this_value, callee };
|
||||
}
|
||||
return { &global_object, m_callee->execute(interpreter, global_object) };
|
||||
@ -134,7 +147,6 @@ Value CallExpression::execute(Interpreter& interpreter, GlobalObject& global_obj
|
||||
auto& function = callee.as_function();
|
||||
|
||||
MarkedValueList arguments(interpreter.heap());
|
||||
arguments.values().append(function.bound_arguments());
|
||||
|
||||
for (size_t i = 0; i < m_arguments.size(); ++i) {
|
||||
auto value = m_arguments[i].value->execute(interpreter, global_object);
|
||||
@ -163,31 +175,26 @@ Value CallExpression::execute(Interpreter& interpreter, GlobalObject& global_obj
|
||||
}
|
||||
}
|
||||
|
||||
auto& call_frame = interpreter.push_call_frame();
|
||||
call_frame.function_name = function.name();
|
||||
call_frame.arguments = arguments.values();
|
||||
call_frame.environment = function.create_environment();
|
||||
|
||||
Object* new_object = nullptr;
|
||||
Value result;
|
||||
if (is_new_expression()) {
|
||||
new_object = Object::create_empty(interpreter, global_object);
|
||||
auto prototype = function.get("prototype");
|
||||
result = interpreter.construct(function, function, move(arguments), global_object);
|
||||
if (result.is_object())
|
||||
new_object = &result.as_object();
|
||||
} else if (m_callee->is_super_expression()) {
|
||||
auto* super_constructor = interpreter.current_environment()->current_function()->prototype();
|
||||
// FIXME: Functions should track their constructor kind.
|
||||
if (!super_constructor || !super_constructor->is_function())
|
||||
return interpreter.throw_exception<TypeError>(ErrorType::NotAConstructor, "Super constructor");
|
||||
|
||||
result = interpreter.construct(static_cast<Function&>(*super_constructor), function, move(arguments), global_object);
|
||||
if (interpreter.exception())
|
||||
return {};
|
||||
if (prototype.is_object()) {
|
||||
new_object->set_prototype(&prototype.as_object());
|
||||
if (interpreter.exception())
|
||||
return {};
|
||||
}
|
||||
call_frame.this_value = new_object;
|
||||
result = function.construct(interpreter);
|
||||
} else {
|
||||
call_frame.this_value = function.bound_this().value_or(this_value);
|
||||
result = function.call(interpreter);
|
||||
}
|
||||
|
||||
interpreter.pop_call_frame();
|
||||
interpreter.current_environment()->bind_this_value(result);
|
||||
} else {
|
||||
result = interpreter.call(function, this_value, move(arguments));
|
||||
}
|
||||
|
||||
if (interpreter.exception())
|
||||
return {};
|
||||
@ -658,6 +665,112 @@ Value UnaryExpression::execute(Interpreter& interpreter, GlobalObject& global_ob
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
Value SuperExpression::execute(Interpreter&, GlobalObject&) const
|
||||
{
|
||||
// The semantics for SuperExpressions are handled in CallExpression::compute_this_and_callee()
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
Value ClassMethod::execute(Interpreter& interpreter, GlobalObject& global_object) const
|
||||
{
|
||||
return m_function->execute(interpreter, global_object);
|
||||
}
|
||||
|
||||
Value ClassExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const
|
||||
{
|
||||
Value class_constructor_value = m_constructor->execute(interpreter, global_object);
|
||||
if (interpreter.exception())
|
||||
return {};
|
||||
|
||||
update_function_name(class_constructor_value, m_name);
|
||||
|
||||
ASSERT(class_constructor_value.is_function() && class_constructor_value.as_function().is_script_function());
|
||||
ScriptFunction* class_constructor = static_cast<ScriptFunction*>(&class_constructor_value.as_function());
|
||||
Value super_constructor = js_undefined();
|
||||
if (!m_super_class.is_null()) {
|
||||
super_constructor = m_super_class->execute(interpreter, global_object);
|
||||
if (interpreter.exception())
|
||||
return {};
|
||||
if (!super_constructor.is_function() && !super_constructor.is_null())
|
||||
return interpreter.throw_exception<TypeError>(ErrorType::ClassDoesNotExtendAConstructorOrNull, super_constructor.to_string_without_side_effects().characters());
|
||||
|
||||
class_constructor->set_constructor_kind(Function::ConstructorKind::Derived);
|
||||
Object* prototype = Object::create_empty(interpreter, interpreter.global_object());
|
||||
|
||||
Object* super_constructor_prototype = nullptr;
|
||||
if (!super_constructor.is_null()) {
|
||||
super_constructor_prototype = &super_constructor.as_object().get("prototype").as_object();
|
||||
if (interpreter.exception())
|
||||
return {};
|
||||
}
|
||||
prototype->set_prototype(super_constructor_prototype);
|
||||
|
||||
prototype->define_property("constructor", class_constructor, 0);
|
||||
if (interpreter.exception())
|
||||
return {};
|
||||
class_constructor->define_property("prototype", prototype, 0);
|
||||
if (interpreter.exception())
|
||||
return {};
|
||||
class_constructor->set_prototype(super_constructor.is_null() ? global_object.function_prototype() : &super_constructor.as_object());
|
||||
}
|
||||
|
||||
auto class_prototype = class_constructor->get("prototype");
|
||||
if (interpreter.exception())
|
||||
return {};
|
||||
|
||||
if (!class_prototype.is_object())
|
||||
return interpreter.throw_exception<TypeError>(ErrorType::NotAnObject, "Class prototype");
|
||||
|
||||
for (const auto& method : m_methods) {
|
||||
auto method_value = method.execute(interpreter, global_object);
|
||||
if (interpreter.exception())
|
||||
return {};
|
||||
|
||||
auto& method_function = method_value.as_function();
|
||||
|
||||
auto key = method.key().execute(interpreter, global_object);
|
||||
if (interpreter.exception())
|
||||
return {};
|
||||
|
||||
auto& target = method.is_static() ? *class_constructor : class_prototype.as_object();
|
||||
method_function.set_home_object(&target);
|
||||
|
||||
auto property_name = key.to_string(interpreter);
|
||||
|
||||
if (method.kind() == ClassMethod::Kind::Method) {
|
||||
target.define_property(property_name, method_value);
|
||||
} else {
|
||||
String accessor_name = [&] {
|
||||
switch (method.kind()) {
|
||||
case ClassMethod::Kind::Getter:
|
||||
return String::format("get %s", property_name.characters());
|
||||
case ClassMethod::Kind::Setter:
|
||||
return String::format("set %s", property_name.characters());
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
}();
|
||||
update_function_name(method_value, accessor_name);
|
||||
target.define_accessor(property_name, method_function, method.kind() == ClassMethod::Kind::Getter, Attribute::Configurable | Attribute::Enumerable);
|
||||
}
|
||||
if (interpreter.exception())
|
||||
return {};
|
||||
}
|
||||
|
||||
return class_constructor;
|
||||
}
|
||||
|
||||
Value ClassDeclaration::execute(Interpreter& interpreter, GlobalObject& global_object) const
|
||||
{
|
||||
Value class_constructor = m_class_expression->execute(interpreter, global_object);
|
||||
if (interpreter.exception())
|
||||
return {};
|
||||
|
||||
interpreter.current_environment()->set(m_class_expression->name(), { class_constructor, DeclarationKind::Let });
|
||||
|
||||
return js_undefined();
|
||||
}
|
||||
|
||||
static void print_indent(int indent)
|
||||
{
|
||||
for (int i = 0; i < indent * 2; ++i)
|
||||
@ -833,12 +946,76 @@ void CallExpression::dump(int indent) const
|
||||
argument.value->dump(indent + 1);
|
||||
}
|
||||
|
||||
void ClassDeclaration::dump(int indent) const
|
||||
{
|
||||
ASTNode::dump(indent);
|
||||
m_class_expression->dump(indent + 1);
|
||||
}
|
||||
|
||||
void ClassExpression::dump(int indent) const
|
||||
{
|
||||
print_indent(indent);
|
||||
printf("ClassExpression: \"%s\"\n", m_name.characters());
|
||||
|
||||
print_indent(indent);
|
||||
printf("(Constructor)\n");
|
||||
m_constructor->dump(indent + 1);
|
||||
|
||||
if (!m_super_class.is_null()) {
|
||||
print_indent(indent);
|
||||
printf("(Super Class)\n");
|
||||
m_super_class->dump(indent + 1);
|
||||
}
|
||||
|
||||
print_indent(indent);
|
||||
printf("(Methods)\n");
|
||||
for (auto& method : m_methods)
|
||||
method.dump(indent + 1);
|
||||
}
|
||||
|
||||
void ClassMethod::dump(int indent) const
|
||||
{
|
||||
ASTNode::dump(indent);
|
||||
|
||||
print_indent(indent);
|
||||
printf("(Key)\n");
|
||||
m_key->dump(indent + 1);
|
||||
|
||||
const char* kind_string = nullptr;
|
||||
switch (m_kind) {
|
||||
case Kind::Method:
|
||||
kind_string = "Method";
|
||||
break;
|
||||
case Kind::Getter:
|
||||
kind_string = "Getter";
|
||||
break;
|
||||
case Kind::Setter:
|
||||
kind_string = "Setter";
|
||||
break;
|
||||
}
|
||||
print_indent(indent);
|
||||
printf("Kind: %s\n", kind_string);
|
||||
|
||||
print_indent(indent);
|
||||
printf("Static: %s\n", m_is_static ? "true" : "false");
|
||||
|
||||
print_indent(indent);
|
||||
printf("(Function)\n");
|
||||
m_function->dump(indent + 1);
|
||||
}
|
||||
|
||||
void StringLiteral::dump(int indent) const
|
||||
{
|
||||
print_indent(indent);
|
||||
printf("StringLiteral \"%s\"\n", m_value.characters());
|
||||
}
|
||||
|
||||
void SuperExpression::dump(int indent) const
|
||||
{
|
||||
print_indent(indent);
|
||||
printf("super\n");
|
||||
}
|
||||
|
||||
void NumericLiteral::dump(int indent) const
|
||||
{
|
||||
print_indent(indent);
|
||||
@ -1006,9 +1183,9 @@ Value SpreadExpression::execute(Interpreter& interpreter, GlobalObject& global_o
|
||||
return m_target->execute(interpreter, global_object);
|
||||
}
|
||||
|
||||
Value ThisExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const
|
||||
Value ThisExpression::execute(Interpreter& interpreter, GlobalObject&) const
|
||||
{
|
||||
return interpreter.this_value(global_object);
|
||||
return interpreter.resolve_this_binding();
|
||||
}
|
||||
|
||||
void ThisExpression::dump(int indent) const
|
||||
@ -1353,6 +1530,9 @@ Value ObjectExpression::execute(Interpreter& interpreter, GlobalObject& global_o
|
||||
if (interpreter.exception())
|
||||
return {};
|
||||
|
||||
if (value.is_function() && property.is_method())
|
||||
value.as_function().set_home_object(object);
|
||||
|
||||
String name = key;
|
||||
if (property.type() == ObjectProperty::Type::Getter) {
|
||||
name = String::format("get %s", key.characters());
|
||||
|
@ -63,6 +63,7 @@ public:
|
||||
virtual bool is_variable_declaration() const { return false; }
|
||||
virtual bool is_call_expression() const { return false; }
|
||||
virtual bool is_new_expression() const { return false; }
|
||||
virtual bool is_super_expression() const { return false; }
|
||||
|
||||
protected:
|
||||
ASTNode() { }
|
||||
@ -581,6 +582,8 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
StringView value() const { return m_value; }
|
||||
|
||||
virtual Value execute(Interpreter&, GlobalObject&) const override;
|
||||
virtual void dump(int indent) const override;
|
||||
|
||||
@ -642,6 +645,87 @@ private:
|
||||
FlyString m_string;
|
||||
};
|
||||
|
||||
class ClassMethod final : public ASTNode {
|
||||
public:
|
||||
enum class Kind {
|
||||
Method,
|
||||
Getter,
|
||||
Setter,
|
||||
};
|
||||
|
||||
ClassMethod(NonnullRefPtr<Expression> key, NonnullRefPtr<FunctionExpression> function, Kind kind, bool is_static)
|
||||
: m_key(move(key))
|
||||
, m_function(move(function))
|
||||
, m_kind(kind)
|
||||
, m_is_static(is_static)
|
||||
{
|
||||
}
|
||||
|
||||
const Expression& key() const { return *m_key; }
|
||||
Kind kind() const { return m_kind; }
|
||||
bool is_static() const { return m_is_static; }
|
||||
|
||||
virtual Value execute(Interpreter&, GlobalObject&) const override;
|
||||
virtual void dump(int indent) const override;
|
||||
|
||||
private:
|
||||
virtual const char* class_name() const override { return "ClassMethod"; }
|
||||
|
||||
NonnullRefPtr<Expression> m_key;
|
||||
NonnullRefPtr<FunctionExpression> m_function;
|
||||
Kind m_kind;
|
||||
bool m_is_static;
|
||||
};
|
||||
|
||||
class SuperExpression final : public Expression {
|
||||
public:
|
||||
virtual Value execute(Interpreter&, GlobalObject&) const override;
|
||||
virtual void dump(int indent) const override;
|
||||
|
||||
private:
|
||||
virtual bool is_super_expression() const override { return true; }
|
||||
virtual const char* class_name() const override { return "SuperExpression"; }
|
||||
};
|
||||
|
||||
class ClassExpression final : public Expression {
|
||||
public:
|
||||
ClassExpression(String name, RefPtr<FunctionExpression> constructor, RefPtr<Expression> super_class, NonnullRefPtrVector<ClassMethod> methods)
|
||||
: m_name(move(name))
|
||||
, m_constructor(move(constructor))
|
||||
, m_super_class(move(super_class))
|
||||
, m_methods(move(methods))
|
||||
{
|
||||
}
|
||||
|
||||
StringView name() const { return m_name; }
|
||||
|
||||
virtual Value execute(Interpreter&, GlobalObject&) const override;
|
||||
virtual void dump(int indent) const override;
|
||||
|
||||
private:
|
||||
virtual const char* class_name() const override { return "ClassExpression"; }
|
||||
|
||||
String m_name;
|
||||
RefPtr<FunctionExpression> m_constructor;
|
||||
RefPtr<Expression> m_super_class;
|
||||
NonnullRefPtrVector<ClassMethod> m_methods;
|
||||
};
|
||||
|
||||
class ClassDeclaration final : public Declaration {
|
||||
public:
|
||||
ClassDeclaration(NonnullRefPtr<ClassExpression> class_expression)
|
||||
: m_class_expression(move(class_expression))
|
||||
{
|
||||
}
|
||||
|
||||
virtual Value execute(Interpreter&, GlobalObject&) const override;
|
||||
virtual void dump(int indent) const override;
|
||||
|
||||
private:
|
||||
virtual const char* class_name() const override { return "ClassDeclaration"; }
|
||||
NonnullRefPtr<ClassExpression> m_class_expression;
|
||||
};
|
||||
|
||||
class SpreadExpression final : public Expression {
|
||||
public:
|
||||
explicit SpreadExpression(NonnullRefPtr<Expression> target)
|
||||
@ -836,10 +920,11 @@ public:
|
||||
Spread,
|
||||
};
|
||||
|
||||
ObjectProperty(NonnullRefPtr<Expression> key, RefPtr<Expression> value, Type property_type)
|
||||
ObjectProperty(NonnullRefPtr<Expression> key, RefPtr<Expression> value, Type property_type, bool is_method)
|
||||
: m_key(move(key))
|
||||
, m_value(move(value))
|
||||
, m_property_type(property_type)
|
||||
, m_is_method(is_method)
|
||||
{
|
||||
}
|
||||
|
||||
@ -851,6 +936,7 @@ public:
|
||||
}
|
||||
|
||||
Type type() const { return m_property_type; }
|
||||
bool is_method() const { return m_is_method; }
|
||||
|
||||
virtual void dump(int indent) const override;
|
||||
virtual Value execute(Interpreter&, GlobalObject&) const override;
|
||||
@ -861,6 +947,7 @@ private:
|
||||
NonnullRefPtr<Expression> m_key;
|
||||
RefPtr<Expression> m_value;
|
||||
Type m_property_type;
|
||||
bool m_is_method { false };
|
||||
};
|
||||
|
||||
class ObjectExpression : public Expression {
|
||||
|
@ -59,7 +59,10 @@ Value Interpreter::run(GlobalObject& global_object, const Statement& statement,
|
||||
CallFrame global_call_frame;
|
||||
global_call_frame.this_value = &global_object;
|
||||
global_call_frame.function_name = "(global execution context)";
|
||||
global_call_frame.environment = heap().allocate<LexicalEnvironment>(global_object);
|
||||
global_call_frame.environment = heap().allocate<LexicalEnvironment>(global_object, LexicalEnvironment::EnvironmentRecordType::Global);
|
||||
global_call_frame.environment->bind_this_value(&global_object);
|
||||
if (exception())
|
||||
return {};
|
||||
m_call_stack.append(move(global_call_frame));
|
||||
}
|
||||
}
|
||||
@ -226,6 +229,10 @@ Value Interpreter::call(Function& function, Value this_value, Optional<MarkedVal
|
||||
if (arguments.has_value())
|
||||
call_frame.arguments.append(arguments.value().values());
|
||||
call_frame.environment = function.create_environment();
|
||||
|
||||
ASSERT(call_frame.environment->this_binding_status() == LexicalEnvironment::ThisBindingStatus::Uninitialized);
|
||||
call_frame.environment->bind_this_value(call_frame.this_value);
|
||||
|
||||
auto result = function.call(*this);
|
||||
pop_call_frame();
|
||||
return result;
|
||||
@ -235,29 +242,59 @@ Value Interpreter::construct(Function& function, Function& new_target, Optional<
|
||||
{
|
||||
auto& call_frame = push_call_frame();
|
||||
call_frame.function_name = function.name();
|
||||
call_frame.arguments = function.bound_arguments();
|
||||
if (arguments.has_value())
|
||||
call_frame.arguments = arguments.value().values();
|
||||
call_frame.arguments.append(arguments.value().values());
|
||||
call_frame.environment = function.create_environment();
|
||||
|
||||
auto* new_object = Object::create_empty(*this, global_object);
|
||||
auto prototype = new_target.get("prototype");
|
||||
if (exception())
|
||||
return {};
|
||||
if (prototype.is_object()) {
|
||||
new_object->set_prototype(&prototype.as_object());
|
||||
current_environment()->set_new_target(&new_target);
|
||||
|
||||
Object* new_object = nullptr;
|
||||
if (function.constructor_kind() == Function::ConstructorKind::Base) {
|
||||
new_object = Object::create_empty(*this, global_object);
|
||||
current_environment()->bind_this_value(new_object);
|
||||
if (exception())
|
||||
return {};
|
||||
auto prototype = new_target.get("prototype");
|
||||
if (exception())
|
||||
return {};
|
||||
if (prototype.is_object()) {
|
||||
new_object->set_prototype(&prototype.as_object());
|
||||
if (exception())
|
||||
return {};
|
||||
}
|
||||
}
|
||||
call_frame.this_value = new_object;
|
||||
|
||||
// If we are a Derived constructor, |this| has not been constructed before super is called.
|
||||
Value this_value = function.constructor_kind() == Function::ConstructorKind::Base ? new_object : Value {};
|
||||
call_frame.this_value = this_value;
|
||||
auto result = function.construct(*this);
|
||||
|
||||
this_value = current_environment()->get_this_binding();
|
||||
pop_call_frame();
|
||||
|
||||
// If we are constructing an instance of a derived class,
|
||||
// set the prototype on objects created by constructors that return an object (i.e. NativeFunction subclasses).
|
||||
if (function.constructor_kind() == Function::ConstructorKind::Base && new_target.constructor_kind() == Function::ConstructorKind::Derived && result.is_object()) {
|
||||
current_environment()->replace_this_binding(result);
|
||||
auto prototype = new_target.get("prototype");
|
||||
if (exception())
|
||||
return {};
|
||||
if (prototype.is_object()) {
|
||||
result.as_object().set_prototype(&prototype.as_object());
|
||||
if (exception())
|
||||
return {};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if (exception())
|
||||
return {};
|
||||
|
||||
if (result.is_object())
|
||||
return result;
|
||||
return new_object;
|
||||
|
||||
return this_value;
|
||||
}
|
||||
|
||||
Value Interpreter::throw_exception(Exception* exception)
|
||||
@ -301,4 +338,24 @@ String Interpreter::join_arguments() const
|
||||
return joined_arguments.build();
|
||||
}
|
||||
|
||||
Value Interpreter::resolve_this_binding() const
|
||||
{
|
||||
return get_this_environment()->get_this_binding();
|
||||
}
|
||||
|
||||
const LexicalEnvironment* Interpreter::get_this_environment() const
|
||||
{
|
||||
// We will always return because the Global environment will always be reached, which has a |this| binding.
|
||||
for (const LexicalEnvironment* environment = current_environment(); environment; environment = environment->parent()) {
|
||||
if (environment->has_this_binding())
|
||||
return environment;
|
||||
}
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
Value Interpreter::get_new_target() const
|
||||
{
|
||||
return get_this_environment()->new_target();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -192,6 +192,10 @@ public:
|
||||
|
||||
String join_arguments() const;
|
||||
|
||||
Value resolve_this_binding() const;
|
||||
const LexicalEnvironment* get_this_environment() const;
|
||||
Value get_new_target() const;
|
||||
|
||||
private:
|
||||
Interpreter();
|
||||
|
||||
|
@ -244,6 +244,8 @@ NonnullRefPtr<Statement> Parser::parse_statement()
|
||||
{
|
||||
auto statement = [this]() -> NonnullRefPtr<Statement> {
|
||||
switch (m_parser_state.m_current_token.type()) {
|
||||
case TokenType::Class:
|
||||
return parse_class_declaration();
|
||||
case TokenType::Function: {
|
||||
auto declaration = parse_function_node<FunctionDeclaration>();
|
||||
m_parser_state.m_function_scopes.last().append(declaration);
|
||||
@ -421,6 +423,136 @@ RefPtr<Statement> Parser::try_parse_labelled_statement()
|
||||
return statement;
|
||||
}
|
||||
|
||||
NonnullRefPtr<ClassDeclaration> Parser::parse_class_declaration()
|
||||
{
|
||||
return create_ast_node<ClassDeclaration>(parse_class_expression(true));
|
||||
}
|
||||
|
||||
NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_name)
|
||||
{
|
||||
// Classes are always in strict mode.
|
||||
TemporaryChange strict_mode_rollback(m_parser_state.m_strict_mode, true);
|
||||
|
||||
consume(TokenType::Class);
|
||||
|
||||
NonnullRefPtrVector<ClassMethod> methods;
|
||||
RefPtr<Expression> super_class;
|
||||
RefPtr<FunctionExpression> constructor;
|
||||
|
||||
String class_name = expect_class_name || match(TokenType::Identifier) ? consume(TokenType::Identifier).value().to_string() : "";
|
||||
|
||||
if (match(TokenType::Extends)) {
|
||||
consume();
|
||||
super_class = parse_primary_expression();
|
||||
}
|
||||
|
||||
consume(TokenType::CurlyOpen);
|
||||
|
||||
while (!done() && !match(TokenType::CurlyClose)) {
|
||||
RefPtr<Expression> property_key;
|
||||
bool is_static = false;
|
||||
bool is_constructor = false;
|
||||
auto method_kind = ClassMethod::Kind::Method;
|
||||
|
||||
if (match(TokenType::Semicolon)) {
|
||||
consume();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (match_property_key()) {
|
||||
StringView name;
|
||||
if (match(TokenType::Identifier) && m_parser_state.m_current_token.value() == "static") {
|
||||
consume();
|
||||
is_static = true;
|
||||
}
|
||||
|
||||
if (match(TokenType::Identifier)) {
|
||||
auto identifier_name = m_parser_state.m_current_token.value();
|
||||
|
||||
if (identifier_name == "get") {
|
||||
method_kind = ClassMethod::Kind::Getter;
|
||||
consume();
|
||||
} else if (identifier_name == "set") {
|
||||
method_kind = ClassMethod::Kind::Setter;
|
||||
consume();
|
||||
}
|
||||
}
|
||||
|
||||
if (match_property_key()) {
|
||||
switch (m_parser_state.m_current_token.type()) {
|
||||
case TokenType::Identifier:
|
||||
name = consume().value();
|
||||
property_key = create_ast_node<StringLiteral>(name);
|
||||
break;
|
||||
case TokenType::StringLiteral: {
|
||||
auto string_literal = parse_string_literal(consume());
|
||||
name = string_literal->value();
|
||||
property_key = move(string_literal);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
property_key = parse_property_key();
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
expected("property key");
|
||||
}
|
||||
|
||||
// Constructor may be a StringLiteral or an Identifier.
|
||||
if (!is_static && name == "constructor") {
|
||||
if (method_kind != ClassMethod::Kind::Method)
|
||||
syntax_error("Class constructor may not be an accessor");
|
||||
if (!constructor.is_null())
|
||||
syntax_error("Classes may not have more than one constructor");
|
||||
|
||||
is_constructor = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (match(TokenType::ParenOpen)) {
|
||||
auto function = parse_function_node<FunctionExpression>(false, true, !super_class.is_null());
|
||||
auto arg_count = function->parameters().size();
|
||||
|
||||
if (method_kind == ClassMethod::Kind::Getter && arg_count != 0) {
|
||||
syntax_error("Class getter method must have no arguments");
|
||||
} else if (method_kind == ClassMethod::Kind::Setter && arg_count != 1) {
|
||||
syntax_error("Class setter method must have one argument");
|
||||
}
|
||||
|
||||
if (is_constructor) {
|
||||
constructor = move(function);
|
||||
} else if (!property_key.is_null()) {
|
||||
methods.append(create_ast_node<ClassMethod>(property_key.release_nonnull(), move(function), method_kind, is_static));
|
||||
} else {
|
||||
syntax_error("No key for class method");
|
||||
}
|
||||
} else {
|
||||
expected("ParenOpen");
|
||||
consume();
|
||||
}
|
||||
}
|
||||
|
||||
consume(TokenType::CurlyClose);
|
||||
|
||||
if (constructor.is_null()) {
|
||||
auto constructor_body = create_ast_node<BlockStatement>();
|
||||
if (!super_class.is_null()) {
|
||||
// Set constructor to the result of parsing the source text
|
||||
// constructor(... args){ super (...args);}
|
||||
auto super_call = create_ast_node<CallExpression>(create_ast_node<SuperExpression>(), Vector { CallExpression::Argument { create_ast_node<Identifier>("args"), true } });
|
||||
constructor_body->append(create_ast_node<ExpressionStatement>(move(super_call)));
|
||||
constructor_body->add_variables(m_parser_state.m_var_scopes.last());
|
||||
|
||||
constructor = create_ast_node<FunctionExpression>(class_name, move(constructor_body), Vector { FunctionNode::Parameter { "args", nullptr, true } }, 0, NonnullRefPtrVector<VariableDeclaration>());
|
||||
} else {
|
||||
constructor = create_ast_node<FunctionExpression>(class_name, move(constructor_body), Vector<FunctionNode::Parameter> {}, 0, NonnullRefPtrVector<VariableDeclaration>());
|
||||
}
|
||||
}
|
||||
|
||||
return create_ast_node<ClassExpression>(move(class_name), move(constructor), move(super_class), move(methods));
|
||||
}
|
||||
|
||||
NonnullRefPtr<Expression> Parser::parse_primary_expression()
|
||||
{
|
||||
if (match_unary_prefixed_expression())
|
||||
@ -442,6 +574,13 @@ NonnullRefPtr<Expression> Parser::parse_primary_expression()
|
||||
case TokenType::This:
|
||||
consume();
|
||||
return create_ast_node<ThisExpression>();
|
||||
case TokenType::Class:
|
||||
return parse_class_expression(false);
|
||||
case TokenType::Super:
|
||||
consume();
|
||||
if (!m_parser_state.m_allow_super_property_lookup)
|
||||
syntax_error("'super' keyword unexpected here");
|
||||
return create_ast_node<SuperExpression>();
|
||||
case TokenType::Identifier: {
|
||||
auto arrow_function_result = try_parse_arrow_function_expression(false);
|
||||
if (!arrow_function_result.is_null()) {
|
||||
@ -537,6 +676,27 @@ NonnullRefPtr<Expression> Parser::parse_unary_prefixed_expression()
|
||||
}
|
||||
}
|
||||
|
||||
NonnullRefPtr<Expression> Parser::parse_property_key()
|
||||
{
|
||||
if (match(TokenType::StringLiteral)) {
|
||||
return parse_string_literal(consume());
|
||||
} else if (match(TokenType::NumericLiteral)) {
|
||||
return create_ast_node<StringLiteral>(consume(TokenType::NumericLiteral).value());
|
||||
} else if (match(TokenType::BigIntLiteral)) {
|
||||
auto value = consume(TokenType::BigIntLiteral).value();
|
||||
return create_ast_node<StringLiteral>(value.substring_view(0, value.length() - 1));
|
||||
} else if (match(TokenType::BracketOpen)) {
|
||||
consume(TokenType::BracketOpen);
|
||||
auto result = parse_expression(0);
|
||||
consume(TokenType::BracketClose);
|
||||
return result;
|
||||
} else {
|
||||
if (!match_identifier_name())
|
||||
expected("IdentifierName");
|
||||
return create_ast_node<StringLiteral>(consume().value());
|
||||
}
|
||||
}
|
||||
|
||||
NonnullRefPtr<ObjectExpression> Parser::parse_object_expression()
|
||||
{
|
||||
consume(TokenType::CurlyOpen);
|
||||
@ -544,35 +704,6 @@ NonnullRefPtr<ObjectExpression> Parser::parse_object_expression()
|
||||
NonnullRefPtrVector<ObjectProperty> properties;
|
||||
ObjectProperty::Type property_type;
|
||||
|
||||
auto match_property_key = [&]() -> bool {
|
||||
auto type = m_parser_state.m_current_token.type();
|
||||
return match_identifier_name()
|
||||
|| type == TokenType::BracketOpen
|
||||
|| type == TokenType::StringLiteral
|
||||
|| type == TokenType::NumericLiteral
|
||||
|| type == TokenType::BigIntLiteral;
|
||||
};
|
||||
|
||||
auto parse_property_key = [&]() -> NonnullRefPtr<Expression> {
|
||||
if (match(TokenType::StringLiteral)) {
|
||||
return parse_string_literal(consume());
|
||||
} else if (match(TokenType::NumericLiteral)) {
|
||||
return create_ast_node<StringLiteral>(consume(TokenType::NumericLiteral).value());
|
||||
} else if (match(TokenType::BigIntLiteral)) {
|
||||
auto value = consume(TokenType::BigIntLiteral).value();
|
||||
return create_ast_node<StringLiteral>(value.substring_view(0, value.length() - 1));
|
||||
} else if (match(TokenType::BracketOpen)) {
|
||||
consume(TokenType::BracketOpen);
|
||||
auto result = parse_expression(0);
|
||||
consume(TokenType::BracketClose);
|
||||
return result;
|
||||
} else {
|
||||
if (!match_identifier_name())
|
||||
expected("IdentifierName");
|
||||
return create_ast_node<StringLiteral>(consume().value());
|
||||
}
|
||||
};
|
||||
|
||||
auto skip_to_next_property = [&] {
|
||||
while (!done() && !match(TokenType::Comma) && !match(TokenType::CurlyOpen))
|
||||
consume();
|
||||
@ -586,7 +717,7 @@ NonnullRefPtr<ObjectExpression> Parser::parse_object_expression()
|
||||
if (match(TokenType::TripleDot)) {
|
||||
consume();
|
||||
property_name = parse_expression(4);
|
||||
properties.append(create_ast_node<ObjectProperty>(*property_name, nullptr, ObjectProperty::Type::Spread));
|
||||
properties.append(create_ast_node<ObjectProperty>(*property_name, nullptr, ObjectProperty::Type::Spread, false));
|
||||
if (!match(TokenType::Comma))
|
||||
break;
|
||||
consume(TokenType::Comma);
|
||||
@ -622,7 +753,7 @@ NonnullRefPtr<ObjectExpression> Parser::parse_object_expression()
|
||||
|
||||
if (match(TokenType::ParenOpen)) {
|
||||
ASSERT(property_name);
|
||||
auto function = parse_function_node<FunctionExpression>(false);
|
||||
auto function = parse_function_node<FunctionExpression>(false, true);
|
||||
auto arg_count = function->parameters().size();
|
||||
|
||||
if (property_type == ObjectProperty::Type::Getter && arg_count != 0) {
|
||||
@ -642,7 +773,7 @@ NonnullRefPtr<ObjectExpression> Parser::parse_object_expression()
|
||||
continue;
|
||||
}
|
||||
|
||||
properties.append(create_ast_node<ObjectProperty>(*property_name, function, property_type));
|
||||
properties.append(create_ast_node<ObjectProperty>(*property_name, function, property_type, true));
|
||||
} else if (match(TokenType::Colon)) {
|
||||
if (!property_name) {
|
||||
syntax_error("Expected a property name");
|
||||
@ -650,9 +781,9 @@ NonnullRefPtr<ObjectExpression> Parser::parse_object_expression()
|
||||
continue;
|
||||
}
|
||||
consume();
|
||||
properties.append(create_ast_node<ObjectProperty>(*property_name, parse_expression(2), property_type));
|
||||
properties.append(create_ast_node<ObjectProperty>(*property_name, parse_expression(2), property_type, false));
|
||||
} else if (property_name && property_value) {
|
||||
properties.append(create_ast_node<ObjectProperty>(*property_name, *property_value, property_type));
|
||||
properties.append(create_ast_node<ObjectProperty>(*property_name, *property_value, property_type, false));
|
||||
} else {
|
||||
syntax_error("Expected a property");
|
||||
skip_to_next_property();
|
||||
@ -976,6 +1107,9 @@ NonnullRefPtr<Expression> Parser::parse_secondary_expression(NonnullRefPtr<Expre
|
||||
|
||||
NonnullRefPtr<CallExpression> Parser::parse_call_expression(NonnullRefPtr<Expression> lhs)
|
||||
{
|
||||
if (!m_parser_state.m_allow_super_constructor_call && lhs->is_super_expression())
|
||||
syntax_error("'super' keyword unexpected here");
|
||||
|
||||
consume(TokenType::ParenOpen);
|
||||
|
||||
Vector<CallExpression::Argument> arguments;
|
||||
@ -1085,8 +1219,11 @@ NonnullRefPtr<BlockStatement> Parser::parse_block_statement()
|
||||
}
|
||||
|
||||
template<typename FunctionNodeType>
|
||||
NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(bool check_for_function_and_name)
|
||||
NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(bool check_for_function_and_name, bool allow_super_property_lookup, bool allow_super_constructor_call)
|
||||
{
|
||||
TemporaryChange super_property_access_rollback(m_parser_state.m_allow_super_property_lookup, allow_super_property_lookup);
|
||||
TemporaryChange super_constructor_call_rollback(m_parser_state.m_allow_super_constructor_call, allow_super_constructor_call);
|
||||
|
||||
ScopePusher scope(*this, ScopePusher::Var | ScopePusher::Function);
|
||||
|
||||
if (check_for_function_and_name)
|
||||
@ -1465,6 +1602,7 @@ bool Parser::match_expression() const
|
||||
|| type == TokenType::ParenOpen
|
||||
|| type == TokenType::Function
|
||||
|| type == TokenType::This
|
||||
|| type == TokenType::Super
|
||||
|| type == TokenType::RegexLiteral
|
||||
|| match_unary_prefixed_expression();
|
||||
}
|
||||
@ -1563,6 +1701,16 @@ bool Parser::match_identifier_name() const
|
||||
return m_parser_state.m_current_token.is_identifier_name();
|
||||
}
|
||||
|
||||
bool Parser::match_property_key() const
|
||||
{
|
||||
auto type = m_parser_state.m_current_token.type();
|
||||
return match_identifier_name()
|
||||
|| type == TokenType::BracketOpen
|
||||
|| type == TokenType::StringLiteral
|
||||
|| type == TokenType::NumericLiteral
|
||||
|| type == TokenType::BigIntLiteral;
|
||||
}
|
||||
|
||||
bool Parser::done() const
|
||||
{
|
||||
return match(TokenType::Eof);
|
||||
|
@ -46,7 +46,7 @@ public:
|
||||
NonnullRefPtr<Program> parse_program();
|
||||
|
||||
template<typename FunctionNodeType>
|
||||
NonnullRefPtr<FunctionNodeType> parse_function_node(bool check_for_function_and_name = true);
|
||||
NonnullRefPtr<FunctionNodeType> parse_function_node(bool check_for_function_and_name = true, bool allow_super_property_lookup = false, bool allow_super_constructor_call = false);
|
||||
|
||||
NonnullRefPtr<Statement> parse_statement();
|
||||
NonnullRefPtr<BlockStatement> parse_block_statement();
|
||||
@ -80,6 +80,9 @@ public:
|
||||
NonnullRefPtr<NewExpression> parse_new_expression();
|
||||
RefPtr<FunctionExpression> try_parse_arrow_function_expression(bool expect_parens);
|
||||
RefPtr<Statement> try_parse_labelled_statement();
|
||||
NonnullRefPtr<ClassDeclaration> parse_class_declaration();
|
||||
NonnullRefPtr<ClassExpression> parse_class_expression(bool expect_class_name);
|
||||
NonnullRefPtr<Expression> parse_property_key();
|
||||
|
||||
struct Error {
|
||||
String message;
|
||||
@ -126,6 +129,7 @@ private:
|
||||
bool match_statement() const;
|
||||
bool match_variable_declaration() const;
|
||||
bool match_identifier_name() const;
|
||||
bool match_property_key() const;
|
||||
bool match(TokenType type) const;
|
||||
bool done() const;
|
||||
void expected(const char* what);
|
||||
@ -151,6 +155,8 @@ private:
|
||||
Vector<NonnullRefPtrVector<FunctionDeclaration>> m_function_scopes;
|
||||
UseStrictDirectiveState m_use_strict_directive { UseStrictDirectiveState::None };
|
||||
bool m_strict_mode { false };
|
||||
bool m_allow_super_property_lookup { false };
|
||||
bool m_allow_super_constructor_call { false };
|
||||
|
||||
explicit ParserState(Lexer);
|
||||
};
|
||||
|
@ -74,7 +74,7 @@ Value BigIntConstructor::call(Interpreter& interpreter)
|
||||
|
||||
Value BigIntConstructor::construct(Interpreter& interpreter)
|
||||
{
|
||||
interpreter.throw_exception<TypeError>(ErrorType::NotACtor, "BigInt");
|
||||
interpreter.throw_exception<TypeError>(ErrorType::NotAConstructor, "BigInt");
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,7 @@
|
||||
M(BigIntBadOperatorOtherType, "Cannot use %s operator with BigInt and other type") \
|
||||
M(BigIntIntArgument, "BigInt argument must be an integer") \
|
||||
M(BigIntInvalidValue, "Invalid value for BigInt: %s") \
|
||||
M(ClassDoesNotExtendAConstructorOrNull, "Class extends value %s is not a constructor or null") \
|
||||
M(Convert, "Cannot convert %s to %s") \
|
||||
M(ConvertUndefinedToObject, "Cannot convert undefined to object") \
|
||||
M(DescChangeNonConfigurable, "Cannot change attributes of non-configurable property '%s'") \
|
||||
@ -51,7 +52,7 @@
|
||||
M(JsonCircular, "Cannot stringify circular object") \
|
||||
M(JsonMalformed, "Malformed JSON string") \
|
||||
M(NotA, "Not a %s object") \
|
||||
M(NotACtor, "%s is not a constructor") \
|
||||
M(NotAConstructor, "%s is not a constructor") \
|
||||
M(NotAFunction, "%s is not a function") \
|
||||
M(NotAFunctionNoParam, "Not a function") \
|
||||
M(NotAn, "Not an %s object") \
|
||||
@ -63,6 +64,8 @@
|
||||
M(ObjectSetPrototypeOfReturnedFalse, "Object's [[SetPrototypeOf]] method returned false") \
|
||||
M(ObjectSetPrototypeOfTwoArgs, "Object.setPrototypeOf requires at least two arguments") \
|
||||
M(ObjectPreventExtensionsReturnedFalse, "Object's [[PreventExtensions]] method returned false") \
|
||||
M(ObjectPrototypeNullOrUndefinedOnSuperPropertyAccess, \
|
||||
"Object prototype must not be %s on a super property access") \
|
||||
M(ObjectPrototypeWrongType, "Prototype must be an object or null") \
|
||||
M(ProxyCallWithNew, "Proxy must be called with the 'new' operator") \
|
||||
M(ProxyConstructorBadType, "Expected %s argument of Proxy constructor to be object, got %s") \
|
||||
@ -138,6 +141,8 @@
|
||||
M(ReflectBadDescriptorArgument, "Descriptor argument is not an object") \
|
||||
M(StringRawCannotConvert, "Cannot convert property 'raw' to object from %s") \
|
||||
M(StringRepeatCountMustBe, "repeat count must be a %s number") \
|
||||
M(ThisHasNotBeenInitialized, "|this| has not been initialized") \
|
||||
M(ThisIsAlreadyInitialized, "|this| is already initialized") \
|
||||
M(ToObjectNullOrUndef, "ToObject on null or undefined") \
|
||||
M(UnknownIdentifier, "'%s' is not defined") \
|
||||
/* LibWeb bindings */ \
|
||||
|
@ -35,6 +35,11 @@ class Function : public Object {
|
||||
JS_OBJECT(Function, Object);
|
||||
|
||||
public:
|
||||
enum class ConstructorKind {
|
||||
Base,
|
||||
Derived,
|
||||
};
|
||||
|
||||
virtual ~Function();
|
||||
virtual void initialize(Interpreter&, GlobalObject&) override { }
|
||||
|
||||
@ -49,15 +54,15 @@ public:
|
||||
|
||||
BoundFunction* bind(Value bound_this_value, Vector<Value> arguments);
|
||||
|
||||
Value bound_this() const
|
||||
{
|
||||
return m_bound_this;
|
||||
}
|
||||
Value bound_this() const { return m_bound_this; }
|
||||
|
||||
const Vector<Value>& bound_arguments() const
|
||||
{
|
||||
return m_bound_arguments;
|
||||
}
|
||||
const Vector<Value>& bound_arguments() const { return m_bound_arguments; }
|
||||
|
||||
Value home_object() const { return m_home_object; }
|
||||
void set_home_object(Value home_object) { m_home_object = home_object; }
|
||||
|
||||
ConstructorKind constructor_kind() const { return m_constructor_kind; };
|
||||
void set_constructor_kind(ConstructorKind constructor_kind) { m_constructor_kind = constructor_kind; }
|
||||
|
||||
protected:
|
||||
explicit Function(Object& prototype);
|
||||
@ -67,6 +72,8 @@ private:
|
||||
virtual bool is_function() const final { return true; }
|
||||
Value m_bound_this;
|
||||
Vector<Value> m_bound_arguments;
|
||||
Value m_home_object;
|
||||
ConstructorKind m_constructor_kind = ConstructorKind::Base;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -24,7 +24,11 @@
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <LibJS/Interpreter.h>
|
||||
#include <LibJS/Runtime/Error.h>
|
||||
#include <LibJS/Runtime/Function.h>
|
||||
#include <LibJS/Runtime/LexicalEnvironment.h>
|
||||
#include <LibJS/Runtime/Value.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
@ -32,12 +36,24 @@ LexicalEnvironment::LexicalEnvironment()
|
||||
{
|
||||
}
|
||||
|
||||
LexicalEnvironment::LexicalEnvironment(EnvironmentRecordType environment_record_type)
|
||||
: m_environment_record_type(environment_record_type)
|
||||
{
|
||||
}
|
||||
|
||||
LexicalEnvironment::LexicalEnvironment(HashMap<FlyString, Variable> variables, LexicalEnvironment* parent)
|
||||
: m_parent(parent)
|
||||
, m_variables(move(variables))
|
||||
{
|
||||
}
|
||||
|
||||
LexicalEnvironment::LexicalEnvironment(HashMap<FlyString, Variable> variables, LexicalEnvironment* parent, EnvironmentRecordType environment_record_type)
|
||||
: m_parent(parent)
|
||||
, m_variables(move(variables))
|
||||
, m_environment_record_type(environment_record_type)
|
||||
{
|
||||
}
|
||||
|
||||
LexicalEnvironment::~LexicalEnvironment()
|
||||
{
|
||||
}
|
||||
@ -46,6 +62,10 @@ void LexicalEnvironment::visit_children(Visitor& visitor)
|
||||
{
|
||||
Cell::visit_children(visitor);
|
||||
visitor.visit(m_parent);
|
||||
visitor.visit(m_this_value);
|
||||
visitor.visit(m_home_object);
|
||||
visitor.visit(m_new_target);
|
||||
visitor.visit(m_current_function);
|
||||
for (auto& it : m_variables)
|
||||
visitor.visit(it.value.value);
|
||||
}
|
||||
@ -60,4 +80,53 @@ void LexicalEnvironment::set(const FlyString& name, Variable variable)
|
||||
m_variables.set(name, variable);
|
||||
}
|
||||
|
||||
bool LexicalEnvironment::has_super_binding() const
|
||||
{
|
||||
return m_environment_record_type == EnvironmentRecordType::Function && this_binding_status() != ThisBindingStatus::Lexical && m_home_object.is_object();
|
||||
}
|
||||
|
||||
Value LexicalEnvironment::get_super_base()
|
||||
{
|
||||
ASSERT(has_super_binding());
|
||||
if (m_home_object.is_object())
|
||||
return m_home_object.as_object().prototype();
|
||||
return {};
|
||||
}
|
||||
|
||||
bool LexicalEnvironment::has_this_binding() const
|
||||
{
|
||||
// More like "is_capable_of_having_a_this_binding".
|
||||
switch (m_environment_record_type) {
|
||||
case EnvironmentRecordType::Declarative:
|
||||
case EnvironmentRecordType::Object:
|
||||
return false;
|
||||
case EnvironmentRecordType::Function:
|
||||
return this_binding_status() != ThisBindingStatus::Lexical;
|
||||
case EnvironmentRecordType::Module:
|
||||
case EnvironmentRecordType::Global:
|
||||
return true;
|
||||
}
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
Value LexicalEnvironment::get_this_binding() const
|
||||
{
|
||||
ASSERT(has_this_binding());
|
||||
if (this_binding_status() == ThisBindingStatus::Uninitialized)
|
||||
return interpreter().throw_exception<ReferenceError>(ErrorType::ThisHasNotBeenInitialized);
|
||||
|
||||
return m_this_value;
|
||||
}
|
||||
|
||||
void LexicalEnvironment::bind_this_value(Value this_value)
|
||||
{
|
||||
ASSERT(has_this_binding());
|
||||
if (m_this_binding_status == ThisBindingStatus::Initialized) {
|
||||
interpreter().throw_exception<ReferenceError>(ErrorType::ThisIsAlreadyInitialized);
|
||||
return;
|
||||
}
|
||||
m_this_value = this_value;
|
||||
m_this_binding_status = ThisBindingStatus::Initialized;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -40,11 +40,27 @@ struct Variable {
|
||||
|
||||
class LexicalEnvironment final : public Cell {
|
||||
public:
|
||||
enum class ThisBindingStatus {
|
||||
Lexical,
|
||||
Initialized,
|
||||
Uninitialized,
|
||||
};
|
||||
|
||||
enum class EnvironmentRecordType {
|
||||
Declarative,
|
||||
Function,
|
||||
Global,
|
||||
Object,
|
||||
Module,
|
||||
};
|
||||
|
||||
LexicalEnvironment();
|
||||
LexicalEnvironment(EnvironmentRecordType);
|
||||
LexicalEnvironment(HashMap<FlyString, Variable> variables, LexicalEnvironment* parent);
|
||||
LexicalEnvironment(HashMap<FlyString, Variable> variables, LexicalEnvironment* parent, EnvironmentRecordType);
|
||||
virtual ~LexicalEnvironment() override;
|
||||
|
||||
LexicalEnvironment* parent() { return m_parent; }
|
||||
LexicalEnvironment* parent() const { return m_parent; }
|
||||
|
||||
Optional<Variable> get(const FlyString&) const;
|
||||
void set(const FlyString&, Variable);
|
||||
@ -53,12 +69,37 @@ public:
|
||||
|
||||
const HashMap<FlyString, Variable>& variables() const { return m_variables; }
|
||||
|
||||
void set_home_object(Value object) { m_home_object = object; }
|
||||
bool has_super_binding() const;
|
||||
Value get_super_base();
|
||||
|
||||
bool has_this_binding() const;
|
||||
ThisBindingStatus this_binding_status() const { return m_this_binding_status; }
|
||||
Value get_this_binding() const;
|
||||
void bind_this_value(Value this_value);
|
||||
|
||||
// Not a standard operation.
|
||||
void replace_this_binding(Value this_value) { m_this_value = this_value; }
|
||||
|
||||
Value new_target() const { return m_new_target; };
|
||||
void set_new_target(Value new_target) { m_new_target = new_target; }
|
||||
|
||||
Function* current_function() const { return m_current_function; }
|
||||
void set_current_function(Function& function) { m_current_function = &function; }
|
||||
|
||||
private:
|
||||
virtual const char* class_name() const override { return "LexicalEnvironment"; }
|
||||
virtual void visit_children(Visitor&) override;
|
||||
|
||||
LexicalEnvironment* m_parent { nullptr };
|
||||
HashMap<FlyString, Variable> m_variables;
|
||||
EnvironmentRecordType m_environment_record_type = EnvironmentRecordType::Declarative;
|
||||
ThisBindingStatus m_this_binding_status = ThisBindingStatus::Uninitialized;
|
||||
Value m_home_object;
|
||||
Value m_this_value;
|
||||
Value m_new_target;
|
||||
// Corresponds to [[FunctionObject]]
|
||||
Function* m_current_function { nullptr };
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -68,4 +68,9 @@ Value NativeFunction::construct(Interpreter&)
|
||||
return {};
|
||||
}
|
||||
|
||||
LexicalEnvironment* NativeFunction::create_environment()
|
||||
{
|
||||
return interpreter().heap().allocate<LexicalEnvironment>(global_object(), LexicalEnvironment::EnvironmentRecordType::Function);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ protected:
|
||||
|
||||
private:
|
||||
virtual bool is_native_function() const override { return true; }
|
||||
virtual LexicalEnvironment* create_environment() override final { return nullptr; }
|
||||
virtual LexicalEnvironment* create_environment() override final;
|
||||
|
||||
FlyString m_name;
|
||||
AK::Function<Value(Interpreter&, GlobalObject&)> m_native_function;
|
||||
|
@ -437,7 +437,7 @@ bool Object::define_accessor(PropertyName property_name, Function& getter_or_set
|
||||
accessor = &existing_property.as_accessor();
|
||||
}
|
||||
if (!accessor) {
|
||||
accessor = Accessor::create(interpreter(), nullptr, nullptr);
|
||||
accessor = Accessor::create(interpreter(), global_object(), nullptr, nullptr);
|
||||
bool definition_success = define_property(property_name, accessor, attributes, throw_exceptions);
|
||||
if (interpreter().exception())
|
||||
return {};
|
||||
|
@ -66,8 +66,8 @@ ScriptFunction::ScriptFunction(GlobalObject& global_object, const FlyString& nam
|
||||
void ScriptFunction::initialize(Interpreter& interpreter, GlobalObject& global_object)
|
||||
{
|
||||
Function::initialize(interpreter, global_object);
|
||||
if (!is_arrow_function) {
|
||||
Object* prototype = Object::create_empty(interpreter(), interpreter().global_object());
|
||||
if (!m_is_arrow_function) {
|
||||
Object* prototype = Object::create_empty(interpreter, global_object);
|
||||
prototype->define_property("constructor", this, Attribute::Writable | Attribute::Configurable);
|
||||
define_property("prototype", prototype, 0);
|
||||
}
|
||||
@ -99,9 +99,11 @@ LexicalEnvironment* ScriptFunction::create_environment()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (variables.is_empty())
|
||||
return m_parent_environment;
|
||||
return heap().allocate<LexicalEnvironment>(global_object(), move(variables), m_parent_environment);
|
||||
|
||||
auto* environment = heap().allocate<LexicalEnvironment>(global_object(), move(variables), m_parent_environment, LexicalEnvironment::EnvironmentRecordType::Function);
|
||||
environment->set_home_object(home_object());
|
||||
environment->set_current_function(*this);
|
||||
return environment;
|
||||
}
|
||||
|
||||
Value ScriptFunction::call(Interpreter& interpreter)
|
||||
@ -134,7 +136,7 @@ Value ScriptFunction::call(Interpreter& interpreter)
|
||||
Value ScriptFunction::construct(Interpreter& interpreter)
|
||||
{
|
||||
if (m_is_arrow_function)
|
||||
return interpreter.throw_exception<TypeError>(ErrorType::NotACtor, m_name.characters());
|
||||
return interpreter.throw_exception<TypeError>(ErrorType::NotAConstructor, m_name.characters());
|
||||
return call(interpreter);
|
||||
}
|
||||
|
||||
|
@ -76,7 +76,7 @@ Value SymbolConstructor::call(Interpreter& interpreter)
|
||||
|
||||
Value SymbolConstructor::construct(Interpreter& interpreter)
|
||||
{
|
||||
interpreter.throw_exception<TypeError>(ErrorType::NotACtor, "Symbol");
|
||||
interpreter.throw_exception<TypeError>(ErrorType::NotAConstructor, "Symbol");
|
||||
return {};
|
||||
}
|
||||
|
||||
|
248
Libraries/LibJS/Tests/class-basic.js
Normal file
248
Libraries/LibJS/Tests/class-basic.js
Normal file
@ -0,0 +1,248 @@
|
||||
load("test-common.js");
|
||||
|
||||
try {
|
||||
class X {
|
||||
constructor() {
|
||||
this.x = 3;
|
||||
}
|
||||
getX() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
init() {
|
||||
this.y = 3;
|
||||
}
|
||||
}
|
||||
|
||||
assert(X.name === "X");
|
||||
assert(X.length === 0);
|
||||
|
||||
class Y extends X {
|
||||
init() {
|
||||
super.init();
|
||||
this.y += 3;
|
||||
}
|
||||
}
|
||||
|
||||
assert(new Y().getX() === 3);
|
||||
assert(new Y().x === 3);
|
||||
|
||||
let x = new X();
|
||||
assert(x.x === 3);
|
||||
assert(x.getX() === 3);
|
||||
|
||||
let y = new Y();
|
||||
assert(y.x === 3);
|
||||
assert(y.y === undefined);
|
||||
y.init();
|
||||
assert(y.y === 6);
|
||||
assert(y.hasOwnProperty("y"));
|
||||
|
||||
class Foo {
|
||||
constructor(x) {
|
||||
this.x = x;
|
||||
}
|
||||
}
|
||||
assert(Foo.length === 1);
|
||||
|
||||
class Bar extends Foo {
|
||||
constructor() {
|
||||
super(5);
|
||||
}
|
||||
}
|
||||
|
||||
class Baz {
|
||||
"constructor"() {
|
||||
this.foo = 55;
|
||||
this._bar = 33;
|
||||
}
|
||||
|
||||
get bar() {
|
||||
return this._bar;
|
||||
}
|
||||
|
||||
set bar(value) {
|
||||
this._bar = value;
|
||||
}
|
||||
|
||||
["get" + "Foo"]() {
|
||||
return this.foo;
|
||||
}
|
||||
|
||||
static get staticFoo() {
|
||||
assert(this === Baz);
|
||||
return 11;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let barPropertyDescriptor = Object.getOwnPropertyDescriptor(Baz.prototype, "bar");
|
||||
assert(barPropertyDescriptor.get.name === "get bar");
|
||||
assert(barPropertyDescriptor.set.name === "set bar");
|
||||
|
||||
let baz = new Baz();
|
||||
assert(baz.foo === 55);
|
||||
assert(baz.bar === 33);
|
||||
baz.bar = 22;
|
||||
assert(baz.bar === 22);
|
||||
|
||||
assert(baz.getFoo() === 55);
|
||||
assert(Baz.staticFoo === 11);
|
||||
|
||||
assert(new Bar().x === 5);
|
||||
|
||||
class ExtendsFunction extends function () { this.foo = 22; } { }
|
||||
assert(new ExtendsFunction().foo === 22);
|
||||
|
||||
class ExtendsString extends String { }
|
||||
assert(new ExtendsString() instanceof String);
|
||||
assert(new ExtendsString() instanceof ExtendsString);
|
||||
assert(new ExtendsString("abc").charAt(1) === "b");
|
||||
|
||||
class MyWeirdString extends ExtendsString {
|
||||
charAt(i) {
|
||||
return "#" + super.charAt(i);
|
||||
}
|
||||
}
|
||||
assert(new MyWeirdString("abc").charAt(1) === "#b")
|
||||
|
||||
class ExtendsNull extends null { }
|
||||
|
||||
assertThrowsError(() => {
|
||||
new ExtendsNull();
|
||||
}, {
|
||||
error: ReferenceError
|
||||
});
|
||||
assert(Object.getPrototypeOf(ExtendsNull.prototype) === null);
|
||||
|
||||
class ExtendsClassExpression extends class { constructor(x) { this.x = x; } } {
|
||||
constructor(y) {
|
||||
super(5);
|
||||
this.y = 6;
|
||||
}
|
||||
}
|
||||
let extendsClassExpression = new ExtendsClassExpression();
|
||||
assert(extendsClassExpression.x === 5);
|
||||
assert(extendsClassExpression.y === 6);
|
||||
|
||||
class InStrictMode {
|
||||
constructor() {
|
||||
assert(isStrictMode());
|
||||
}
|
||||
|
||||
method() {
|
||||
assert(isStrictMode());
|
||||
}
|
||||
}
|
||||
|
||||
let resultOfAnExpression = new (class {
|
||||
constructor(x) {
|
||||
this.x = x;
|
||||
}
|
||||
getX() {
|
||||
return this.x + 10;
|
||||
}
|
||||
})(55);
|
||||
assert(resultOfAnExpression.x === 55);
|
||||
assert(resultOfAnExpression.getX() === 65);
|
||||
|
||||
let ClassExpression = class Foo { };
|
||||
assert(ClassExpression.name === "Foo");
|
||||
|
||||
new InStrictMode().method();
|
||||
assert(!isStrictMode());
|
||||
|
||||
assertIsSyntaxError(`
|
||||
class GetterWithArgument {
|
||||
get foo(bar) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
assertIsSyntaxError(`
|
||||
class SetterWithNoArgumetns {
|
||||
set foo() {
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
assertIsSyntaxError(`
|
||||
class SetterWithMoreThanOneArgument {
|
||||
set foo(bar, baz) {
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
assertIsSyntaxError(`
|
||||
class FooBase {}
|
||||
class IsASyntaxError extends FooBase {
|
||||
bar() {
|
||||
function f() { super.baz; }
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
assertIsSyntaxError(`
|
||||
class NoBaseSuper {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
assertThrowsError(() => {
|
||||
class BadExtends extends 3 { }
|
||||
|
||||
}, {
|
||||
error: TypeError
|
||||
});
|
||||
|
||||
assertThrowsError(() => {
|
||||
class BadExtends extends undefined { }
|
||||
}, {
|
||||
error: TypeError
|
||||
});
|
||||
|
||||
class SuperNotASyntaxError {
|
||||
bar() {
|
||||
() => { super.baz };
|
||||
}
|
||||
}
|
||||
|
||||
class SuperNoBasePropertyLookup {
|
||||
constructor() {
|
||||
super.foo;
|
||||
}
|
||||
}
|
||||
|
||||
assertThrowsError(() => {
|
||||
class Base { }
|
||||
class DerivedDoesntCallSuper extends Base {
|
||||
constructor() {
|
||||
this;
|
||||
}
|
||||
}
|
||||
|
||||
new DerivedDoesntCallSuper();
|
||||
}, {
|
||||
error: ReferenceError,
|
||||
});
|
||||
assertThrowsError(() => {
|
||||
class Base { }
|
||||
class CallsSuperTwice extends Base {
|
||||
constructor() {
|
||||
super();
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
new CallsSuperTwice();
|
||||
}, {
|
||||
error: ReferenceError,
|
||||
});
|
||||
|
||||
console.log("PASS");
|
||||
} catch (e) {
|
||||
console.log("FAIL: " + e);
|
||||
}
|
@ -70,6 +70,22 @@ try {
|
||||
assert(a[2] === 3);
|
||||
assert(o4.test === undefined);
|
||||
|
||||
var base = {
|
||||
getNumber() {
|
||||
return 10;
|
||||
}
|
||||
};
|
||||
|
||||
var derived = {
|
||||
getNumber() {
|
||||
return 20 + super.getNumber();
|
||||
}
|
||||
};
|
||||
|
||||
Object.setPrototypeOf(derived, base)
|
||||
assert(derived.getNumber() === 30);
|
||||
|
||||
assertIsSyntaxError("({ foo: function() { super.bar; } })")
|
||||
assertIsSyntaxError("({ get ...foo })");
|
||||
assertIsSyntaxError("({ get... foo })");
|
||||
assertIsSyntaxError("({ get foo })");
|
||||
|
Loading…
Reference in New Issue
Block a user