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