// 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 { defineOwnProperty, isObject } from "./object.js";
+export const {
+ /**
+ * Creates a bound function from the provided function using the
+ * provided this value and arguments list.
+ *
+ * ☡ As with call and construct, the arguments must be passed as an
+ * array.
+ */
+ bind,
-/**
- * Creates a bound function from the provided function using the
- * provided this value and arguments list.
- *
- * ☡ As with call and construct, the arguments must be passed as an
- * array.
- */
-export const bind = (() => {
- const callBind = Function.prototype.call.bind(
- Function.prototype.bind,
- );
- const bind = ($, boundThis, boundArgs) =>
- callBind($, boundThis, ...boundArgs);
- return bind;
+ /**
+ * Returns a new function which calls the provided function with its
+ * first argument as the `this` value and the remaining arguments
+ * passed through.
+ *
+ * ※ This is effectively an alias for Function.prototype.call.bind.
+ */
+ makeCallable,
+} = (() => {
+ // ☡ Because these functions are used to initialize module constants,
+ // they can’t depend on imports from elsewhere.
+ const {
+ bind: functionBind,
+ call: functionCall,
+ } = Function.prototype;
+ const callBind = Reflect.apply(functionBind, functionCall, [
+ functionBind,
+ ]);
+ const {
+ create: objectCreate,
+ defineProperty: defineOwnProperty,
+ getPrototypeOf: getPrototype,
+ } = Object;
+ const { iterator: iteratorSymbol } = Symbol;
+ const { [iteratorSymbol]: arrayIterator } = Array.prototype;
+ const {
+ next: arrayIteratorNext,
+ } = getPrototype([][iteratorSymbol]());
+ const argumentIterablePrototype = {
+ [iteratorSymbol]() {
+ return {
+ next: callBind(
+ arrayIteratorNext,
+ call(arrayIterator, this.args, []),
+ ),
+ };
+ },
+ };
+ return {
+ bind: ($, boundThis, boundArgs) =>
+ callBind(
+ $,
+ boundThis,
+ ...objectCreate(
+ argumentIterablePrototype,
+ { args: { value: boundArgs } },
+ ),
+ ),
+ makeCallable: ($) =>
+ defineOwnProperty(
+ callBind(functionCall, $),
+ "length",
+ { value: $.length + 1 },
+ ),
+ };
})();
/**
*/
export const construct = Reflect.construct;
+/**
+ * Returns the provided value.
+ *
+ * ※ This function can be called as a constructor. When used in an
+ * `extends` clause and called via `super`, it will set the value of
+ * `this` to the provided value, enabling it to be extended with
+ * private class features.
+ */
+export const identity = function ($) {
+ return $;
+};
+
+/** Returns whether the provided value is callable. */
+export const isCallable = ($) => typeof $ === "function";
+
/** Returns whether the provided value is a constructor. */
export const isConstructor = ($) => {
- if (!isObject($)) {
- // The provided value is not an object.
+ // The provided value is an object.
+ try {
+ construct(
+ function () {},
+ [],
+ $,
+ ); // will throw if $ is not a constructor
+ return true;
+ } catch {
return false;
- } else {
- // The provided value is an object.
- try {
- construct(
- function () {},
- [],
- $,
- ); // will throw if $ is not a constructor
- return true;
- } catch {
- return false;
- }
}
};
-/**
- * Returns a new function which calls the provided function with its
- * first argument as the `this` value and the remaining arguments
- * passed through.
- *
- * ※ This is effectively an alias for Function.prototype.call.bind.
- */
-export const makeCallable = (() => {
- const functionCall = Function.prototype.call;
- const callable = ($) => defineOwnProperty(
- bind(functionCall, $, []),
- "length",
- { value: $.length + 1 },
- );
- return callable;
-})();
-
/**
* Returns whether the provided object inherits from the prototype of
* the provided function.
*/
export const ordinaryHasInstance = makeCallable(
- Function.prototype[Symbol.hasInstance],
+ Function.prototype[Symbol.hasInstance],
);
--- /dev/null
+// ♓🌟 Piscēs ∷ function.test.js
+// ====================================================================
+//
+// Copyright © 2022 Lady [@ Lady’s Computer].
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// 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 {
+ assert,
+ assertEquals,
+ assertStrictEquals,
+ describe,
+ it,
+} from "./dev-deps.js";
+import {
+ bind,
+ call,
+ construct,
+ identity,
+ isCallable,
+ isConstructor,
+ makeCallable,
+ ordinaryHasInstance,
+} from "./function.js";
+
+describe("bind", () => {
+ it("[[Call]] binds this", () => {
+ assertStrictEquals(
+ bind(
+ function () {
+ return this;
+ },
+ "pass",
+ [],
+ ).call("fail"),
+ "pass",
+ );
+ });
+
+ it("[[Call]] binds arguments", () => {
+ assertEquals(
+ bind(
+ function (...args) {
+ return [this, ...args];
+ },
+ "etaoin",
+ ["shrdlu"],
+ ).call("failure", "cmfwyp"),
+ ["etaoin", "shrdlu", "cmfwyp"],
+ );
+ });
+
+ it("[[Call]] works with any arraylike third argument", () => {
+ assertEquals(
+ bind(
+ function (...args) {
+ return [this, ...args];
+ },
+ "etaoin",
+ {
+ 1: "shrdlu",
+ 3: "failure",
+ length: 3,
+ },
+ ).call("failure", "cmfwyp"),
+ ["etaoin", undefined, "shrdlu", undefined, "cmfwyp"],
+ );
+ });
+});
+
+describe("call", () => {
+ it("[[Call]] calls with the provided this value", () => {
+ assertStrictEquals(
+ call(
+ function () {
+ return this;
+ },
+ "pass",
+ [],
+ ),
+ "pass",
+ );
+ });
+
+ it("[[Call]] calls with the provided arguments", () => {
+ assertEquals(
+ call(
+ function (...args) {
+ return [this, ...args];
+ },
+ "etaoin",
+ ["shrdlu", "cmfwyp"],
+ ),
+ ["etaoin", "shrdlu", "cmfwyp"],
+ );
+ });
+
+ it("[[Call]] works with any arraylike third argument", () => {
+ assertEquals(
+ call(
+ function (...args) {
+ return [this, ...args];
+ },
+ "etaoin",
+ {
+ 1: "shrdlu",
+ 3: "cmfwyp",
+ 4: "failure",
+ length: 4,
+ },
+ ),
+ ["etaoin", undefined, "shrdlu", undefined, "cmfwyp"],
+ );
+ });
+});
+
+describe("construct", () => {
+ it("[[Call]] defaults to the constructor as the target", () => {
+ const constructor = class {};
+ assertStrictEquals(
+ Object.getPrototypeOf(construct(
+ constructor,
+ [],
+ )),
+ constructor.prototype,
+ );
+ });
+
+ it("[[Call]] constructs with the provided new target", () => {
+ const target = function () {};
+ assertStrictEquals(
+ construct(
+ function () {
+ return new.target;
+ },
+ [],
+ target,
+ ),
+ target,
+ );
+ });
+
+ it("[[Call]] constructs with the provided arguments", () => {
+ assertEquals(
+ construct(
+ function (...args) {
+ return [new.target.value, ...args];
+ },
+ ["shrdlu", "cmfwyp"],
+ Object.assign(function () {}, { value: "etaoin" }),
+ ),
+ ["etaoin", "shrdlu", "cmfwyp"],
+ );
+ });
+
+ it("[[Call]] works with any arraylike third argument", () => {
+ assertEquals(
+ construct(
+ function (...args) {
+ return [new.target.value, ...args];
+ },
+ {
+ 1: "shrdlu",
+ 3: "cmfwyp",
+ 4: "failure",
+ length: 4,
+ },
+ Object.assign(function () {}, { value: "etaoin" }),
+ ),
+ ["etaoin", undefined, "shrdlu", undefined, "cmfwyp"],
+ );
+ });
+});
+
+describe("identity", () => {
+ it("[[Call]] returns what it is given", () => {
+ const value = {};
+ assertStrictEquals(identity(value), value);
+ });
+
+ it("[[Construct]] is constructable", () => {
+ const value = {};
+ assertStrictEquals(new identity(value), value);
+ });
+
+ it("[[Construct]] is subclassable", () => {
+ const value = {};
+ assertStrictEquals(new class extends identity {}(value), value);
+ });
+});
+
+describe("isCallable", () => {
+ it("[[Call]] returns true for ordinary functions", () => {
+ assert(isCallable(function () {}));
+ });
+
+ it("[[Call]] returns true for arrow functions", () => {
+ assert(isCallable(() => {}));
+ });
+
+ it("[[Call]] returns true for generator functions", () => {
+ assert(isCallable(function* () {}));
+ });
+
+ it("[[Call]] returns true for classes", () => {
+ assert(isCallable(class {}));
+ });
+
+ it("[[Call]] returns true for builtin functions", () => {
+ assert(isCallable(Math.ceil));
+ });
+
+ it("[[Call]] returns false for null", () => {
+ assert(!isCallable(null));
+ });
+
+ it("[[Call]] returns false for objects", () => {
+ assert(!isCallable({}));
+ });
+});
+
+describe("isConstructor", () => {
+ it("[[Call]] returns true for ordinary functions", () => {
+ assert(isConstructor(function () {}));
+ });
+
+ it("[[Call]] returns false for arrow functions", () => {
+ assert(!isConstructor(() => {}));
+ });
+
+ it("[[Call]] returns false for generator functions", () => {
+ assert(!isConstructor(function* () {}));
+ });
+
+ it("[[Call]] returns true for classes", () => {
+ assert(isConstructor(class {}));
+ });
+
+ it("[[Call]] returns false for builtin functions", () => {
+ assert(!isConstructor(Math.ceil));
+ });
+
+ it("[[Call]] returns false for null", () => {
+ assert(!isConstructor(null));
+ });
+
+ it("[[Call]] returns false for objects", () => {
+ assert(!isConstructor({}));
+ });
+});
+
+describe("makeCallable", () => {
+ it("[[Call]] transfers the first argument to this", () => {
+ assertStrictEquals(
+ makeCallable(
+ function () {
+ return this;
+ },
+ ).call("fail", "pass"),
+ "pass",
+ );
+ });
+
+ it("[[Call]] transfers the remaining arguments", () => {
+ assertEquals(
+ makeCallable(
+ function (...args) {
+ return [this, ...args];
+ },
+ ).call("failure", "etaoin", "shrdlu", "cmfwyp"),
+ ["etaoin", "shrdlu", "cmfwyp"],
+ );
+ });
+});
+
+describe("ordinaryHasInstance", () => {
+ it("[[Call]] walks the prototype chain", () => {
+ const constructor = class {
+ [Symbol.hasInstance]() {
+ return false;
+ }
+ };
+ assert(
+ ordinaryHasInstance(
+ constructor,
+ new class extends constructor {}(),
+ ),
+ );
+ });
+});