diff --git a/RELEASES.rst b/RELEASES.rst index aebac0f7..629fca83 100644 --- a/RELEASES.rst +++ b/RELEASES.rst @@ -1996,6 +1996,10 @@ Planned for a target function with name "foo", bound function name is "bound foo" (GH-1113) +* Change bound function internal prototype handling to match ES6 requirements; + bound function internal prototype is copied from the target function + instead of always being Function.prototype (GH-1135) + * Add a fastint check for duk_put_number_list() values (GH-1086) * Remove an unintended fastint downgrade check for unary minus executor diff --git a/doc/release-notes-v2-0.rst b/doc/release-notes-v2-0.rst index 9af0531e..3b9f44e7 100644 --- a/doc/release-notes-v2-0.rst +++ b/doc/release-notes-v2-0.rst @@ -1058,6 +1058,10 @@ Other incompatible changes "enumerate" trap has been obsoleted. Key enumerability is also now checked when "ownKeys" trap is used in Object.keys() and for-in. +* Bound function internal prototype is copied from the target function to match + ES6 requirements; in ES5 (and Duktape 1.x) bound function internal prototype + is always set to Function.prototype. + Known issues ============ diff --git a/src-input/duk_bi_function.c b/src-input/duk_bi_function.c index 11d6b557..7fe8e14f 100644 --- a/src-input/duk_bi_function.c +++ b/src-input/duk_bi_function.c @@ -302,6 +302,7 @@ DUK_INTERNAL duk_ret_t duk_bi_function_prototype_call(duk_context *ctx) { * merges argument lists etc here. */ DUK_INTERNAL duk_ret_t duk_bi_function_prototype_bind(duk_context *ctx) { + duk_hthread *thr = (duk_hthread *) ctx; duk_hobject *h_bound; duk_hobject *h_target; duk_idx_t nargs; @@ -349,8 +350,15 @@ DUK_INTERNAL duk_ret_t duk_bi_function_prototype_bind(duk_context *ctx) { /* [ thisArg arg1 ... argN func boundFunc ] */ - /* bound function 'length' property is interesting */ h_target = duk_get_hobject(ctx, -2); + + /* internal prototype must be copied from the target */ + if (h_target != NULL) { + /* For lightfuncs Function.prototype is used and is already in place. */ + DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, h_bound, DUK_HOBJECT_GET_PROTOTYPE(thr->heap, h_target)); + } + + /* bound function 'length' property is interesting */ if (h_target == NULL || /* lightfunc */ DUK_HOBJECT_GET_CLASS_NUMBER(h_target) == DUK_HOBJECT_CLASS_FUNCTION) { /* For lightfuncs, simply read the virtual property. */ diff --git a/tests/ecmascript/test-dev-bound-func-prototype.js b/tests/ecmascript/test-dev-bound-func-prototype.js new file mode 100644 index 00000000..6049d19b --- /dev/null +++ b/tests/ecmascript/test-dev-bound-func-prototype.js @@ -0,0 +1,39 @@ +/* + * Bound function internal prototype is copied from the target in ES6. + * In ES5 it is always Function.prototype. Test for ES6 behavior. + */ + +/*=== +bar +bar +true +quux +bar +false +===*/ + +function test() { + var F = function foo() {}; + var proto = { foo: 'bar' }; + Object.setPrototypeOf(F, proto); + + // Create a bound function; its prototype is copied from the current + // prototype of the target. + var G = Function.prototype.bind.call(F); + print(F.foo); + print(G.foo); + print(Object.getPrototypeOf(G) === proto); + + // Alter target's internal prototype; doesn't affect the bound function. + proto = { foo: 'quux' }; + Object.setPrototypeOf(F, proto); + print(F.foo); + print(G.foo); + print(Object.getPrototypeOf(G) === proto); +} + +try { + test(); +} catch (e) { + print(e.stack || e); +}