mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 12:32:43 +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) }; |         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()) { |     if (m_callee->is_member_expression()) { | ||||||
|         auto& member_expression = static_cast<const MemberExpression&>(*m_callee); |         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()) |         if (interpreter.exception()) | ||||||
|             return {}; |             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()) |         if (interpreter.exception()) | ||||||
|             return {}; |             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 { this_value, callee }; | ||||||
|     } |     } | ||||||
|     return { &global_object, m_callee->execute(interpreter, global_object) }; |     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(); |     auto& function = callee.as_function(); | ||||||
| 
 | 
 | ||||||
|     MarkedValueList arguments(interpreter.heap()); |     MarkedValueList arguments(interpreter.heap()); | ||||||
|     arguments.values().append(function.bound_arguments()); |  | ||||||
| 
 | 
 | ||||||
|     for (size_t i = 0; i < m_arguments.size(); ++i) { |     for (size_t i = 0; i < m_arguments.size(); ++i) { | ||||||
|         auto value = m_arguments[i].value->execute(interpreter, global_object); |         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; |     Object* new_object = nullptr; | ||||||
|     Value result; |     Value result; | ||||||
|     if (is_new_expression()) { |     if (is_new_expression()) { | ||||||
|         new_object = Object::create_empty(interpreter, global_object); |         result = interpreter.construct(function, function, move(arguments), global_object); | ||||||
|         auto prototype = function.get("prototype"); |         if (result.is_object()) | ||||||
|         if (interpreter.exception()) |             new_object = &result.as_object(); | ||||||
|             return {}; |     } else if (m_callee->is_super_expression()) { | ||||||
|         if (prototype.is_object()) { |         auto* super_constructor = interpreter.current_environment()->current_function()->prototype(); | ||||||
|             new_object->set_prototype(&prototype.as_object()); |         // FIXME: Functions should track their constructor kind.
 | ||||||
|             if (interpreter.exception()) |         if (!super_constructor || !super_constructor->is_function()) | ||||||
|                 return {}; |             return interpreter.throw_exception<TypeError>(ErrorType::NotAConstructor, "Super constructor"); | ||||||
|         } |  | ||||||
|         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(); |         result = interpreter.construct(static_cast<Function&>(*super_constructor), function, move(arguments), global_object); | ||||||
|  |         if (interpreter.exception()) | ||||||
|  |             return {}; | ||||||
|  | 
 | ||||||
|  |         interpreter.current_environment()->bind_this_value(result); | ||||||
|  |     } else { | ||||||
|  |         result = interpreter.call(function, this_value, move(arguments)); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     if (interpreter.exception()) |     if (interpreter.exception()) | ||||||
|         return {}; |         return {}; | ||||||
|  | @ -658,6 +665,112 @@ Value UnaryExpression::execute(Interpreter& interpreter, GlobalObject& global_ob | ||||||
|     ASSERT_NOT_REACHED(); |     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) | static void print_indent(int indent) | ||||||
| { | { | ||||||
|     for (int i = 0; i < indent * 2; ++i) |     for (int i = 0; i < indent * 2; ++i) | ||||||
|  | @ -833,12 +946,76 @@ void CallExpression::dump(int indent) const | ||||||
|         argument.value->dump(indent + 1); |         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 | void StringLiteral::dump(int indent) const | ||||||
| { | { | ||||||
|     print_indent(indent); |     print_indent(indent); | ||||||
|     printf("StringLiteral \"%s\"\n", m_value.characters()); |     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 | void NumericLiteral::dump(int indent) const | ||||||
| { | { | ||||||
|     print_indent(indent); |     print_indent(indent); | ||||||
|  | @ -1006,9 +1183,9 @@ Value SpreadExpression::execute(Interpreter& interpreter, GlobalObject& global_o | ||||||
|     return m_target->execute(interpreter, global_object); |     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 | void ThisExpression::dump(int indent) const | ||||||
|  | @ -1353,6 +1530,9 @@ Value ObjectExpression::execute(Interpreter& interpreter, GlobalObject& global_o | ||||||
|         if (interpreter.exception()) |         if (interpreter.exception()) | ||||||
|             return {}; |             return {}; | ||||||
| 
 | 
 | ||||||
|  |         if (value.is_function() && property.is_method()) | ||||||
|  |             value.as_function().set_home_object(object); | ||||||
|  | 
 | ||||||
|         String name = key; |         String name = key; | ||||||
|         if (property.type() == ObjectProperty::Type::Getter) { |         if (property.type() == ObjectProperty::Type::Getter) { | ||||||
|             name = String::format("get %s", key.characters()); |             name = String::format("get %s", key.characters()); | ||||||
|  |  | ||||||
|  | @ -63,6 +63,7 @@ public: | ||||||
|     virtual bool is_variable_declaration() const { return false; } |     virtual bool is_variable_declaration() const { return false; } | ||||||
|     virtual bool is_call_expression() const { return false; } |     virtual bool is_call_expression() const { return false; } | ||||||
|     virtual bool is_new_expression() const { return false; } |     virtual bool is_new_expression() const { return false; } | ||||||
|  |     virtual bool is_super_expression() const { return false; } | ||||||
| 
 | 
 | ||||||
| protected: | protected: | ||||||
|     ASTNode() { } |     ASTNode() { } | ||||||
|  | @ -581,6 +582,8 @@ public: | ||||||
|     { |     { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     StringView value() const { return m_value; } | ||||||
|  | 
 | ||||||
|     virtual Value execute(Interpreter&, GlobalObject&) const override; |     virtual Value execute(Interpreter&, GlobalObject&) const override; | ||||||
|     virtual void dump(int indent) const override; |     virtual void dump(int indent) const override; | ||||||
| 
 | 
 | ||||||
|  | @ -642,6 +645,87 @@ private: | ||||||
|     FlyString m_string; |     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 { | class SpreadExpression final : public Expression { | ||||||
| public: | public: | ||||||
|     explicit SpreadExpression(NonnullRefPtr<Expression> target) |     explicit SpreadExpression(NonnullRefPtr<Expression> target) | ||||||
|  | @ -836,10 +920,11 @@ public: | ||||||
|         Spread, |         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_key(move(key)) | ||||||
|         , m_value(move(value)) |         , m_value(move(value)) | ||||||
|         , m_property_type(property_type) |         , m_property_type(property_type) | ||||||
|  |         , m_is_method(is_method) | ||||||
|     { |     { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -851,6 +936,7 @@ public: | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     Type type() const { return m_property_type; } |     Type type() const { return m_property_type; } | ||||||
|  |     bool is_method() const { return m_is_method; } | ||||||
| 
 | 
 | ||||||
|     virtual void dump(int indent) const override; |     virtual void dump(int indent) const override; | ||||||
|     virtual Value execute(Interpreter&, GlobalObject&) const override; |     virtual Value execute(Interpreter&, GlobalObject&) const override; | ||||||
|  | @ -861,6 +947,7 @@ private: | ||||||
|     NonnullRefPtr<Expression> m_key; |     NonnullRefPtr<Expression> m_key; | ||||||
|     RefPtr<Expression> m_value; |     RefPtr<Expression> m_value; | ||||||
|     Type m_property_type; |     Type m_property_type; | ||||||
|  |     bool m_is_method { false }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class ObjectExpression : public Expression { | class ObjectExpression : public Expression { | ||||||
|  |  | ||||||
|  | @ -59,7 +59,10 @@ Value Interpreter::run(GlobalObject& global_object, const Statement& statement, | ||||||
|             CallFrame global_call_frame; |             CallFrame global_call_frame; | ||||||
|             global_call_frame.this_value = &global_object; |             global_call_frame.this_value = &global_object; | ||||||
|             global_call_frame.function_name = "(global execution context)"; |             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)); |             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()) |     if (arguments.has_value()) | ||||||
|         call_frame.arguments.append(arguments.value().values()); |         call_frame.arguments.append(arguments.value().values()); | ||||||
|     call_frame.environment = function.create_environment(); |     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); |     auto result = function.call(*this); | ||||||
|     pop_call_frame(); |     pop_call_frame(); | ||||||
|     return result; |     return result; | ||||||
|  | @ -235,11 +242,19 @@ Value Interpreter::construct(Function& function, Function& new_target, Optional< | ||||||
| { | { | ||||||
|     auto& call_frame = push_call_frame(); |     auto& call_frame = push_call_frame(); | ||||||
|     call_frame.function_name = function.name(); |     call_frame.function_name = function.name(); | ||||||
|  |     call_frame.arguments = function.bound_arguments(); | ||||||
|     if (arguments.has_value()) |     if (arguments.has_value()) | ||||||
|         call_frame.arguments = arguments.value().values(); |         call_frame.arguments.append(arguments.value().values()); | ||||||
|     call_frame.environment = function.create_environment(); |     call_frame.environment = function.create_environment(); | ||||||
| 
 | 
 | ||||||
|     auto* new_object = Object::create_empty(*this, global_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"); |         auto prototype = new_target.get("prototype"); | ||||||
|         if (exception()) |         if (exception()) | ||||||
|             return {}; |             return {}; | ||||||
|  | @ -248,16 +263,38 @@ Value Interpreter::construct(Function& function, Function& new_target, Optional< | ||||||
|             if (exception()) |             if (exception()) | ||||||
|                 return {}; |                 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); |     auto result = function.construct(*this); | ||||||
| 
 | 
 | ||||||
|  |     this_value = current_environment()->get_this_binding(); | ||||||
|     pop_call_frame(); |     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()) |     if (exception()) | ||||||
|         return {}; |         return {}; | ||||||
|  | 
 | ||||||
|     if (result.is_object()) |     if (result.is_object()) | ||||||
|         return result; |         return result; | ||||||
|     return new_object; | 
 | ||||||
|  |     return this_value; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Value Interpreter::throw_exception(Exception* exception) | Value Interpreter::throw_exception(Exception* exception) | ||||||
|  | @ -301,4 +338,24 @@ String Interpreter::join_arguments() const | ||||||
|     return joined_arguments.build(); |     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; |     String join_arguments() const; | ||||||
| 
 | 
 | ||||||
|  |     Value resolve_this_binding() const; | ||||||
|  |     const LexicalEnvironment* get_this_environment() const; | ||||||
|  |     Value get_new_target() const; | ||||||
|  | 
 | ||||||
| private: | private: | ||||||
|     Interpreter(); |     Interpreter(); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -244,6 +244,8 @@ NonnullRefPtr<Statement> Parser::parse_statement() | ||||||
| { | { | ||||||
|     auto statement = [this]() -> NonnullRefPtr<Statement> { |     auto statement = [this]() -> NonnullRefPtr<Statement> { | ||||||
|     switch (m_parser_state.m_current_token.type()) { |     switch (m_parser_state.m_current_token.type()) { | ||||||
|  |     case TokenType::Class: | ||||||
|  |       return parse_class_declaration(); | ||||||
|     case TokenType::Function: { |     case TokenType::Function: { | ||||||
|         auto declaration = parse_function_node<FunctionDeclaration>(); |         auto declaration = parse_function_node<FunctionDeclaration>(); | ||||||
|         m_parser_state.m_function_scopes.last().append(declaration); |         m_parser_state.m_function_scopes.last().append(declaration); | ||||||
|  | @ -421,6 +423,136 @@ RefPtr<Statement> Parser::try_parse_labelled_statement() | ||||||
|     return 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() | NonnullRefPtr<Expression> Parser::parse_primary_expression() | ||||||
| { | { | ||||||
|     if (match_unary_prefixed_expression()) |     if (match_unary_prefixed_expression()) | ||||||
|  | @ -442,6 +574,13 @@ NonnullRefPtr<Expression> Parser::parse_primary_expression() | ||||||
|     case TokenType::This: |     case TokenType::This: | ||||||
|         consume(); |         consume(); | ||||||
|         return create_ast_node<ThisExpression>(); |         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: { |     case TokenType::Identifier: { | ||||||
|         auto arrow_function_result = try_parse_arrow_function_expression(false); |         auto arrow_function_result = try_parse_arrow_function_expression(false); | ||||||
|         if (!arrow_function_result.is_null()) { |         if (!arrow_function_result.is_null()) { | ||||||
|  | @ -537,23 +676,8 @@ NonnullRefPtr<Expression> Parser::parse_unary_prefixed_expression() | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| NonnullRefPtr<ObjectExpression> Parser::parse_object_expression() | NonnullRefPtr<Expression> Parser::parse_property_key() | ||||||
| { | { | ||||||
|     consume(TokenType::CurlyOpen); |  | ||||||
| 
 |  | ||||||
|     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)) { |     if (match(TokenType::StringLiteral)) { | ||||||
|         return parse_string_literal(consume()); |         return parse_string_literal(consume()); | ||||||
|     } else if (match(TokenType::NumericLiteral)) { |     } else if (match(TokenType::NumericLiteral)) { | ||||||
|  | @ -571,7 +695,14 @@ NonnullRefPtr<ObjectExpression> Parser::parse_object_expression() | ||||||
|             expected("IdentifierName"); |             expected("IdentifierName"); | ||||||
|         return create_ast_node<StringLiteral>(consume().value()); |         return create_ast_node<StringLiteral>(consume().value()); | ||||||
|     } |     } | ||||||
|     }; | } | ||||||
|  | 
 | ||||||
|  | NonnullRefPtr<ObjectExpression> Parser::parse_object_expression() | ||||||
|  | { | ||||||
|  |     consume(TokenType::CurlyOpen); | ||||||
|  | 
 | ||||||
|  |     NonnullRefPtrVector<ObjectProperty> properties; | ||||||
|  |     ObjectProperty::Type property_type; | ||||||
| 
 | 
 | ||||||
|     auto skip_to_next_property = [&] { |     auto skip_to_next_property = [&] { | ||||||
|         while (!done() && !match(TokenType::Comma) && !match(TokenType::CurlyOpen)) |         while (!done() && !match(TokenType::Comma) && !match(TokenType::CurlyOpen)) | ||||||
|  | @ -586,7 +717,7 @@ NonnullRefPtr<ObjectExpression> Parser::parse_object_expression() | ||||||
|         if (match(TokenType::TripleDot)) { |         if (match(TokenType::TripleDot)) { | ||||||
|             consume(); |             consume(); | ||||||
|             property_name = parse_expression(4); |             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)) |             if (!match(TokenType::Comma)) | ||||||
|                 break; |                 break; | ||||||
|             consume(TokenType::Comma); |             consume(TokenType::Comma); | ||||||
|  | @ -622,7 +753,7 @@ NonnullRefPtr<ObjectExpression> Parser::parse_object_expression() | ||||||
| 
 | 
 | ||||||
|         if (match(TokenType::ParenOpen)) { |         if (match(TokenType::ParenOpen)) { | ||||||
|             ASSERT(property_name); |             ASSERT(property_name); | ||||||
|             auto function = parse_function_node<FunctionExpression>(false); |             auto function = parse_function_node<FunctionExpression>(false, true); | ||||||
|             auto arg_count = function->parameters().size(); |             auto arg_count = function->parameters().size(); | ||||||
| 
 | 
 | ||||||
|             if (property_type == ObjectProperty::Type::Getter && arg_count != 0) { |             if (property_type == ObjectProperty::Type::Getter && arg_count != 0) { | ||||||
|  | @ -642,7 +773,7 @@ NonnullRefPtr<ObjectExpression> Parser::parse_object_expression() | ||||||
|                 continue; |                 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)) { |         } else if (match(TokenType::Colon)) { | ||||||
|             if (!property_name) { |             if (!property_name) { | ||||||
|                 syntax_error("Expected a property name"); |                 syntax_error("Expected a property name"); | ||||||
|  | @ -650,9 +781,9 @@ NonnullRefPtr<ObjectExpression> Parser::parse_object_expression() | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
|             consume(); |             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) { |         } 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 { |         } else { | ||||||
|             syntax_error("Expected a property"); |             syntax_error("Expected a property"); | ||||||
|             skip_to_next_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) | 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); |     consume(TokenType::ParenOpen); | ||||||
| 
 | 
 | ||||||
|     Vector<CallExpression::Argument> arguments; |     Vector<CallExpression::Argument> arguments; | ||||||
|  | @ -1085,8 +1219,11 @@ NonnullRefPtr<BlockStatement> Parser::parse_block_statement() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| template<typename FunctionNodeType> | 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); |     ScopePusher scope(*this, ScopePusher::Var | ScopePusher::Function); | ||||||
| 
 | 
 | ||||||
|     if (check_for_function_and_name) |     if (check_for_function_and_name) | ||||||
|  | @ -1465,6 +1602,7 @@ bool Parser::match_expression() const | ||||||
|         || type == TokenType::ParenOpen |         || type == TokenType::ParenOpen | ||||||
|         || type == TokenType::Function |         || type == TokenType::Function | ||||||
|         || type == TokenType::This |         || type == TokenType::This | ||||||
|  |         || type == TokenType::Super | ||||||
|         || type == TokenType::RegexLiteral |         || type == TokenType::RegexLiteral | ||||||
|         || match_unary_prefixed_expression(); |         || match_unary_prefixed_expression(); | ||||||
| } | } | ||||||
|  | @ -1563,6 +1701,16 @@ bool Parser::match_identifier_name() const | ||||||
|     return m_parser_state.m_current_token.is_identifier_name(); |     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 | bool Parser::done() const | ||||||
| { | { | ||||||
|     return match(TokenType::Eof); |     return match(TokenType::Eof); | ||||||
|  |  | ||||||
|  | @ -46,7 +46,7 @@ public: | ||||||
|     NonnullRefPtr<Program> parse_program(); |     NonnullRefPtr<Program> parse_program(); | ||||||
| 
 | 
 | ||||||
|     template<typename FunctionNodeType> |     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<Statement> parse_statement(); | ||||||
|     NonnullRefPtr<BlockStatement> parse_block_statement(); |     NonnullRefPtr<BlockStatement> parse_block_statement(); | ||||||
|  | @ -80,6 +80,9 @@ public: | ||||||
|     NonnullRefPtr<NewExpression> parse_new_expression(); |     NonnullRefPtr<NewExpression> parse_new_expression(); | ||||||
|     RefPtr<FunctionExpression> try_parse_arrow_function_expression(bool expect_parens); |     RefPtr<FunctionExpression> try_parse_arrow_function_expression(bool expect_parens); | ||||||
|     RefPtr<Statement> try_parse_labelled_statement(); |     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 { |     struct Error { | ||||||
|         String message; |         String message; | ||||||
|  | @ -126,6 +129,7 @@ private: | ||||||
|     bool match_statement() const; |     bool match_statement() const; | ||||||
|     bool match_variable_declaration() const; |     bool match_variable_declaration() const; | ||||||
|     bool match_identifier_name() const; |     bool match_identifier_name() const; | ||||||
|  |     bool match_property_key() const; | ||||||
|     bool match(TokenType type) const; |     bool match(TokenType type) const; | ||||||
|     bool done() const; |     bool done() const; | ||||||
|     void expected(const char* what); |     void expected(const char* what); | ||||||
|  | @ -151,6 +155,8 @@ private: | ||||||
|         Vector<NonnullRefPtrVector<FunctionDeclaration>> m_function_scopes; |         Vector<NonnullRefPtrVector<FunctionDeclaration>> m_function_scopes; | ||||||
|         UseStrictDirectiveState m_use_strict_directive { UseStrictDirectiveState::None }; |         UseStrictDirectiveState m_use_strict_directive { UseStrictDirectiveState::None }; | ||||||
|         bool m_strict_mode { false }; |         bool m_strict_mode { false }; | ||||||
|  |         bool m_allow_super_property_lookup { false }; | ||||||
|  |         bool m_allow_super_constructor_call { false }; | ||||||
| 
 | 
 | ||||||
|         explicit ParserState(Lexer); |         explicit ParserState(Lexer); | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|  | @ -74,7 +74,7 @@ Value BigIntConstructor::call(Interpreter& interpreter) | ||||||
| 
 | 
 | ||||||
| Value BigIntConstructor::construct(Interpreter& interpreter) | Value BigIntConstructor::construct(Interpreter& interpreter) | ||||||
| { | { | ||||||
|     interpreter.throw_exception<TypeError>(ErrorType::NotACtor, "BigInt"); |     interpreter.throw_exception<TypeError>(ErrorType::NotAConstructor, "BigInt"); | ||||||
|     return {}; |     return {}; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -36,6 +36,7 @@ | ||||||
|     M(BigIntBadOperatorOtherType, "Cannot use %s operator with BigInt and other type")                 \ |     M(BigIntBadOperatorOtherType, "Cannot use %s operator with BigInt and other type")                 \ | ||||||
|     M(BigIntIntArgument, "BigInt argument must be an integer")                                         \ |     M(BigIntIntArgument, "BigInt argument must be an integer")                                         \ | ||||||
|     M(BigIntInvalidValue, "Invalid value for BigInt: %s")                                              \ |     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(Convert, "Cannot convert %s to %s")                                                              \ | ||||||
|     M(ConvertUndefinedToObject, "Cannot convert undefined to object")                                  \ |     M(ConvertUndefinedToObject, "Cannot convert undefined to object")                                  \ | ||||||
|     M(DescChangeNonConfigurable, "Cannot change attributes of non-configurable property '%s'")         \ |     M(DescChangeNonConfigurable, "Cannot change attributes of non-configurable property '%s'")         \ | ||||||
|  | @ -51,7 +52,7 @@ | ||||||
|     M(JsonCircular, "Cannot stringify circular object")                                                \ |     M(JsonCircular, "Cannot stringify circular object")                                                \ | ||||||
|     M(JsonMalformed, "Malformed JSON string")                                                          \ |     M(JsonMalformed, "Malformed JSON string")                                                          \ | ||||||
|     M(NotA, "Not a %s object")                                                                         \ |     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(NotAFunction, "%s is not a function")                                                            \ | ||||||
|     M(NotAFunctionNoParam, "Not a function")                                                           \ |     M(NotAFunctionNoParam, "Not a function")                                                           \ | ||||||
|     M(NotAn, "Not an %s object")                                                                       \ |     M(NotAn, "Not an %s object")                                                                       \ | ||||||
|  | @ -63,6 +64,8 @@ | ||||||
|     M(ObjectSetPrototypeOfReturnedFalse, "Object's [[SetPrototypeOf]] method returned false")          \ |     M(ObjectSetPrototypeOfReturnedFalse, "Object's [[SetPrototypeOf]] method returned false")          \ | ||||||
|     M(ObjectSetPrototypeOfTwoArgs, "Object.setPrototypeOf requires at least two arguments")            \ |     M(ObjectSetPrototypeOfTwoArgs, "Object.setPrototypeOf requires at least two arguments")            \ | ||||||
|     M(ObjectPreventExtensionsReturnedFalse, "Object's [[PreventExtensions]] method returned false")    \ |     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(ObjectPrototypeWrongType, "Prototype must be an object or null")                                 \ | ||||||
|     M(ProxyCallWithNew, "Proxy must be called with the 'new' operator")                                \ |     M(ProxyCallWithNew, "Proxy must be called with the 'new' operator")                                \ | ||||||
|     M(ProxyConstructorBadType, "Expected %s argument of Proxy constructor to be object, got %s")       \ |     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(ReflectBadDescriptorArgument, "Descriptor argument is not an object")                            \ | ||||||
|     M(StringRawCannotConvert, "Cannot convert property 'raw' to object from %s")                       \ |     M(StringRawCannotConvert, "Cannot convert property 'raw' to object from %s")                       \ | ||||||
|     M(StringRepeatCountMustBe, "repeat count must be a %s number")                                     \ |     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(ToObjectNullOrUndef, "ToObject on null or undefined")                                            \ | ||||||
|     M(UnknownIdentifier, "'%s' is not defined")                                                        \ |     M(UnknownIdentifier, "'%s' is not defined")                                                        \ | ||||||
|     /* LibWeb bindings */                                                                              \ |     /* LibWeb bindings */                                                                              \ | ||||||
|  |  | ||||||
|  | @ -35,6 +35,11 @@ class Function : public Object { | ||||||
|     JS_OBJECT(Function, Object); |     JS_OBJECT(Function, Object); | ||||||
| 
 | 
 | ||||||
| public: | public: | ||||||
|  |     enum class ConstructorKind { | ||||||
|  |         Base, | ||||||
|  |         Derived, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|     virtual ~Function(); |     virtual ~Function(); | ||||||
|     virtual void initialize(Interpreter&, GlobalObject&) override { } |     virtual void initialize(Interpreter&, GlobalObject&) override { } | ||||||
| 
 | 
 | ||||||
|  | @ -49,15 +54,15 @@ public: | ||||||
| 
 | 
 | ||||||
|     BoundFunction* bind(Value bound_this_value, Vector<Value> arguments); |     BoundFunction* bind(Value bound_this_value, Vector<Value> arguments); | ||||||
| 
 | 
 | ||||||
|     Value bound_this() const |     Value bound_this() const { return m_bound_this; } | ||||||
|     { |  | ||||||
|         return m_bound_this; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     const Vector<Value>& bound_arguments() const |     const Vector<Value>& bound_arguments() const { return m_bound_arguments; } | ||||||
|     { | 
 | ||||||
|         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: | protected: | ||||||
|     explicit Function(Object& prototype); |     explicit Function(Object& prototype); | ||||||
|  | @ -67,6 +72,8 @@ private: | ||||||
|     virtual bool is_function() const final { return true; } |     virtual bool is_function() const final { return true; } | ||||||
|     Value m_bound_this; |     Value m_bound_this; | ||||||
|     Vector<Value> m_bound_arguments; |     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. |  * 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/LexicalEnvironment.h> | ||||||
|  | #include <LibJS/Runtime/Value.h> | ||||||
| 
 | 
 | ||||||
| namespace JS { | 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) | LexicalEnvironment::LexicalEnvironment(HashMap<FlyString, Variable> variables, LexicalEnvironment* parent) | ||||||
|     : m_parent(parent) |     : m_parent(parent) | ||||||
|     , m_variables(move(variables)) |     , 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() | LexicalEnvironment::~LexicalEnvironment() | ||||||
| { | { | ||||||
| } | } | ||||||
|  | @ -46,6 +62,10 @@ void LexicalEnvironment::visit_children(Visitor& visitor) | ||||||
| { | { | ||||||
|     Cell::visit_children(visitor); |     Cell::visit_children(visitor); | ||||||
|     visitor.visit(m_parent); |     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) |     for (auto& it : m_variables) | ||||||
|         visitor.visit(it.value.value); |         visitor.visit(it.value.value); | ||||||
| } | } | ||||||
|  | @ -60,4 +80,53 @@ void LexicalEnvironment::set(const FlyString& name, Variable variable) | ||||||
|     m_variables.set(name, 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 { | class LexicalEnvironment final : public Cell { | ||||||
| public: | public: | ||||||
|  |     enum class ThisBindingStatus { | ||||||
|  |         Lexical, | ||||||
|  |         Initialized, | ||||||
|  |         Uninitialized, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     enum class EnvironmentRecordType { | ||||||
|  |         Declarative, | ||||||
|  |         Function, | ||||||
|  |         Global, | ||||||
|  |         Object, | ||||||
|  |         Module, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|     LexicalEnvironment(); |     LexicalEnvironment(); | ||||||
|  |     LexicalEnvironment(EnvironmentRecordType); | ||||||
|     LexicalEnvironment(HashMap<FlyString, Variable> variables, LexicalEnvironment* parent); |     LexicalEnvironment(HashMap<FlyString, Variable> variables, LexicalEnvironment* parent); | ||||||
|  |     LexicalEnvironment(HashMap<FlyString, Variable> variables, LexicalEnvironment* parent, EnvironmentRecordType); | ||||||
|     virtual ~LexicalEnvironment() override; |     virtual ~LexicalEnvironment() override; | ||||||
| 
 | 
 | ||||||
|     LexicalEnvironment* parent() { return m_parent; } |     LexicalEnvironment* parent() const { return m_parent; } | ||||||
| 
 | 
 | ||||||
|     Optional<Variable> get(const FlyString&) const; |     Optional<Variable> get(const FlyString&) const; | ||||||
|     void set(const FlyString&, Variable); |     void set(const FlyString&, Variable); | ||||||
|  | @ -53,12 +69,37 @@ public: | ||||||
| 
 | 
 | ||||||
|     const HashMap<FlyString, Variable>& variables() const { return m_variables; } |     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: | private: | ||||||
|     virtual const char* class_name() const override { return "LexicalEnvironment"; } |     virtual const char* class_name() const override { return "LexicalEnvironment"; } | ||||||
|     virtual void visit_children(Visitor&) override; |     virtual void visit_children(Visitor&) override; | ||||||
| 
 | 
 | ||||||
|     LexicalEnvironment* m_parent { nullptr }; |     LexicalEnvironment* m_parent { nullptr }; | ||||||
|     HashMap<FlyString, Variable> m_variables; |     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 {}; |     return {}; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | LexicalEnvironment* NativeFunction::create_environment() | ||||||
|  | { | ||||||
|  |     return interpreter().heap().allocate<LexicalEnvironment>(global_object(), LexicalEnvironment::EnvironmentRecordType::Function); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -53,7 +53,7 @@ protected: | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     virtual bool is_native_function() const override { return true; } |     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; |     FlyString m_name; | ||||||
|     AK::Function<Value(Interpreter&, GlobalObject&)> m_native_function; |     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(); |             accessor = &existing_property.as_accessor(); | ||||||
|     } |     } | ||||||
|     if (!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); |         bool definition_success = define_property(property_name, accessor, attributes, throw_exceptions); | ||||||
|         if (interpreter().exception()) |         if (interpreter().exception()) | ||||||
|             return {}; |             return {}; | ||||||
|  |  | ||||||
|  | @ -66,8 +66,8 @@ ScriptFunction::ScriptFunction(GlobalObject& global_object, const FlyString& nam | ||||||
| void ScriptFunction::initialize(Interpreter& interpreter, GlobalObject& global_object) | void ScriptFunction::initialize(Interpreter& interpreter, GlobalObject& global_object) | ||||||
| { | { | ||||||
|     Function::initialize(interpreter, global_object); |     Function::initialize(interpreter, global_object); | ||||||
|     if (!is_arrow_function) { |     if (!m_is_arrow_function) { | ||||||
|         Object* prototype = Object::create_empty(interpreter(), interpreter().global_object()); |         Object* prototype = Object::create_empty(interpreter, global_object); | ||||||
|         prototype->define_property("constructor", this, Attribute::Writable | Attribute::Configurable); |         prototype->define_property("constructor", this, Attribute::Writable | Attribute::Configurable); | ||||||
|         define_property("prototype", prototype, 0); |         define_property("prototype", prototype, 0); | ||||||
|     } |     } | ||||||
|  | @ -99,9 +99,11 @@ LexicalEnvironment* ScriptFunction::create_environment() | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     if (variables.is_empty()) | 
 | ||||||
|         return m_parent_environment; |     auto* environment = heap().allocate<LexicalEnvironment>(global_object(), move(variables), m_parent_environment, LexicalEnvironment::EnvironmentRecordType::Function); | ||||||
|     return heap().allocate<LexicalEnvironment>(global_object(), move(variables), m_parent_environment); |     environment->set_home_object(home_object()); | ||||||
|  |     environment->set_current_function(*this); | ||||||
|  |     return environment; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Value ScriptFunction::call(Interpreter& interpreter) | Value ScriptFunction::call(Interpreter& interpreter) | ||||||
|  | @ -134,7 +136,7 @@ Value ScriptFunction::call(Interpreter& interpreter) | ||||||
| Value ScriptFunction::construct(Interpreter& interpreter) | Value ScriptFunction::construct(Interpreter& interpreter) | ||||||
| { | { | ||||||
|     if (m_is_arrow_function) |     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); |     return call(interpreter); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -76,7 +76,7 @@ Value SymbolConstructor::call(Interpreter& interpreter) | ||||||
| 
 | 
 | ||||||
| Value SymbolConstructor::construct(Interpreter& interpreter) | Value SymbolConstructor::construct(Interpreter& interpreter) | ||||||
| { | { | ||||||
|     interpreter.throw_exception<TypeError>(ErrorType::NotACtor, "Symbol"); |     interpreter.throw_exception<TypeError>(ErrorType::NotAConstructor, "Symbol"); | ||||||
|     return {}; |     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(a[2] === 3); | ||||||
|     assert(o4.test === undefined); |     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 })"); |     assertIsSyntaxError("({ get... foo })"); | ||||||
|     assertIsSyntaxError("({ get foo })"); |     assertIsSyntaxError("({ get foo })"); | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Jack Karamanian
						Jack Karamanian