summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Libraries/LibJS/AST.cpp228
-rw-r--r--Libraries/LibJS/AST.h89
-rw-r--r--Libraries/LibJS/Interpreter.cpp77
-rw-r--r--Libraries/LibJS/Interpreter.h4
-rw-r--r--Libraries/LibJS/Parser.cpp218
-rw-r--r--Libraries/LibJS/Parser.h8
-rw-r--r--Libraries/LibJS/Runtime/BigIntConstructor.cpp2
-rw-r--r--Libraries/LibJS/Runtime/ErrorTypes.h7
-rw-r--r--Libraries/LibJS/Runtime/Function.h23
-rw-r--r--Libraries/LibJS/Runtime/LexicalEnvironment.cpp69
-rw-r--r--Libraries/LibJS/Runtime/LexicalEnvironment.h43
-rw-r--r--Libraries/LibJS/Runtime/NativeFunction.cpp5
-rw-r--r--Libraries/LibJS/Runtime/NativeFunction.h2
-rw-r--r--Libraries/LibJS/Runtime/Object.cpp2
-rw-r--r--Libraries/LibJS/Runtime/ScriptFunction.cpp14
-rw-r--r--Libraries/LibJS/Runtime/SymbolConstructor.cpp2
-rw-r--r--Libraries/LibJS/Tests/class-basic.js248
-rw-r--r--Libraries/LibJS/Tests/object-basic.js16
18 files changed, 966 insertions, 91 deletions
diff --git a/Libraries/LibJS/AST.cpp b/Libraries/LibJS/AST.cpp
index 4d86f628c2..183d747adb 100644
--- a/Libraries/LibJS/AST.cpp
+++ b/Libraries/LibJS/AST.cpp
@@ -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,32 +175,27 @@ 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);
+
+ interpreter.current_environment()->bind_this_value(result);
} else {
- call_frame.this_value = function.bound_this().value_or(this_value);
- result = function.call(interpreter);
+ result = interpreter.call(function, this_value, move(arguments));
}
- interpreter.pop_call_frame();
-
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());
diff --git a/Libraries/LibJS/AST.h b/Libraries/LibJS/AST.h
index d6084121f1..2e1cdbebd5 100644
--- a/Libraries/LibJS/AST.h
+++ b/Libraries/LibJS/AST.h
@@ -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 {
diff --git a/Libraries/LibJS/Interpreter.cpp b/Libraries/LibJS/Interpreter.cpp
index de98858134..3c585aef53 100644
--- a/Libraries/LibJS/Interpreter.cpp
+++ b/Libraries/LibJS/Interpreter.cpp
@@ -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();
+}
+
}
diff --git a/Libraries/LibJS/Interpreter.h b/Libraries/LibJS/Interpreter.h
index 00e4cacf79..ef3c274964 100644
--- a/Libraries/LibJS/Interpreter.h
+++ b/Libraries/LibJS/Interpreter.h
@@ -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();
diff --git a/Libraries/LibJS/Parser.cpp b/Libraries/LibJS/Parser.cpp
index 7924c4913a..f06bab3572 100644
--- a/Libraries/LibJS/Parser.cpp
+++ b/Libraries/LibJS/Parser.cpp
@@ -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);
diff --git a/Libraries/LibJS/Parser.h b/Libraries/LibJS/Parser.h
index 0b256d3f76..c1f0bcb252 100644
--- a/Libraries/LibJS/Parser.h
+++ b/Libraries/LibJS/Parser.h
@@ -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);
};
diff --git a/Libraries/LibJS/Runtime/BigIntConstructor.cpp b/Libraries/LibJS/Runtime/BigIntConstructor.cpp
index b7d263d90b..fb0ccf7dc0 100644
--- a/Libraries/LibJS/Runtime/BigIntConstructor.cpp
+++ b/Libraries/LibJS/Runtime/BigIntConstructor.cpp
@@ -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 {};
}
diff --git a/Libraries/LibJS/Runtime/ErrorTypes.h b/Libraries/LibJS/Runtime/ErrorTypes.h
index 3dfaccdc95..883ed1962b 100644
--- a/Libraries/LibJS/Runtime/ErrorTypes.h
+++ b/Libraries/LibJS/Runtime/ErrorTypes.h
@@ -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 */ \
diff --git a/Libraries/LibJS/Runtime/Function.h b/Libraries/LibJS/Runtime/Function.h
index 8e19472fcc..bd25fcb0e9 100644
--- a/Libraries/LibJS/Runtime/Function.h
+++ b/Libraries/LibJS/Runtime/Function.h
@@ -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; }
+
+ Value home_object() const { return m_home_object; }
+ void set_home_object(Value home_object) { m_home_object = home_object; }
- const Vector<Value>& bound_arguments() const
- {
- return m_bound_arguments;
- }
+ 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;
};
}
diff --git a/Libraries/LibJS/Runtime/LexicalEnvironment.cpp b/Libraries/LibJS/Runtime/LexicalEnvironment.cpp
index 518126e0d5..0dae1029ce 100644
--- a/Libraries/LibJS/Runtime/LexicalEnvironment.cpp
+++ b/Libraries/LibJS/Runtime/LexicalEnvironment.cpp
@@ -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;
+}
+
}
diff --git a/Libraries/LibJS/Runtime/LexicalEnvironment.h b/Libraries/LibJS/Runtime/LexicalEnvironment.h
index c2cf3d8651..a4c9deda13 100644
--- a/Libraries/LibJS/Runtime/LexicalEnvironment.h
+++ b/Libraries/LibJS/Runtime/LexicalEnvironment.h
@@ -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 };
};
}
diff --git a/Libraries/LibJS/Runtime/NativeFunction.cpp b/Libraries/LibJS/Runtime/NativeFunction.cpp
index 070632bef0..6d350ee9af 100644
--- a/Libraries/LibJS/Runtime/NativeFunction.cpp
+++ b/Libraries/LibJS/Runtime/NativeFunction.cpp
@@ -68,4 +68,9 @@ Value NativeFunction::construct(Interpreter&)
return {};
}
+LexicalEnvironment* NativeFunction::create_environment()
+{
+ return interpreter().heap().allocate<LexicalEnvironment>(global_object(), LexicalEnvironment::EnvironmentRecordType::Function);
+}
+
}
diff --git a/Libraries/LibJS/Runtime/NativeFunction.h b/Libraries/LibJS/Runtime/NativeFunction.h
index f569e382ac..19cb64bd4e 100644
--- a/Libraries/LibJS/Runtime/NativeFunction.h
+++ b/Libraries/LibJS/Runtime/NativeFunction.h
@@ -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;
diff --git a/Libraries/LibJS/Runtime/Object.cpp b/Libraries/LibJS/Runtime/Object.cpp
index 2d1feb8ebd..7abeeaf997 100644
--- a/Libraries/LibJS/Runtime/Object.cpp
+++ b/Libraries/LibJS/Runtime/Object.cpp
@@ -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 {};
diff --git a/Libraries/LibJS/Runtime/ScriptFunction.cpp b/Libraries/LibJS/Runtime/ScriptFunction.cpp
index 11fa9c5da0..7c3b461649 100644
--- a/Libraries/LibJS/Runtime/ScriptFunction.cpp
+++ b/Libraries/LibJS/Runtime/ScriptFunction.cpp
@@ -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);
}
diff --git a/Libraries/LibJS/Runtime/SymbolConstructor.cpp b/Libraries/LibJS/Runtime/SymbolConstructor.cpp
index 4a74c4d0bc..9080b2d56b 100644
--- a/Libraries/LibJS/Runtime/SymbolConstructor.cpp
+++ b/Libraries/LibJS/Runtime/SymbolConstructor.cpp
@@ -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 {};
}
diff --git a/Libraries/LibJS/Tests/class-basic.js b/Libraries/LibJS/Tests/class-basic.js
new file mode 100644
index 0000000000..842efd472b
--- /dev/null
+++ b/Libraries/LibJS/Tests/class-basic.js
@@ -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);
+}
diff --git a/Libraries/LibJS/Tests/object-basic.js b/Libraries/LibJS/Tests/object-basic.js
index 04580afa0c..5e61525e40 100644
--- a/Libraries/LibJS/Tests/object-basic.js
+++ b/Libraries/LibJS/Tests/object-basic.js
@@ -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 })");