diff --git a/Userland/Libraries/LibJS/Runtime/BoundFunction.cpp b/Userland/Libraries/LibJS/Runtime/BoundFunction.cpp index dae5079bab..49c1291b9e 100644 --- a/Userland/Libraries/LibJS/Runtime/BoundFunction.cpp +++ b/Userland/Libraries/LibJS/Runtime/BoundFunction.cpp @@ -11,23 +11,37 @@ namespace JS { -BoundFunction::BoundFunction(GlobalObject& global_object, FunctionObject& bound_target_function, Value bound_this, Vector bound_arguments, i32 length) - : FunctionObject(*global_object.function_prototype()) +// 10.4.1.3 BoundFunctionCreate ( targetFunction, boundThis, boundArgs ), https://tc39.es/ecma262/multipage/ordinary-and-exotic-objects-behaviours.html#sec-boundfunctioncreate +ThrowCompletionOr BoundFunction::create(GlobalObject& global_object, FunctionObject& target_function, Value bound_this, Vector 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(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 bound_arguments, Object* prototype) + : FunctionObject(global_object, prototype) , m_bound_target_function(&bound_target_function) , m_bound_this(bound_this) , m_bound_arguments(move(bound_arguments)) + // FIXME: Non-standard and redundant, remove. , 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() { } diff --git a/Userland/Libraries/LibJS/Runtime/BoundFunction.h b/Userland/Libraries/LibJS/Runtime/BoundFunction.h index 5afb692e16..3015ba76e9 100644 --- a/Userland/Libraries/LibJS/Runtime/BoundFunction.h +++ b/Userland/Libraries/LibJS/Runtime/BoundFunction.h @@ -6,6 +6,7 @@ #pragma once +#include #include namespace JS { @@ -14,8 +15,9 @@ class BoundFunction final : public FunctionObject { JS_OBJECT(BoundFunction, FunctionObject); public: - BoundFunction(GlobalObject&, FunctionObject& target_function, Value bound_this, Vector bound_arguments, i32 length); - virtual void initialize(GlobalObject&) override; + static ThrowCompletionOr create(GlobalObject&, FunctionObject& target_function, Value bound_this, Vector bound_arguments); + + BoundFunction(GlobalObject&, FunctionObject& target_function, Value bound_this, Vector bound_arguments, Object* prototype); virtual ~BoundFunction(); virtual ThrowCompletionOr internal_call(Value this_argument, MarkedVector arguments_list) override; @@ -37,7 +39,6 @@ private: Vector m_bound_arguments; // [[BoundArguments]] FlyString m_name; - i32 m_length { 0 }; }; } diff --git a/Userland/Libraries/LibJS/Runtime/FunctionPrototype.cpp b/Userland/Libraries/LibJS/Runtime/FunctionPrototype.cpp index 2189f324aa..6b355d51c0 100644 --- a/Userland/Libraries/LibJS/Runtime/FunctionPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/FunctionPrototype.cpp @@ -16,6 +16,7 @@ #include #include #include +#include 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 +// 3.1.2.1 Function.prototype.bind ( thisArg, ...args ), https://tc39.es/proposal-shadowrealm/#sec-function.prototype.bind JS_DEFINE_NATIVE_FUNCTION(FunctionPrototype::bind) { - auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); - if (!this_object->is_function()) - return vm.throw_completion(global_object, ErrorType::NotAnObjectOfType, "Function"); - auto& this_function = static_cast(*this_object); - auto bound_this_arg = vm.argument(0); + auto this_argument = vm.argument(0); + + // 1. Let Target be the this value. + auto target_value = vm.this_value(global_object); + + // 2. If IsCallable(Target) is false, throw a TypeError exception. + if (!target_value.is_function()) + return vm.throw_completion(global_object, ErrorType::NotAFunction, target_value.to_string_without_side_effects()); + + auto& target = static_cast(target_value.as_object()); Vector arguments; if (vm.argument_count() > 1) { @@ -72,7 +79,17 @@ JS_DEFINE_NATIVE_FUNCTION(FunctionPrototype::bind) 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 diff --git a/Userland/Libraries/LibJS/Tests/builtins/Function/Function.prototype.bind.js b/Userland/Libraries/LibJS/Tests/builtins/Function/Function.prototype.bind.js index 0ae42710f9..b121f6a888 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Function/Function.prototype.bind.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Function/Function.prototype.bind.js @@ -33,6 +33,20 @@ describe("basic behavior", () => { ) ).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", () => { @@ -144,6 +158,6 @@ describe("errors", () => { test("does not accept non-function values", () => { expect(() => { Function.prototype.bind.call("foo"); - }).toThrowWithMessage(TypeError, "Not an object of type Function"); + }).toThrowWithMessage(TypeError, "foo is not a function"); }); });