From: Lady <redacted>
Date: Sun, 4 Sep 2022 21:47:15 +0000 (-0700)
Subject: Treat object function args more consistently
X-Git-Tag: 0.1.3
X-Git-Url: https://git.ladys.computer/Pisces/commitdiff_plain/f1667ed7321c6802dc2e96532b0b96eabba4b929?ds=sidebyside;hp=20911ecea4d7c09ac104d4e059975444bb4238d7

Treat object function args more consistently

Functions like getOwnPropertyKeys now do not require that their
arguments are objects, but *do* require that they are not null or
undefined. Similarly, toObject now throws for null and undefined
values, rather than returning an empty object. Use `toObject($ ?? {})`
if you need the old behaviour.
---

diff --git a/object.js b/object.js
index a34423e..f68909e 100644
--- a/object.js
+++ b/object.js
@@ -573,26 +573,62 @@ export const {
    * Removes the provided property key from the provided object and
    * returns the object.
    *
-   * ☡ This function differs from Reflect.deleteProperty and the
+   * ※ This function differs from Reflect.deleteProperty and the
    * `delete` operator in that it throws if the deletion is
    * unsuccessful.
+   *
+   * ※ This function throws if the first argument is not an object.
    */
   deleteOwnProperty,
 
+  /**
+   * Returns an array of property keys on the provided object.
+   *
+   * ※ This is effectively an alias for Reflect.ownKeys, except that
+   * it does not require that the argument be an object.
+   */
+  getOwnPropertyKeys,
+
+  /**
+   * Returns the value of the provided property key on the provided
+   * object.
+   *
+   * ※ This is effectively an alias for Reflect.get, except that it
+   * does not require that the argument be an object.
+   */
+  getPropertyValue,
+
+  /**
+   * Returns whether the provided property key exists on the provided
+   * object.
+   *
+   * ※ This is effectively an alias for Reflect.has, except that it
+   * does not require that the argument be an object.
+   *
+   * ※ This includes properties present on the prototype chain.
+   */
+  hasProperty,
+
   /**
    * Sets the provided property key to the provided value on the
    * provided object and returns the object.
    *
    * ※ This function differs from Reflect.set in that it throws if the
    * setting is unsuccessful.
+   *
+   * ※ This function throws if the first argument is not an object.
    */
   setPropertyValue,
 } = (() => {
-  const { deleteProperty, set } = Reflect;
+  const { deleteProperty, get, has, ownKeys, set } = Reflect;
 
   return {
     deleteOwnProperty: (O, P) => {
-      if (!deleteProperty(O, P)) {
+      if (type(O) !== "object") {
+        throw new TypeError(
+          `Piscēs: Tried to set property but provided value was not an object: ${V}`,
+        );
+      } else if (!deleteProperty(O, P)) {
         throw new TypeError(
           `Piscēs: Tried to delete property from object but [[Delete]] returned false: ${P}`,
         );
@@ -600,8 +636,16 @@ export const {
         return O;
       }
     },
+    getOwnPropertyKeys: (O) => ownKeys(toObject(O)),
+    getPropertyValue: (O, P, Receiver = O) =>
+      get(toObject(O), P, Receiver),
+    hasProperty: (O, P) => has(toObject(O), P),
     setPropertyValue: (O, P, V, Receiver = O) => {
-      if (!set(O, P, V, Receiver)) {
+      if (type(O) !== "object") {
+        throw new TypeError(
+          `Piscēs: Tried to set property but provided value was not an object: ${V}`,
+        );
+      } else if (!set(O, P, V, Receiver)) {
         throw new TypeError(
           `Piscēs: Tried to set property on object but [[Set]] returned false: ${P}`,
         );
@@ -714,46 +758,38 @@ export const {
   };
 })();
 
-export const {
-  /**
-   * Returns an array of property keys on the provided object.
-   *
-   * ※ This is an alias for Reflect.ownKeys.
-   */
-  ownKeys: getOwnPropertyKeys,
-
-  /**
-   * Returns the value of the provided property key on the provided
-   * object.
-   *
-   * ※ This is an alias for Reflect.get.
-   */
-  get: getPropertyValue,
-
-  /**
-   * Returns whether the provided property key exists on the provided
-   * object.
-   *
-   * ※ This is an alias for Reflect.has.
-   *
-   * ※ This includes properties present on the prototype chain.
-   */
-  has: hasProperty,
-} = Reflect;
+export const getMethod = (V, P) => {
+  const func = getPropertyValue(V, P);
+  if (func == null) {
+    return undefined;
+  } else if (typeof func !== "function") {
+    throw new TypeError(`Piscēs: Method not callable: ${P}`);
+  } else {
+    return func;
+  }
+};
 
 /**
  * Returns the provided value converted to an object.
  *
- * Null and undefined are converted to a new, empty object. Other
- * primitives are wrapped. Existing objects are returned with no
- * modification.
+ * Existing objects are returned with no modification.
  *
- * ※ This is effectively a nonconstructible version of the Object
- * constructor.
+ * ☡ This function throws a TypeError if its argument is null or
+ * undefined.
  */
 export const { toObject } = (() => {
   const makeObject = Object;
-  return { toObject: ($) => makeObject($) };
+  return {
+    toObject: ($) => {
+      if ($ == null) {
+        throw new TypeError(
+          `Piscēs: Cannot convert ${$} into an object.`,
+        );
+      } else {
+        return makeObject($);
+      }
+    },
+  };
 })();
 
 /**
diff --git a/object.test.js b/object.test.js
index 2b6cbb8..efef4d8 100644
--- a/object.test.js
+++ b/object.test.js
@@ -22,6 +22,10 @@ import {
   defineOwnProperties,
   deleteOwnProperty,
   frozenCopy,
+  getMethod,
+  getOwnPropertyKeys,
+  getPropertyValue,
+  hasProperty,
   LazyLoader,
   PropertyDescriptor,
   setPropertyValue,
@@ -875,6 +879,80 @@ describe("frozenCopy", () => {
   });
 });
 
+describe("getMethod", () => {
+  it("[[Call]] gets a method", () => {
+    const method = () => {};
+    assertStrictEquals(getMethod({ method }, "method"), method);
+  });
+
+  it("[[Call]] works for values coercible to objects", () => {
+    assertEquals(getMethod("", "toString"), String.prototype.toString);
+  });
+
+  it("[[Call]] throws for null and undefined", () => {
+    assertThrows(() => getMethod(null, "valueOf"));
+    assertThrows(() => getMethod(undefined, "valueOf"));
+  });
+
+  it("[[Call]] throws if the resulting value isn’t callable", () => {
+    assertThrows(() => getMethod({ "failure": true }, "failure"));
+  });
+});
+
+describe("getOwnPropertyKeys", () => {
+  it("[[Call]] gets own (but not inherited) property keys", () => {
+    assertEquals(getOwnPropertyKeys({ success: true }), ["success"]);
+  });
+
+  it("[[Call]] works for values coercible to objects", () => {
+    assertEquals(getOwnPropertyKeys("foo"), ["0", "1", "2", "length"]);
+  });
+
+  it("[[Call]] throws for null and undefined", () => {
+    assertThrows(() => getOwnPropertyKeys(null));
+    assertThrows(() => getOwnPropertyKeys(undefined));
+  });
+});
+
+describe("getPropertyValue", () => {
+  it("[[Call]] gets property values on the provided object", () => {
+    assertStrictEquals(
+      getPropertyValue({ success: true }, "success"),
+      true,
+    );
+  });
+
+  it("[[Call]] works for values coercible to objects", () => {
+    assertStrictEquals(
+      getPropertyValue("", "toString"),
+      String.prototype.toString,
+    );
+  });
+
+  it("[[Call]] throws for null and undefined", () => {
+    assertThrows(() => getPropertyValue(null, "valueOf"));
+    assertThrows(() => getPropertyValue(undefined, "valueOf"));
+  });
+});
+
+describe("hasProperty", () => {
+  it("[[Call]] gets whether a property exists on the provided object", () => {
+    assertStrictEquals(
+      hasProperty({ success: "etaoin" }, "success"),
+      true,
+    );
+  });
+
+  it("[[Call]] works for values coercible to objects", () => {
+    assertStrictEquals(hasProperty("", "toString"), true);
+  });
+
+  it("[[Call]] throws for null and undefined", () => {
+    assertThrows(() => hasProperty(null, "valueOf"));
+    assertThrows(() => hasProperty(undefined, "valueOf"));
+  });
+});
+
 describe("setPropertyValue", () => {
   it("[[Call]] sets the provided property on the provided object", () => {
     const obj = {};
@@ -935,9 +1013,9 @@ describe("toObject", () => {
     assertStrictEquals(toObject(obj), obj);
   });
 
-  it("returns a new object for nullish values", () => {
-    assertEquals(toObject(null), {});
-    assertEquals(toObject(void {}), {});
+  it("throws for nullish values", () => {
+    assertThrows(() => toObject(null));
+    assertThrows(() => toObject(void {}));
   });
 
   it("returns a wrapper object for other primitives", () => {