// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
-import { ITERATOR, toPrimitive } from "./value.js";
+import { ITERATOR, toLength, toPrimitive } from "./value.js";
export const {
/**
* first argument as the `this` value and the remaining arguments
* passed through.
*
+ * The `length`, `name`, and prototype of the provided function will
+ * be preserved in the new one. A second argument may be used to
+ * override `length` and `name`.
+ */
+ createArrowFunction,
+
+ /**
+ * Returns a new function which calls the provided function with its
+ * first argument as the `this` value and the remaining arguments
+ * passed through.
+ *
+ * The `length`, `name`, and prototype of the provided function will
+ * be preserved in the new one. A second argument may be used to
+ * override `length` and `name`.
+ *
* ※ This is effectively an alias for `Function::call.bind`.
*/
createCallableFunction,
* Returns a constructor which throws whenever it is called but has
* the same `.name` and `.prototype` as the provided value.
*
- * If a second argument is provided, the returned constructor will
- * use that as its prototype; otherwise, it will use the prototype of
- * the provided value.
+ * The `length`, `name`, `prototype`, and prototype of the provided
+ * function will be preserved in the new one. A second argument may
+ * be used to override `length`, `name`, and `prototype`.
*/
createIllegalConstructor,
]);
const {
create: objectCreate,
+ defineProperty: defineOwnProperty,
defineProperties: defineOwnProperties,
getOwnPropertyDescriptor,
getPrototypeOf: getPrototype,
const argumentIterablePrototype = {
[ITERATOR]() {
return {
+ [ITERATOR]() {
+ return this;
+ },
next: callBind(
arrayIteratorNext,
call(arrayIterator, this.args, []),
Symbol.prototype,
"description",
).get;
+ const applyBaseFunction = ($, base, lengthDelta = 0) => {
+ if (base === undefined) {
+ // No base function was provided to apply.
+ return $;
+ } else {
+ // A base function was provided; apply it.
+ const { length, name, prototype } = base;
+ setPrototype($, getPrototype(base));
+ return applyProperties($, {
+ length: +length + lengthDelta,
+ name,
+ prototype,
+ });
+ }
+ };
+ const applyProperties = ($, override) => {
+ if (override === undefined) {
+ // No properties were provided to apply.
+ return $;
+ } else {
+ // Properties were provided; apply them.
+ const { length, name, prototype } = override;
+ if (!isConstructor($) || prototype === undefined) {
+ // The provided function is not a constructor or no prototype
+ // value was provided.
+ //
+ // Do not modify the prototype property of the provided
+ // function.
+ /* do nothing */
+ } else {
+ // The provided function is a constructor and a prototype value
+ // was provided.
+ //
+ // Change the prototype property of the provided function to
+ // match.
+ defineOwnProperty($, "prototype", { value: prototype });
+ }
+ return defineOwnProperties($, {
+ length: {
+ value: toLength(length === undefined ? $.length : length),
+ },
+ name: {
+ value: toFunctionName(
+ name === undefined ? $.name ?? "" : name,
+ ),
+ },
+ });
+ }
+ };
return {
bind: ($, boundThis, boundArgs) =>
{ args: { value: boundArgs } },
),
),
- createCallableFunction: ($, name = undefined) =>
- defineOwnProperties(callBind(functionCall, $), {
- length: { value: $.length + 1 },
- name: {
- value: toFunctionName(
- name === undefined ? $.name ?? "" : name,
- ),
- },
- }),
- createIllegalConstructor: ($, proto = undefined) => {
- const constructor = function () {
- throw new TypeError("Illegal constructor");
- };
- if ($ == null && proto === undefined) {
- // The provided argument is nullish and no explicit prototype
- // was provided.
- //
- // Do not modify the prototype of the generated constructor.
- /* do nothing */
- } else {
- // The provided argument is not nullish or an explicit
- // prototype was provided.
- //
- // Set the prototype of the generated constructor to match.
- setPrototype(
- constructor,
- proto === undefined ? getPrototype($) : proto,
- );
- }
- return defineOwnProperties(constructor, {
- name: { value: $?.name ?? "" },
- prototype: { value: $?.prototype ?? {}, writable: false },
+ createArrowFunction: ($, propertyOverride = undefined) =>
+ applyProperties(
+ applyBaseFunction(
+ (...$s) =>
+ $(...objectCreate(
+ argumentIterablePrototype,
+ { args: { value: $s } },
+ )),
+ $,
+ ),
+ propertyOverride,
+ ),
+ createCallableFunction: ($, propertyOverride = undefined) =>
+ applyProperties(
+ applyBaseFunction(
+ (...$s) => {
+ const iterator = objectCreate(
+ argumentIterablePrototype,
+ { args: { value: $s } },
+ )[ITERATOR]();
+ const { value: thisValue } = iterator.next();
+ return call($, thisValue, [...iterator]);
+ },
+ $,
+ 1,
+ ),
+ propertyOverride,
+ ),
+ createIllegalConstructor: ($, propertyOverride = undefined) => {
+ const constructor = applyProperties(
+ applyBaseFunction(
+ function () {
+ throw new TypeError("Illegal constructor");
+ },
+ $,
+ ),
+ propertyOverride,
+ );
+ return defineOwnProperty(constructor, "prototype", {
+ writable: false,
});
},
toFunctionName: ($, prefix = undefined) => {
*/
export const ordinaryHasInstance = createCallableFunction(
Function.prototype[Symbol.hasInstance],
- "ordinaryHasInstance",
+ { name: "ordinaryHasInstance" },
);
call,
completesNormally,
construct,
+ createArrowFunction,
createCallableFunction,
createIllegalConstructor,
identity,
});
});
+describe("createArrowFunction", () => {
+ it("[[Call]] sets this to undefined", () => {
+ assertStrictEquals(
+ createArrowFunction(
+ function () {
+ return this;
+ },
+ ).call("fail"),
+ undefined,
+ );
+ });
+
+ it("[[Call]] transfers the provided arguments", () => {
+ assertEquals(
+ createArrowFunction(
+ function (...args) {
+ return [this, ...args];
+ },
+ ).call("failure", "etaoin", "shrdlu", "cmfwyp"),
+ [undefined, "etaoin", "shrdlu", "cmfwyp"],
+ );
+ });
+
+ it("[[Call]] correctly sets the length", () => {
+ assertStrictEquals(
+ createArrowFunction(
+ function (_a, _b, _c) {},
+ ).length,
+ 3,
+ );
+ });
+
+ it("[[Call]] correctly sets the name", () => {
+ assertStrictEquals(
+ createArrowFunction(
+ function etaoin() {},
+ ).name,
+ "etaoin",
+ );
+ });
+
+ it("[[Call]] allows the length to be overridden", () => {
+ assertStrictEquals(
+ createArrowFunction(
+ function etaoin() {},
+ { length: 100 },
+ ).length,
+ 100,
+ );
+ });
+
+ it("[[Call]] allows the name to be overridden", () => {
+ assertStrictEquals(
+ createArrowFunction(
+ function etaoin() {},
+ { name: "shrdlu" },
+ ).name,
+ "shrdlu",
+ );
+ });
+
+ it("[[Call]] returns an arrow function", () => {
+ assertThrows(() => new (createArrowFunction(function () {}))());
+ });
+
+ it("[[Call]] returns a function with no prototype property", () => {
+ assert(
+ !("prototype" in
+ createArrowFunction(function () {}, { prototype: {} })),
+ );
+ });
+
+ it("[[Construct]] throws an error", () => {
+ assertThrows(() => new createArrowFunction(function () {}));
+ });
+
+ describe(".length", () => {
+ it("[[Get]] returns the correct length", () => {
+ assertStrictEquals(createArrowFunction.length, 1);
+ });
+ });
+
+ describe(".name", () => {
+ it("[[Get]] returns the correct name", () => {
+ assertStrictEquals(
+ createArrowFunction.name,
+ "createArrowFunction",
+ );
+ });
+ });
+});
+
describe("createCallableFunction", () => {
it("[[Call]] transfers the first argument to this", () => {
assertStrictEquals(
);
});
+ it("[[Call]] allows the length to be overridden", () => {
+ assertStrictEquals(
+ createCallableFunction(
+ function etaoin() {},
+ { length: 100 },
+ ).length,
+ 100,
+ );
+ });
+
it("[[Call]] allows the name to be overridden", () => {
assertStrictEquals(
createCallableFunction(
function etaoin() {},
- "shrdlu",
+ { name: "shrdlu" },
).name,
"shrdlu",
);
});
+ it("[[Call]] returns an arrow function", () => {
+ assertThrows(() => new (createCallableFunction(function () {}))());
+ });
+
+ it("[[Call]] returns a function with no prototype property", () => {
+ assert(
+ !("prototype" in
+ createCallableFunction(function () {}, { prototype: {} })),
+ );
+ });
+
it("[[Construct]] throws an error", () => {
assertThrows(() => new createCallableFunction(function () {}));
});
Object.getPrototypeOf(constructor.prototype),
Object.prototype,
);
- assertEquals(constructor.prototype, {});
- assert(
- !Object.getOwnPropertyDescriptor(constructor, "prototype")
- .writable,
- );
- assertStrictEquals(constructor.name, "");
- });
-
- it("[[Call]] returns a correctly‐formed constructor when provided one argument", () => {
- const constructorPrototype = Object.create(null, {
- name: { value: "etaoin" },
- prototype: { value: {} },
- });
- const constructor = createIllegalConstructor(
- Object.create(constructorPrototype),
+ console.dir(
+ Object.getOwnPropertyDescriptors(constructor.prototype),
);
- assert(isConstructor(constructor));
- assertStrictEquals(
- Object.getPrototypeOf(constructor),
- constructorPrototype,
- );
- assert(Object.hasOwn(constructor, "prototype"));
assertEquals(
constructor.prototype,
- constructorPrototype.prototype,
+ Object.create(Object.prototype, {
+ constructor: {
+ configurable: true,
+ enumerable: false,
+ value: constructor,
+ writable: true,
+ },
+ }),
);
assert(
!Object.getOwnPropertyDescriptor(constructor, "prototype")
.writable,
);
- assertStrictEquals(constructor.name, "etaoin");
+ assertStrictEquals(constructor.name, "");
});
- it("[[Call]] allows the second argument to override the prototype of the constructor", () => {
+ it("[[Call]] returns a correctly‐formed constructor when provided one argument", () => {
const constructorPrototype = Object.create(null, {
name: { value: "etaoin" },
prototype: { value: {} },
});
- const expectedPrototype = Object.create(null, {
- name: { value: "shrdlu" },
- prototype: { value: {} },
- });
const constructor = createIllegalConstructor(
Object.create(constructorPrototype),
- expectedPrototype,
);
assert(isConstructor(constructor));
assertStrictEquals(
Object.getPrototypeOf(constructor),
- expectedPrototype,
+ constructorPrototype,
);
assert(Object.hasOwn(constructor, "prototype"));
assertEquals(