diff options
-rw-r--r-- | Libraries/LibJS/AST.cpp | 228 | ||||
-rw-r--r-- | Libraries/LibJS/AST.h | 89 | ||||
-rw-r--r-- | Libraries/LibJS/Interpreter.cpp | 77 | ||||
-rw-r--r-- | Libraries/LibJS/Interpreter.h | 4 | ||||
-rw-r--r-- | Libraries/LibJS/Parser.cpp | 218 | ||||
-rw-r--r-- | Libraries/LibJS/Parser.h | 8 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/BigIntConstructor.cpp | 2 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/ErrorTypes.h | 7 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/Function.h | 23 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/LexicalEnvironment.cpp | 69 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/LexicalEnvironment.h | 43 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/NativeFunction.cpp | 5 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/NativeFunction.h | 2 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/Object.cpp | 2 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/ScriptFunction.cpp | 14 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/SymbolConstructor.cpp | 2 | ||||
-rw-r--r-- | Libraries/LibJS/Tests/class-basic.js | 248 | ||||
-rw-r--r-- | Libraries/LibJS/Tests/object-basic.js | 16 |
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 })"); |