Explorar o código

LibJS: Implement constructor/non-constructor function calls

This adds Function::construct() for constructor function calls via `new`
keyword. NativeFunction doesn't have constructor behaviour by default,
ScriptFunction simply calls call() in construct()
Linus Groh %!s(int64=5) %!d(string=hai) anos
pai
achega
849e2c77e4

+ 13 - 1
Libraries/LibJS/AST.cpp

@@ -31,6 +31,7 @@
 #include <LibJS/Interpreter.h>
 #include <LibJS/Runtime/Array.h>
 #include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/NativeFunction.h>
 #include <LibJS/Runtime/PrimitiveString.h>
 #include <LibJS/Runtime/ScriptFunction.h>
 #include <LibJS/Runtime/Value.h>
@@ -87,6 +88,14 @@ Value CallExpression::execute(Interpreter& interpreter) const
     if (interpreter.exception())
         return {};
 
+    if (is_new_expression()) {
+        if (!callee.is_object()
+            || !callee.as_object()->is_function()
+            || (callee.as_object()->is_native_function()
+                && !static_cast<NativeFunction*>(callee.as_object())->has_constructor()))
+            return interpreter.throw_exception<Error>("TypeError", String::format("%s is not a constructor", callee.to_string().characters()));
+    }
+
     if (!callee.is_object() || !callee.as_object()->is_function())
         return interpreter.throw_exception<Error>("TypeError", String::format("%s is not a function", callee.to_string().characters()));
 
@@ -103,17 +112,19 @@ Value CallExpression::execute(Interpreter& interpreter) const
     }
 
     Object* new_object = nullptr;
+    Value result;
     if (is_new_expression()) {
         new_object = interpreter.heap().allocate<Object>();
         auto prototype = function->get("prototype");
         if (prototype.has_value() && prototype.value().is_object())
             new_object->set_prototype(prototype.value().as_object());
         call_frame.this_value = new_object;
+        result = function->construct(interpreter);
     } else {
         call_frame.this_value = this_value;
+        result = function->call(interpreter);
     }
 
-    auto result = function->call(interpreter);
     interpreter.pop_call_frame();
 
     if (is_new_expression()) {
@@ -955,4 +966,5 @@ void SwitchCase::dump(int indent) const
         statement.dump(indent + 1);
     }
 }
+
 }

+ 8 - 0
Libraries/LibJS/Runtime/DateConstructor.cpp

@@ -46,6 +46,14 @@ DateConstructor::~DateConstructor()
 }
 
 Value DateConstructor::call(Interpreter& interpreter)
+{
+    auto* date = static_cast<Date*>(construct(interpreter).as_object());
+    if (!date)
+        return {};
+    return js_string(interpreter.heap(), date->string());
+}
+
+Value DateConstructor::construct(Interpreter& interpreter)
 {
     // TODO: Support args
     struct timeval tv;

+ 2 - 0
Libraries/LibJS/Runtime/DateConstructor.h

@@ -36,8 +36,10 @@ public:
     virtual ~DateConstructor() override;
 
     virtual Value call(Interpreter&) override;
+    virtual Value construct(Interpreter&) override;
 
 private:
+    virtual bool has_constructor() const override { return true; }
     virtual const char* class_name() const override { return "DateConstructor"; }
 
     static Value now(Interpreter&);

+ 1 - 0
Libraries/LibJS/Runtime/Function.h

@@ -36,6 +36,7 @@ public:
     virtual ~Function();
 
     virtual Value call(Interpreter&) = 0;
+    virtual Value construct(Interpreter&) = 0;
 
 protected:
     Function();

+ 5 - 0
Libraries/LibJS/Runtime/NativeFunction.cpp

@@ -44,4 +44,9 @@ Value NativeFunction::call(Interpreter& interpreter)
     return m_native_function(interpreter);
 }
 
+Value NativeFunction::construct(Interpreter&)
+{
+    return {};
+}
+
 }

+ 3 - 0
Libraries/LibJS/Runtime/NativeFunction.h

@@ -37,6 +37,9 @@ public:
     virtual ~NativeFunction() override;
 
     virtual Value call(Interpreter&) override;
+    virtual Value construct(Interpreter&) override;
+
+    virtual bool has_constructor() const { return false; }
 
 protected:
     NativeFunction() {}

+ 5 - 0
Libraries/LibJS/Runtime/ObjectConstructor.cpp

@@ -50,6 +50,11 @@ Value ObjectConstructor::call(Interpreter& interpreter)
     return interpreter.heap().allocate<Object>();
 }
 
+Value ObjectConstructor::construct(Interpreter& interpreter)
+{
+    return call(interpreter);
+}
+
 Value ObjectConstructor::get_own_property_names(Interpreter& interpreter)
 {
     if (interpreter.call_frame().arguments.size() < 1)

+ 2 - 0
Libraries/LibJS/Runtime/ObjectConstructor.h

@@ -36,8 +36,10 @@ public:
     virtual ~ObjectConstructor() override;
 
     virtual Value call(Interpreter&) override;
+    virtual Value construct(Interpreter&) override;
 
 private:
+    virtual bool has_constructor() const override { return true; }
     virtual const char* class_name() const override { return "ObjectConstructor"; }
 
     static Value get_own_property_names(Interpreter&);

+ 5 - 0
Libraries/LibJS/Runtime/ScriptFunction.cpp

@@ -55,4 +55,9 @@ Value ScriptFunction::call(Interpreter& interpreter)
     return interpreter.run(m_body, arguments, ScopeType::Function);
 }
 
+Value ScriptFunction::construct(Interpreter& interpreter)
+{
+    return call(interpreter);
+}
+
 }

+ 1 - 0
Libraries/LibJS/Runtime/ScriptFunction.h

@@ -39,6 +39,7 @@ public:
     const Vector<FlyString>& parameters() const { return m_parameters; };
 
     virtual Value call(Interpreter&) override;
+    virtual Value construct(Interpreter&) override;
 
 private:
     virtual bool is_script_function() const final { return true; }

+ 21 - 0
Libraries/LibJS/Tests/function-TypeError.js

@@ -25,6 +25,27 @@ try {
         assert(e.message === "undefined is not a function");
     }
 
+    try {
+        Math();
+    } catch(e) {
+        assert(e.name === "TypeError");
+        assert(e.message === "[object MathObject] is not a function");
+    }
+
+    try {
+        new Math();
+    } catch(e) {
+        assert(e.name === "TypeError");
+        assert(e.message === "[object MathObject] is not a constructor");
+    }
+
+    try {
+        new isNaN();
+    } catch(e) {
+        assert(e.name === "TypeError");
+        assert(e.message === "[object NativeFunction] is not a constructor");
+    }
+
     console.log("PASS");
 } catch(e) {
     console.log("FAIL: " + e);