]> Lady’s Gitweb - Pisces/commitdiff
Redo create⸺Function A·P·I in function.js
authorLady <redacted>
Sun, 5 Nov 2023 20:41:30 +0000 (15:41 -0500)
committerLady <redacted>
Sun, 5 Nov 2023 21:11:09 +0000 (16:11 -0500)
`createCallableFunction` and `createIllegalConstructor` are now joined
by a `createArrowFunction`, and all three have extremely similar
interfaces. Further refactoring is probably necessary elsewhere in the
codebase to make use of these changes.

function.js
function.test.js
symbol.js

index 7da0a3a1ccfc7f3f9a63df3a30835459bdf54773..d13be725527efb910f3fa59102630c6941c81052 100644 (file)
@@ -7,7 +7,7 @@
 // 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 {
   /**
@@ -24,6 +24,21 @@ 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,
@@ -32,9 +47,9 @@ export const {
    * 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,
 
@@ -55,6 +70,7 @@ export const {
   ]);
   const {
     create: objectCreate,
+    defineProperty: defineOwnProperty,
     defineProperties: defineOwnProperties,
     getOwnPropertyDescriptor,
     getPrototypeOf: getPrototype,
@@ -67,6 +83,9 @@ export const {
   const argumentIterablePrototype = {
     [ITERATOR]() {
       return {
+        [ITERATOR]() {
+          return this;
+        },
         next: callBind(
           arrayIteratorNext,
           call(arrayIterator, this.args, []),
@@ -78,6 +97,55 @@ export const {
     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) =>
@@ -89,38 +157,46 @@ export const {
           { 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) => {
@@ -228,5 +304,5 @@ export const isCallable = ($) => typeof $ === "function";
  */
 export const ordinaryHasInstance = createCallableFunction(
   Function.prototype[Symbol.hasInstance],
-  "ordinaryHasInstance",
+  { name: "ordinaryHasInstance" },
 );
index dd9b642a48ba85353cadf607787ecb6152314674..9be4db682418619a53820f35a3da2657ad1e4773 100644 (file)
@@ -20,6 +20,7 @@ import {
   call,
   completesNormally,
   construct,
+  createArrowFunction,
   createCallableFunction,
   createIllegalConstructor,
   identity,
@@ -265,6 +266,98 @@ describe("construct", () => {
   });
 });
 
+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(
@@ -306,16 +399,37 @@ describe("createCallableFunction", () => {
     );
   });
 
+  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 () {}));
   });
@@ -351,56 +465,39 @@ describe("createIllegalConstructor", () => {
       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(
index a96cb812636aca31952302955166c036fee18077..e1183ef05076327dba3518c6fc6e1a2eefd68d1a 100644 (file)
--- a/symbol.js
+++ b/symbol.js
@@ -20,7 +20,7 @@ import { getOwnPropertyDescriptor } from "./object.js";
  */
 export const getSymbolDescription = createCallableFunction(
   getOwnPropertyDescriptor(Symbol.prototype, "description").get,
-  "getSymbolDescription",
+  { name: "getSymbolDescription" },
 );
 
 /**
@@ -35,7 +35,7 @@ export const getSymbolDescription = createCallableFunction(
  */
 export const symbolToString = createCallableFunction(
   Symbol.prototype.toString,
-  "symbolToString",
+  { name: "symbolToString" },
 );
 
 /**
@@ -48,5 +48,5 @@ export const symbolToString = createCallableFunction(
  */
 export const symbolValue = createCallableFunction(
   Symbol.prototype.valueOf,
-  "symbolValue",
+  { name: "symbolValue" },
 );
This page took 0.031704 seconds and 4 git commands to generate.