Prechádzať zdrojové kódy

LibJS: Initial class implementation; allow super expressions in object
literal methods; add EnvrionmentRecord fields and methods to
LexicalEnvironment

Adding EnvrionmentRecord's fields and methods lets us throw an exception
when |this| is not initialized, which occurs when the super constructor
in a derived class has not yet been called, or when |this| has already
been initialized (the super constructor was already called).

Jack Karamanian 5 rokov pred
rodič
commit
7533fd8b02

+ 204 - 24
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());

+ 88 - 1
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 {

+ 67 - 10
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();
+}
+
 }

+ 4 - 0
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();
 

+ 183 - 35
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);

+ 7 - 1
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);
     };

+ 1 - 1
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 {};
 }
 

+ 6 - 1
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 */                                                                              \

+ 15 - 8
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;
 };
 
 }

+ 69 - 0
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;
+}
+
 }

+ 42 - 1
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 };
 };
 
 }

+ 5 - 0
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);
+}
+
 }

+ 1 - 1
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;

+ 1 - 1
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 {};

+ 8 - 6
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);
 }
 

+ 1 - 1
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 {};
 }
 

+ 248 - 0
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);
+}

+ 16 - 0
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 })");