mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 08:58:11 +00:00
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()
This commit is contained in:
parent
a27884e4be
commit
849e2c77e4
11 changed files with 66 additions and 1 deletions
|
@ -31,6 +31,7 @@
|
||||||
#include <LibJS/Interpreter.h>
|
#include <LibJS/Interpreter.h>
|
||||||
#include <LibJS/Runtime/Array.h>
|
#include <LibJS/Runtime/Array.h>
|
||||||
#include <LibJS/Runtime/Error.h>
|
#include <LibJS/Runtime/Error.h>
|
||||||
|
#include <LibJS/Runtime/NativeFunction.h>
|
||||||
#include <LibJS/Runtime/PrimitiveString.h>
|
#include <LibJS/Runtime/PrimitiveString.h>
|
||||||
#include <LibJS/Runtime/ScriptFunction.h>
|
#include <LibJS/Runtime/ScriptFunction.h>
|
||||||
#include <LibJS/Runtime/Value.h>
|
#include <LibJS/Runtime/Value.h>
|
||||||
|
@ -87,6 +88,14 @@ Value CallExpression::execute(Interpreter& interpreter) const
|
||||||
if (interpreter.exception())
|
if (interpreter.exception())
|
||||||
return {};
|
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())
|
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()));
|
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;
|
Object* new_object = nullptr;
|
||||||
|
Value result;
|
||||||
if (is_new_expression()) {
|
if (is_new_expression()) {
|
||||||
new_object = interpreter.heap().allocate<Object>();
|
new_object = interpreter.heap().allocate<Object>();
|
||||||
auto prototype = function->get("prototype");
|
auto prototype = function->get("prototype");
|
||||||
if (prototype.has_value() && prototype.value().is_object())
|
if (prototype.has_value() && prototype.value().is_object())
|
||||||
new_object->set_prototype(prototype.value().as_object());
|
new_object->set_prototype(prototype.value().as_object());
|
||||||
call_frame.this_value = new_object;
|
call_frame.this_value = new_object;
|
||||||
|
result = function->construct(interpreter);
|
||||||
} else {
|
} else {
|
||||||
call_frame.this_value = this_value;
|
call_frame.this_value = this_value;
|
||||||
|
result = function->call(interpreter);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto result = function->call(interpreter);
|
|
||||||
interpreter.pop_call_frame();
|
interpreter.pop_call_frame();
|
||||||
|
|
||||||
if (is_new_expression()) {
|
if (is_new_expression()) {
|
||||||
|
@ -955,4 +966,5 @@ void SwitchCase::dump(int indent) const
|
||||||
statement.dump(indent + 1);
|
statement.dump(indent + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,14 @@ DateConstructor::~DateConstructor()
|
||||||
}
|
}
|
||||||
|
|
||||||
Value DateConstructor::call(Interpreter& interpreter)
|
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
|
// TODO: Support args
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
|
|
|
@ -36,8 +36,10 @@ public:
|
||||||
virtual ~DateConstructor() override;
|
virtual ~DateConstructor() override;
|
||||||
|
|
||||||
virtual Value call(Interpreter&) override;
|
virtual Value call(Interpreter&) override;
|
||||||
|
virtual Value construct(Interpreter&) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
virtual bool has_constructor() const override { return true; }
|
||||||
virtual const char* class_name() const override { return "DateConstructor"; }
|
virtual const char* class_name() const override { return "DateConstructor"; }
|
||||||
|
|
||||||
static Value now(Interpreter&);
|
static Value now(Interpreter&);
|
||||||
|
|
|
@ -36,6 +36,7 @@ public:
|
||||||
virtual ~Function();
|
virtual ~Function();
|
||||||
|
|
||||||
virtual Value call(Interpreter&) = 0;
|
virtual Value call(Interpreter&) = 0;
|
||||||
|
virtual Value construct(Interpreter&) = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Function();
|
Function();
|
||||||
|
|
|
@ -44,4 +44,9 @@ Value NativeFunction::call(Interpreter& interpreter)
|
||||||
return m_native_function(interpreter);
|
return m_native_function(interpreter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Value NativeFunction::construct(Interpreter&)
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,9 @@ public:
|
||||||
virtual ~NativeFunction() override;
|
virtual ~NativeFunction() override;
|
||||||
|
|
||||||
virtual Value call(Interpreter&) override;
|
virtual Value call(Interpreter&) override;
|
||||||
|
virtual Value construct(Interpreter&) override;
|
||||||
|
|
||||||
|
virtual bool has_constructor() const { return false; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
NativeFunction() {}
|
NativeFunction() {}
|
||||||
|
|
|
@ -50,6 +50,11 @@ Value ObjectConstructor::call(Interpreter& interpreter)
|
||||||
return interpreter.heap().allocate<Object>();
|
return interpreter.heap().allocate<Object>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Value ObjectConstructor::construct(Interpreter& interpreter)
|
||||||
|
{
|
||||||
|
return call(interpreter);
|
||||||
|
}
|
||||||
|
|
||||||
Value ObjectConstructor::get_own_property_names(Interpreter& interpreter)
|
Value ObjectConstructor::get_own_property_names(Interpreter& interpreter)
|
||||||
{
|
{
|
||||||
if (interpreter.call_frame().arguments.size() < 1)
|
if (interpreter.call_frame().arguments.size() < 1)
|
||||||
|
|
|
@ -36,8 +36,10 @@ public:
|
||||||
virtual ~ObjectConstructor() override;
|
virtual ~ObjectConstructor() override;
|
||||||
|
|
||||||
virtual Value call(Interpreter&) override;
|
virtual Value call(Interpreter&) override;
|
||||||
|
virtual Value construct(Interpreter&) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
virtual bool has_constructor() const override { return true; }
|
||||||
virtual const char* class_name() const override { return "ObjectConstructor"; }
|
virtual const char* class_name() const override { return "ObjectConstructor"; }
|
||||||
|
|
||||||
static Value get_own_property_names(Interpreter&);
|
static Value get_own_property_names(Interpreter&);
|
||||||
|
|
|
@ -55,4 +55,9 @@ Value ScriptFunction::call(Interpreter& interpreter)
|
||||||
return interpreter.run(m_body, arguments, ScopeType::Function);
|
return interpreter.run(m_body, arguments, ScopeType::Function);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Value ScriptFunction::construct(Interpreter& interpreter)
|
||||||
|
{
|
||||||
|
return call(interpreter);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ public:
|
||||||
const Vector<FlyString>& parameters() const { return m_parameters; };
|
const Vector<FlyString>& parameters() const { return m_parameters; };
|
||||||
|
|
||||||
virtual Value call(Interpreter&) override;
|
virtual Value call(Interpreter&) override;
|
||||||
|
virtual Value construct(Interpreter&) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
virtual bool is_script_function() const final { return true; }
|
virtual bool is_script_function() const final { return true; }
|
||||||
|
|
|
@ -25,6 +25,27 @@ try {
|
||||||
assert(e.message === "undefined is not a function");
|
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");
|
console.log("PASS");
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
console.log("FAIL: " + e);
|
console.log("FAIL: " + e);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue