mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 03:27:45 +00:00
LibJS: Implement Function.prototype.bind() according to the spec :^)
This commit is contained in:
parent
87b9fa2636
commit
898ad7c682
4 changed files with 66 additions and 20 deletions
|
@ -11,23 +11,37 @@
|
||||||
|
|
||||||
namespace JS {
|
namespace JS {
|
||||||
|
|
||||||
BoundFunction::BoundFunction(GlobalObject& global_object, FunctionObject& bound_target_function, Value bound_this, Vector<Value> bound_arguments, i32 length)
|
// 10.4.1.3 BoundFunctionCreate ( targetFunction, boundThis, boundArgs ), https://tc39.es/ecma262/multipage/ordinary-and-exotic-objects-behaviours.html#sec-boundfunctioncreate
|
||||||
: FunctionObject(*global_object.function_prototype())
|
ThrowCompletionOr<BoundFunction*> BoundFunction::create(GlobalObject& global_object, FunctionObject& target_function, Value bound_this, Vector<Value> bound_arguments)
|
||||||
|
{
|
||||||
|
// 1. Let proto be ? targetFunction.[[GetPrototypeOf]]().
|
||||||
|
auto* prototype = TRY(target_function.internal_get_prototype_of());
|
||||||
|
|
||||||
|
// 2. Let internalSlotsList be the list-concatenation of « [[Prototype]], [[Extensible]] » and the internal slots listed in Table 33.
|
||||||
|
// 3. Let obj be ! MakeBasicObject(internalSlotsList).
|
||||||
|
// 4. Set obj.[[Prototype]] to proto.
|
||||||
|
// 5. Set obj.[[Call]] as described in 10.4.1.1.
|
||||||
|
// 6. If IsConstructor(targetFunction) is true, then
|
||||||
|
// a. Set obj.[[Construct]] as described in 10.4.1.2.
|
||||||
|
// 7. Set obj.[[BoundTargetFunction]] to targetFunction.
|
||||||
|
// 8. Set obj.[[BoundThis]] to boundThis.
|
||||||
|
// 9. Set obj.[[BoundArguments]] to boundArgs.
|
||||||
|
auto* object = global_object.heap().allocate<BoundFunction>(global_object, global_object, target_function, bound_this, move(bound_arguments), prototype);
|
||||||
|
|
||||||
|
// 10. Return obj.
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
BoundFunction::BoundFunction(GlobalObject& global_object, FunctionObject& bound_target_function, Value bound_this, Vector<Value> bound_arguments, Object* prototype)
|
||||||
|
: FunctionObject(global_object, prototype)
|
||||||
, m_bound_target_function(&bound_target_function)
|
, m_bound_target_function(&bound_target_function)
|
||||||
, m_bound_this(bound_this)
|
, m_bound_this(bound_this)
|
||||||
, m_bound_arguments(move(bound_arguments))
|
, m_bound_arguments(move(bound_arguments))
|
||||||
|
// FIXME: Non-standard and redundant, remove.
|
||||||
, m_name(String::formatted("bound {}", bound_target_function.name()))
|
, m_name(String::formatted("bound {}", bound_target_function.name()))
|
||||||
, m_length(length)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void BoundFunction::initialize(GlobalObject& global_object)
|
|
||||||
{
|
|
||||||
auto& vm = this->vm();
|
|
||||||
Base::initialize(global_object);
|
|
||||||
define_direct_property(vm.names.length, Value(m_length), Attribute::Configurable);
|
|
||||||
}
|
|
||||||
|
|
||||||
BoundFunction::~BoundFunction()
|
BoundFunction::~BoundFunction()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <LibJS/Runtime/Completion.h>
|
||||||
#include <LibJS/Runtime/FunctionObject.h>
|
#include <LibJS/Runtime/FunctionObject.h>
|
||||||
|
|
||||||
namespace JS {
|
namespace JS {
|
||||||
|
@ -14,8 +15,9 @@ class BoundFunction final : public FunctionObject {
|
||||||
JS_OBJECT(BoundFunction, FunctionObject);
|
JS_OBJECT(BoundFunction, FunctionObject);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
BoundFunction(GlobalObject&, FunctionObject& target_function, Value bound_this, Vector<Value> bound_arguments, i32 length);
|
static ThrowCompletionOr<BoundFunction*> create(GlobalObject&, FunctionObject& target_function, Value bound_this, Vector<Value> bound_arguments);
|
||||||
virtual void initialize(GlobalObject&) override;
|
|
||||||
|
BoundFunction(GlobalObject&, FunctionObject& target_function, Value bound_this, Vector<Value> bound_arguments, Object* prototype);
|
||||||
virtual ~BoundFunction();
|
virtual ~BoundFunction();
|
||||||
|
|
||||||
virtual ThrowCompletionOr<Value> internal_call(Value this_argument, MarkedVector<Value> arguments_list) override;
|
virtual ThrowCompletionOr<Value> internal_call(Value this_argument, MarkedVector<Value> arguments_list) override;
|
||||||
|
@ -37,7 +39,6 @@ private:
|
||||||
Vector<Value> m_bound_arguments; // [[BoundArguments]]
|
Vector<Value> m_bound_arguments; // [[BoundArguments]]
|
||||||
|
|
||||||
FlyString m_name;
|
FlyString m_name;
|
||||||
i32 m_length { 0 };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include <LibJS/Runtime/FunctionPrototype.h>
|
#include <LibJS/Runtime/FunctionPrototype.h>
|
||||||
#include <LibJS/Runtime/GlobalObject.h>
|
#include <LibJS/Runtime/GlobalObject.h>
|
||||||
#include <LibJS/Runtime/NativeFunction.h>
|
#include <LibJS/Runtime/NativeFunction.h>
|
||||||
|
#include <LibJS/Runtime/ShadowRealm.h>
|
||||||
|
|
||||||
namespace JS {
|
namespace JS {
|
||||||
|
|
||||||
|
@ -58,13 +59,19 @@ JS_DEFINE_NATIVE_FUNCTION(FunctionPrototype::apply)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 20.2.3.2 Function.prototype.bind ( thisArg, ...args ), https://tc39.es/ecma262/#sec-function.prototype.bind
|
// 20.2.3.2 Function.prototype.bind ( thisArg, ...args ), https://tc39.es/ecma262/#sec-function.prototype.bind
|
||||||
|
// 3.1.2.1 Function.prototype.bind ( thisArg, ...args ), https://tc39.es/proposal-shadowrealm/#sec-function.prototype.bind
|
||||||
JS_DEFINE_NATIVE_FUNCTION(FunctionPrototype::bind)
|
JS_DEFINE_NATIVE_FUNCTION(FunctionPrototype::bind)
|
||||||
{
|
{
|
||||||
auto* this_object = TRY(vm.this_value(global_object).to_object(global_object));
|
auto this_argument = vm.argument(0);
|
||||||
if (!this_object->is_function())
|
|
||||||
return vm.throw_completion<TypeError>(global_object, ErrorType::NotAnObjectOfType, "Function");
|
// 1. Let Target be the this value.
|
||||||
auto& this_function = static_cast<FunctionObject&>(*this_object);
|
auto target_value = vm.this_value(global_object);
|
||||||
auto bound_this_arg = vm.argument(0);
|
|
||||||
|
// 2. If IsCallable(Target) is false, throw a TypeError exception.
|
||||||
|
if (!target_value.is_function())
|
||||||
|
return vm.throw_completion<TypeError>(global_object, ErrorType::NotAFunction, target_value.to_string_without_side_effects());
|
||||||
|
|
||||||
|
auto& target = static_cast<FunctionObject&>(target_value.as_object());
|
||||||
|
|
||||||
Vector<Value> arguments;
|
Vector<Value> arguments;
|
||||||
if (vm.argument_count() > 1) {
|
if (vm.argument_count() > 1) {
|
||||||
|
@ -72,7 +79,17 @@ JS_DEFINE_NATIVE_FUNCTION(FunctionPrototype::bind)
|
||||||
arguments.remove(0);
|
arguments.remove(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return TRY(this_function.bind(bound_this_arg, move(arguments)));
|
// 3. Let F be ? BoundFunctionCreate(Target, thisArg, args).
|
||||||
|
auto* function = TRY(BoundFunction::create(global_object, target, this_argument, move(arguments)));
|
||||||
|
|
||||||
|
// 4. Let argCount be the number of elements in args.
|
||||||
|
auto arg_count = vm.argument_count() - 1;
|
||||||
|
|
||||||
|
// 5. Perform ? CopyNameAndLength(F, Target, "bound", argCount).
|
||||||
|
TRY(copy_name_and_length(global_object, *function, target, "bound"sv, arg_count));
|
||||||
|
|
||||||
|
// 6. Return F.
|
||||||
|
return function;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 20.2.3.3 Function.prototype.call ( thisArg, ...args ), https://tc39.es/ecma262/#sec-function.prototype.call
|
// 20.2.3.3 Function.prototype.call ( thisArg, ...args ), https://tc39.es/ecma262/#sec-function.prototype.call
|
||||||
|
|
|
@ -33,6 +33,20 @@ describe("basic behavior", () => {
|
||||||
)
|
)
|
||||||
).toBe(12);
|
).toBe(12);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("name has 'bound' prefix", () => {
|
||||||
|
function foo() {}
|
||||||
|
const boundFoo = foo.bind(123);
|
||||||
|
expect(foo.name).toBe("foo");
|
||||||
|
expect(boundFoo.name).toBe("bound foo");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("prototype is inherited from target function", () => {
|
||||||
|
function foo() {}
|
||||||
|
Object.setPrototypeOf(foo, Array.prototype);
|
||||||
|
const boundFoo = Function.prototype.bind.call(foo, 123);
|
||||||
|
expect(Object.getPrototypeOf(boundFoo)).toBe(Array.prototype);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("bound function arguments", () => {
|
describe("bound function arguments", () => {
|
||||||
|
@ -144,6 +158,6 @@ describe("errors", () => {
|
||||||
test("does not accept non-function values", () => {
|
test("does not accept non-function values", () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
Function.prototype.bind.call("foo");
|
Function.prototype.bind.call("foo");
|
||||||
}).toThrowWithMessage(TypeError, "Not an object of type Function");
|
}).toThrowWithMessage(TypeError, "foo is not a function");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue