]> Lady’s Gitweb - Pisces/blobdiff - object.js
Move isConcatSpreadable[Object] into object.js
[Pisces] / object.js
index 8f5c4903882ce1fd4b3561cac44c2ce4edd19553..ee3d51da336d6f1c5a09123cd41b8ec921550f80 100644 (file)
--- a/object.js
+++ b/object.js
 // ♓🌟 Piscēs ∷ object.js
 // ====================================================================
 //
-// Copyright © 2022 Lady [@ Lady’s Computer].
+// Copyright © 2022–2023 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 {
+  bind,
+  call,
+  createArrowFunction,
+  toFunctionName,
+} from "./function.js";
+import {
+  IS_CONCAT_SPREADABLE,
+  ITERATOR,
+  SPECIES,
+  toLength,
+  toPrimitive,
+  type,
+} from "./value.js";
+
+/**
+ * An object whose properties are lazy‐loaded from the methods on the
+ * own properties of the provided object.
+ *
+ * This is useful when you are looking to reference properties on
+ * objects which, due to module dependency graphs, cannot be guaranteed
+ * to have been initialized yet.
+ *
+ * The resulting properties will have the same attributes (regarding
+ * configurability, enumerability, and writability) as the
+ * corresponding properties on the methods object. If a property is
+ * marked as writable, the method will never be called if it is set
+ * before it is gotten. By necessity, the resulting properties are all
+ * configurable before they are accessed for the first time.
+ *
+ * Methods will be called with the resulting object as their this
+ * value.
+ *
+ * `LazyLoader` objects have the same prototype as the passed methods
+ * object.
+ */
+export class LazyLoader extends null {
+  /**
+   * Constructs a new `LazyLoader` object.
+   *
+   * ☡ This function throws if the provided value is not an object.
+   */
+  constructor(loadMethods) {
+    if (type(loadMethods) !== "object") {
+      // The provided value is not an object; throw an error.
+      throw new TypeError(
+        `Piscēs: Cannot construct LazyLoader: Provided argument is not an object: ${loadMethods}.`,
+      );
+    } else {
+      // The provided value is an object; process it and build the
+      // result.
+      const result = objectCreate(getPrototype(loadMethods));
+      const methodKeys = getOwnPropertyKeys(loadMethods);
+      for (let index = 0; index < methodKeys.length; ++index) {
+        // Iterate over the property keys of the provided object and
+        // define getters and setters appropriately on the result.
+        const methodKey = methodKeys[index];
+        const { configurable, enumerable, writable } =
+          getOwnPropertyDescriptor(loadMethods, methodKey);
+        defineOwnProperty(result, methodKey, {
+          configurable: true,
+          enumerable,
+          get: defineOwnProperty(
+            () => {
+              const value = call(loadMethods[methodKey], result, []);
+              defineOwnProperty(result, methodKey, {
+                configurable,
+                enumerable,
+                value,
+                writable,
+              });
+              return value;
+            },
+            "name",
+            { value: toFunctionName(methodKey, "get") },
+          ),
+          set: writable
+            ? defineOwnProperty(
+              ($) =>
+                defineOwnProperty(result, methodKey, {
+                  configurable,
+                  enumerable,
+                  value: $,
+                  writable,
+                }),
+              "name",
+              { value: toFunctionName(methodKey, "set") },
+            )
+            : void {},
+        });
+      }
+      return result;
+    }
+  }
+}
+
 /**
  * A property descriptor object.
  *
  *
  * Otherwise, the instance properties and methods are generic.
  */
-export const PropertyDescriptor = (() => {
+export const { PropertyDescriptor } = (() => {
   class PropertyDescriptor extends null {
     /**
      * Constructs a new property descriptor object from the provided
      * object.
      *
      * The resulting object is proxied to enforce types (for example,
-     * its `enumerable` property, if defined, will always be a
+     * its `.enumerable` property, if defined, will always be a
      * boolean).
      */
-    //deno-lint-ignore constructor-super
-    constructor(Obj) {
-      if (!isObject(Obj)) {
+    constructor(O) {
+      if (type(O) !== "object") {
         // The provided value is not an object.
         throw new TypeError(
-          "Piscēs: Cannot convert primitive to property descriptor.",
+          `Piscēs: Cannot convert primitive to property descriptor: ${O}.`,
         );
       } else {
         // The provided value is an object.
-        const desc = Object.create(propertyDescriptorPrototype);
-        if ("enumerable" in Obj) {
+        const desc = objectCreate(propertyDescriptorPrototype);
+        if ("enumerable" in O) {
           // An enumerable property is specified.
-          desc.enumerable = !!Obj.enumerable;
+          desc.enumerable = !!O.enumerable;
         } else {
           // An enumerable property is not specified.
           /* do nothing */
         }
-        if ("configurable" in Obj) {
+        if ("configurable" in O) {
           // A configurable property is specified.
-          desc.configurable = !!Obj.configurable;
+          desc.configurable = !!O.configurable;
         } else {
           // A configurable property is not specified.
           /* do nothing */
         }
-        if ("value" in Obj) {
+        if ("value" in O) {
           // A value property is specified.
-          desc.value = Obj.value;
+          desc.value = O.value;
         } else {
           // A value property is not specified.
           /* do nothing */
         }
-        if ("writable" in Obj) {
+        if ("writable" in O) {
           // A writable property is specified.
-          desc.writable = !!Obj.writable;
+          desc.writable = !!O.writable;
         } else {
           // A writable property is not specified.
           /* do nothing */
         }
-        if ("get" in Obj) {
+        if ("get" in O) {
           // A get property is specified.
-          const getter = Obj.get;
-          if (typeof getter != "function") {
+          const getter = O.get;
+          if (getter !== undefined && typeof getter !== "function") {
             // The getter is not callable.
             throw new TypeError("Piscēs: Getters must be callable.");
           } else {
@@ -80,10 +175,10 @@ export const PropertyDescriptor = (() => {
           // A get property is not specified.
           /* do nothing */
         }
-        if ("set" in Obj) {
+        if ("set" in O) {
           // A set property is specified.
-          const setter = Obj.set;
-          if (typeof setter != "function") {
+          const setter = O.set;
+          if (setter !== undefined && typeof setter !== "function") {
             // The setter is not callable.
             throw new TypeError("Piscēs: Setters must be callable.");
           } else {
@@ -165,20 +260,18 @@ export const PropertyDescriptor = (() => {
       }
     }
 
-    /** Returns whether this is an accessor descrtiptor. */
+    /** Gets whether this is an accessor descrtiptor. */
     get isAccessorDescriptor() {
       return this !== undefined && ("get" in this || "set" in this);
     }
 
-    /** Returns whether this is a data descrtiptor. */
+    /** Gets whether this is a data descrtiptor. */
     get isDataDescriptor() {
       return this !== undefined &&
         ("value" in this || "writable" in this);
     }
 
-    /**
-     * Returns whether this is a fully‐populated property descriptor.
-     */
+    /** Gets whether this is a fully‐populated property descriptor. */
     get isFullyPopulated() {
       return this !== undefined &&
         ("value" in this && "writable" in this ||
@@ -187,7 +280,7 @@ export const PropertyDescriptor = (() => {
     }
 
     /**
-     * Returns whether this is a generic (not accessor or data)
+     * Gets whether this is a generic (not accessor or data)
      * descrtiptor.
      */
     get isGenericDescriptor() {
@@ -197,7 +290,7 @@ export const PropertyDescriptor = (() => {
     }
   }
 
-  const coercePropretyDescriptorValue = (P, V) => {
+  const coercePropertyDescriptorValue = (P, V) => {
     switch (P) {
       case "configurable":
       case "enumerable":
@@ -206,7 +299,7 @@ export const PropertyDescriptor = (() => {
       case "value":
         return V;
       case "get":
-        if (typeof V != "function") {
+        if (V !== undefined && typeof V !== "function") {
           throw new TypeError(
             "Piscēs: Getters must be callable.",
           );
@@ -214,7 +307,7 @@ export const PropertyDescriptor = (() => {
           return V;
         }
       case "set":
-        if (typeof V != "function") {
+        if (V !== undefined && typeof V !== "function") {
           throw new TypeError(
             "Piscēs: Setters must be callable.",
           );
@@ -226,7 +319,9 @@ export const PropertyDescriptor = (() => {
     }
   };
 
-  const propertyDescriptorPrototype = PropertyDescriptor.prototype;
+  const {
+    prototype: propertyDescriptorPrototype,
+  } = PropertyDescriptor;
 
   const propertyDescriptorProxyHandler = Object.assign(
     Object.create(null),
@@ -244,9 +339,9 @@ export const PropertyDescriptor = (() => {
             throw new TypeError(
               "Piscēs: Property descriptor attributes must be data properties.",
             );
-          } else if ("value" in desc) {
-            // Desc has a value.
-            desc.value = coercePropretyDescriptorValue(P, desc.value);
+          } else if ("value" in desc || !(P in O)) {
+            // Desc has a value or P does not already exist on O.
+            desc.value = coercePropertyDescriptorValue(P, desc.value);
           } else {
             // Desc is not an accessor property descriptor and has no
             // value.
@@ -264,28 +359,44 @@ export const PropertyDescriptor = (() => {
             );
           } else {
             // P can be safely defined on O.
-            return Reflect.defineProperty(O, P, desc);
+            return defineOwnProperty(O, P, desc);
           }
         } else {
           // P is not a property descriptor attribute.
-          return Reflect.defineProperty(O, P, Desc);
+          return defineOwnProperty(O, P, Desc);
         }
       },
       set(O, P, V, Receiver) {
-        const newValue = coercePropertyDescriptorValue(P, V);
-        const isAccessorDescriptor = "get" === P || "set" === P ||
-          "get" in O || "set" in O;
-        const isDataDescriptor = "value" === P || "writable" === P ||
-          "value" in O || "writable" in O;
-        if (isAccessorDescriptor && isDataDescriptor) {
-          // Both accessor and data attributes will be present on O
-          // after defining P.
-          throw new TypeError(
-            "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
-          );
+        if (
+          P === "configurable" || P === "enumerable" ||
+          P === "writable" || P === "value" ||
+          P === "get" || P === "set"
+        ) {
+          // P is a property descriptor attribute.
+          const newValue = coercePropertyDescriptorValue(P, V);
+          const isAccessorDescriptor = "get" === P || "set" === P ||
+            "get" in O || "set" in O;
+          const isDataDescriptor = "value" === P || "writable" === P ||
+            "value" in O || "writable" in O;
+          if (isAccessorDescriptor && isDataDescriptor) {
+            // Both accessor and data attributes will be present on O
+            // after defining P.
+            throw new TypeError(
+              "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
+            );
+          } else {
+            // P can be safely defined on O.
+            //
+            // ☡ Receiver will be the *proxied* object, so passing it
+            // through to setPropertyValue here would produce an
+            // infinite loop.
+            //
+            // ☡ This has implications on objects with a proxied
+            // PropertyDescriptor in their prototype.
+            return setPropertyValue(O, P, newValue, O);
+          }
         } else {
-          // P can be safely defined on O.
-          return Reflect.set(O, prop, newValue, Receiver);
+          return setPropertyValue(O, P, V, Receiver);
         }
       },
       setPrototypeOf(O, V) {
@@ -294,95 +405,405 @@ export const PropertyDescriptor = (() => {
           return false;
         } else {
           // V is the property descriptor prototype.
-          return Reflect.setPrototypeOf(O, V);
+          return setPrototype(O, V);
         }
       },
     },
   );
 
-  return PropertyDescriptor;
+  return { PropertyDescriptor };
 })();
 
 /**
- * Returns a new frozen shallow copy of the enumerable own properties
- * of the provided object, according to the following rules :—
+ * Defines an own property on the provided object on the provided
+ * property key using the provided property descriptor.
  *
- * - For data properties, create a nonconfigurable, nonwritable
- *   property with the same value.
+ * ※ This is effectively an alias for `Object.defineProperty`.
+ */
+export const defineOwnProperty = createArrowFunction(
+  Object.defineProperty,
+  { name: "defineOwnProperty" },
+);
+
+export const {
+  /**
+   * Defines own properties on the provided object using the
+   * descriptors on the enumerable own properties of the provided
+   * additional objects.
+   *
+   * ※ This differs from `Object.defineProperties` in that it can take
+   * multiple source objects.
+   */
+  defineOwnProperties,
+
+  /**
+   * Returns a new frozen shallow copy of the enumerable own properties
+   * of the provided object, according to the following rules :—
+   *
+   * - For data properties, create a nonconfigurable, nonwritable
+   *   property with the same value.
+   *
+   * - For accessor properties, create a nonconfigurable accessor
+   *   property with the same getter *and* setter.
+   *
+   * The prototype for the resulting object will be taken from the
+   * `.prototype` property of the provided constructor, or the
+   * `.prototype` of the `.constructor` of the provided object if the
+   * provided constructor is undefined. If the used constructor has a
+   * nonnullish `.[Symbol.species]`, that will be used instead. If the
+   * used constructor or species is nullish or does not have a
+   * `.prototype` property, the prototype is set to null.
+   *
+   * ※ The prototype of the provided object itself is ignored.
+   */
+  frozenCopy,
+
+  /**
+   * Returns whether the provided object is frozen.
+   *
+   * ※ This function returns false for nonobjects.
+   *
+   * ※ This is effectively an alias for `!Object.isFrozen`.
+   */
+  isUnfrozenObject,
+
+  /**
+   * Returns whether the provided object is sealed.
+   *
+   * ※ This function returns false for nonobjects.
+   *
+   * ※ This is effectively an alias for `!Object.isSealed`.
+   */
+  isUnsealedObject,
+
+  /**
+   * Sets the prototype of the provided object to the provided value
+   * and returns the object.
+   *
+   * ※ This is effectively an alias for `Object.setPrototypeOf`.
+   */
+  setPrototype,
+
+  /**
+   * Returns the provided value converted to an object.
+   *
+   * Existing objects are returned with no modification.
+   *
+   * ☡ This function throws if its argument is null or undefined.
+   */
+  toObject,
+} = (() => {
+  const createObject = Object;
+  const {
+    create,
+    defineProperties,
+    getPrototypeOf,
+    isFrozen,
+    isSealed,
+    setPrototypeOf,
+  } = Object;
+  const {
+    next: generatorIteratorNext,
+  } = getPrototypeOf(function* () {}.prototype);
+  const propertyDescriptorEntryIterablePrototype = {
+    [ITERATOR]() {
+      return {
+        next: bind(generatorIteratorNext, this.generator(), []),
+      };
+    },
+  };
+  const propertyDescriptorEntryIterable = ($) =>
+    create(propertyDescriptorEntryIterablePrototype, {
+      generator: { value: $ },
+    });
+
+  return {
+    defineOwnProperties: (O, ...sources) => {
+      const { length } = sources;
+      for (let index = 0; index < length; ++index) {
+        defineProperties(O, sources[index]);
+      }
+      return O;
+    },
+    frozenCopy: (O, constructor = O?.constructor) => {
+      if (O == null) {
+        // O is null or undefined.
+        throw new TypeError(
+          "Piscēs: Cannot copy properties of null or undefined.",
+        );
+      } else {
+        // O is not null or undefined.
+        //
+        // (If not provided, the constructor will be the value of
+        // getting the `.constructor` property of O.)
+        const species = constructor?.[SPECIES] ?? constructor;
+        return preventExtensions(
+          objectCreate(
+            species == null || !("prototype" in species)
+              ? null
+              : species.prototype,
+            objectFromEntries(
+              propertyDescriptorEntryIterable(function* () {
+                const ownPropertyKeys = getOwnPropertyKeys(O);
+                for (
+                  let i = 0;
+                  i < ownPropertyKeys.length;
+                  ++i
+                ) {
+                  const P = ownPropertyKeys[i];
+                  const Desc = getOwnPropertyDescriptor(O, P);
+                  if (Desc.enumerable) {
+                    // P is an enumerable property.
+                    yield [
+                      P,
+                      "get" in Desc || "set" in Desc
+                        ? {
+                          configurable: false,
+                          enumerable: true,
+                          get: Desc.get,
+                          set: Desc.set,
+                        }
+                        : {
+                          configurable: false,
+                          enumerable: true,
+                          value: Desc.value,
+                          writable: false,
+                        },
+                    ];
+                  } else {
+                    // P is not an enumerable property.
+                    /* do nothing */
+                  }
+                }
+              }),
+            ),
+          ),
+        );
+      }
+    },
+    isUnfrozenObject: (O) => !isFrozen(O),
+    isUnsealedObject: (O) => !isSealed(O),
+    setPrototype: (O, proto) => {
+      const obj = toObject(O);
+      if (O === obj) {
+        // The provided value is an object; set its prototype normally.
+        return setPrototypeOf(O, proto);
+      } else {
+        // The provided value is not an object; attempt to set the
+        // prototype on a coerced version with extensions prevented,
+        // then return the provided value.
+        //
+        // This will throw if the given prototype does not match the
+        // existing one on the coerced object.
+        setPrototypeOf(preventExtensions(obj), proto);
+        return O;
+      }
+    },
+    toObject: ($) => {
+      if ($ == null) {
+        // The provided value is nullish; this is an error.
+        throw new TypeError(
+          `Piscēs: Cannot convert ${$} into an object.`,
+        );
+      } else {
+        // The provided value is not nullish; coerce it to an object.
+        return createObject($);
+      }
+    },
+  };
+})();
+
+export const {
+  /**
+   * Removes the provided property key from the provided object and
+   * returns the object.
+   *
+   * ※ 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, get, has, ownKeys, set } = Reflect;
+
+  return {
+    deleteOwnProperty: (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}`,
+        );
+      } else {
+        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 (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}`,
+        );
+      } else {
+        return O;
+      }
+    },
+  };
+})();
+
+/**
+ * Marks the provided object as non·extensible and marks all its
+ * properties as nonconfigurable and (if data properties) nonwritable,
+ * and returns the object.
  *
- * - For accessor properties, create a nonconfigurable accessor
- *   property with the same getter *and* setter.
+ * ※ This is effectively an alias for `Object.freeze`.
+ */
+export const freeze = createArrowFunction(Object.freeze);
+
+/**
+ * Returns the function on the provided value at the provided property
+ * key.
  *
- * The prototype for the resulting object will be taken from the
- * `prototype` property of the provided constructor, or the `prototype`
- * of the `constructor` of the provided object if the provided
- * constructor is undefined. If the used constructor has a nonnullish
- * `Symbol.species`, that will be used instead.
+ * ☡ This function throws if the provided property key does not have an
+ * associated value which is callable.
  */
-export const frozenCopy = (O, constructor = O?.constructor) => {
-  if (O == null) {
-    // O is null or undefined.
-    throw new TypeError(
-      "Piscēs: Cannot copy properties of null or undefined.",
-    );
+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 {
-    // O is not null or undefined.
-    //
-    // (If not provided, the constructor will be the value of getting
-    // the `constructor` property of O.)
-    const species = constructor?.[Symbol.species] ?? constructor;
-    return Object.preventExtensions(
-      Object.create(
-        species == null || !("prototype" in species)
-          ? null
-          : species.prototype,
-        Object.fromEntries(
-          function* () {
-            for (const P of Reflect.ownKeys(O)) {
-              const Desc = Object.getOwnPropertyDescriptor(O, P);
-              if (Desc.enumerable) {
-                // P is an enumerable property.
-                yield [
-                  P,
-                  "get" in Desc || "set" in Desc
-                    ? {
-                      configurable: false,
-                      enumerable: true,
-                      get: Desc.get,
-                      set: Desc.set,
-                    }
-                    : {
-                      configurable: false,
-                      enumerable: true,
-                      value: Desc.value,
-                      writable: false,
-                    },
-                ];
-              } else {
-                // P is not an enumerable property.
-                /* do nothing */
-              }
-            }
-          }(),
-        ),
-      ),
-    );
+    return func;
   }
 };
 
-/** Returns whether the provided value is a constructor. */
-export const isConstructor = ($) => {
-  if (!isObject($)) {
-    // The provided value is not an object.
+/**
+ * Returns the property descriptor for the own property with the
+ * provided property key on the provided object, or null if none
+ * exists.
+ *
+ * ※ This is effectively an alias for
+ * `Object.getOwnPropertyDescriptor`.
+ */
+export const getOwnPropertyDescriptor = createArrowFunction(
+  Object.getOwnPropertyDescriptor,
+);
+
+/**
+ * Returns the property descriptors for the own properties on the
+ * provided object.
+ *
+ * ※ This is effectively an alias for
+ * `Object.getOwnPropertyDescriptors`.
+ */
+export const getOwnPropertyDescriptors = createArrowFunction(
+  Object.getOwnPropertyDescriptors,
+);
+
+/**
+ * Returns an array of string‐valued own property keys on the
+ * provided object.
+ *
+ * ☡ This includes both enumerable and non·enumerable properties.
+ *
+ * ※ This is effectively an alias for `Object.getOwnPropertyNames`.
+ */
+export const getOwnPropertyStrings = createArrowFunction(
+  Object.getOwnPropertyNames,
+  { name: "getOwnPropertyStrings" },
+);
+
+/**
+ * Returns an array of symbol‐valued own property keys on the
+ * provided object.
+ *
+ * ☡ This includes both enumerable and non·enumerable properties.
+ *
+ * ※ This is effectively an alias for
+ * `Object.getOwnPropertySymbols`.
+ */
+export const getOwnPropertySymbols = createArrowFunction(
+  Object.getOwnPropertySymbols,
+);
+
+/**
+ * Returns the prototype of the provided object.
+ *
+ * ※ This is effectively an alias for `Object.getPrototypeOf`.
+ */
+export const getPrototype = createArrowFunction(
+  Object.getPrototypeOf,
+  { name: "getPrototype" },
+);
+
+/**
+ * Returns whether the provided object has an own property with the
+ * provided property key.
+ *
+ * ※ This is effectively an alias for `Object.hasOwn`.
+ */
+export const hasOwnProperty = createArrowFunction(Object.hasOwn, {
+  name: "hasOwnProperty",
+});
+
+/** Returns whether the provided value is an arraylike object. */
+export const isArraylikeObject = ($) => {
+  if (type($) !== "object") {
     return false;
   } else {
-    // The provided value is an object.
     try {
-      Reflect.construct(
-        function () {},
-        [],
-        $,
-      ); // will throw if $ is not a constructor
+      lengthOfArraylike($); // throws if not arraylike
       return true;
     } catch {
       return false;
@@ -390,98 +811,131 @@ export const isConstructor = ($) => {
   }
 };
 
-/** Returns whether the provided value is an object. */
-export const isObject = ($) => {
-  return $ !== null &&
-    (typeof $ == "function" || typeof $ == "object");
-};
+export const {
+  /**
+   * Returns whether the provided value is spreadable during array
+   * concatenation.
+   *
+   * This is also used to determine which things should be treated as
+   * collections.
+   */
+  isConcatSpreadableObject,
+} = (() => {
+  const { isArray } = Array;
+
+  return {
+    isConcatSpreadableObject: ($) => {
+      if (type($) !== "object") {
+        // The provided value is not an object.
+        return false;
+      } else {
+        // The provided value is an object.
+        const spreadable = $[IS_CONCAT_SPREADABLE];
+        return spreadable !== undefined ? !!spreadable : isArray($);
+      }
+    },
+  };
+})();
 
 /**
- * Returns whether the provided object inherits from the prototype of
- * the provided function.
+ * Returns whether the provided object is extensible.
+ *
+ * ※ This function returns false for nonobjects.
+ *
+ * ※ This is effectively an alias for `Object.isExtensible`.
  */
-export const ordinaryHasInstance = Function.prototype.call.bind(
-  Function.prototype[Symbol.hasInstance],
+export const isExtensibleObject = createArrowFunction(
+  Object.isExtensible,
+  { name: "isExtensibleObject" },
 );
 
 /**
- * Returns the primitive value of the provided object per its
- * `toString` and `valueOf` methods.
+ * Returns the length of the provided arraylike value.
  *
- * If the provided hint is "string", then `toString` takes precedence;
- * otherwise, `valueOf` does.
+ * This can produce larger lengths than can actually be stored in
+ * arrays, because no such restrictions exist on arraylike methods.
  *
- * Throws an error if both of these methods are not callable or do not
- * return a primitive.
+ * ☡ This function throws if the provided value is not arraylike.
  */
-export const ordinaryToPrimitive = (O, hint) => {
-  for (
-    const name of hint == "string"
-      ? ["toString", "valueOf"]
-      : ["valueOf", "toString"]
-  ) {
-    const method = O[name];
-    if (typeof method == "function") {
-      // Method is callable.
-      const result = method.call(O);
-      if (!isObject(result)) {
-        // Method returns a primitive.
-        return result;
-      } else {
-        // Method returns an object.
-        continue;
-      }
-    } else {
-      // Method is not callable.
-      continue;
-    }
-  }
-  throw new TypeError("Piscēs: Unable to convert object to primitive");
-};
+export const lengthOfArraylike = ({ length }) => toLength(length);
 
 /**
- * Returns the provided value converted to a primitive, or throws if
- * no such conversion is possible.
+ * Returns an array of key~value pairs for the enumerable,
+ * string‐valued property keys on the provided object.
  *
- * The provided preferred type, if specified, should be "string",
- * "number", or "default". If the provided input has a
- * `Symbol.toPrimitive` method, this function will throw rather than
- * calling that method with a preferred type other than one of the
- * above.
+ * ※ This is effectively an alias for `Object.entries`.
  */
-export const toPrimitive = ($, preferredType) => {
-  if (isObject($)) {
-    // The provided value is an object.
-    const exoticToPrim = $[Symbol.toPrimitive] ?? undefined;
-    if (exoticToPrim !== undefined) {
-      // The provided value has an exotic primitive conversion method.
-      if (typeof exoticToPrim != "function") {
-        // The method is not callable.
-        throw new TypeError(
-          "Piscēs: Symbol.toPrimitive was neither nullish nor callable.",
-        );
-      } else {
-        // The method is callable.
-        const hint = `${preferredType ?? "default"}`;
-        if (!["default", "string", "number"].includes(hint)) {
-          // An invalid preferred type was specified.
-          throw new TypeError(
-            `Piscēs: Invalid preferred type: ${preferredType}.`,
-          );
-        } else {
-          // The resulting hint is either default, string, or number.
-          return exoticToPrim.call($, hint);
-        }
-      }
-    } else {
-      // Use the ordinary primitive conversion function.
-      ordinaryToPrimitive($, hint);
-    }
-  } else {
-    // The provided value is already a primitive.
-    return $;
-  }
-};
+export const namedEntries = createArrowFunction(Object.entries, {
+  name: "namedEntries",
+});
+
+/**
+ * Returns an array of the enumerable, string‐valued property keys on
+ * the provided object.
+ *
+ * ※ This is effectively an alias for `Object.keys`.
+ */
+export const namedKeys = createArrowFunction(Object.keys, {
+  name: "namedKeys",
+});
+
+/**
+ * Returns an array of property values for the enumerable,
+ * string‐valued property keys on the provided object.
+ *
+ * ※ This is effectively an alias for `Object.values`.
+ */
+export const namedValues = createArrowFunction(Object.values, {
+  name: "namedValues",
+});
+
+/**
+ * Returns a new object with the provided prototype and property
+ * descriptors.
+ *
+ * ※ This is effectively an alias for `Object.create`.
+ */
+export const objectCreate = createArrowFunction(Object.create, {
+  name: "objectCreate",
+});
+
+/**
+ * Returns a new object with the provided property keys and values.
+ *
+ * ※ This is effectively an alias for `Object.fromEntries`.
+ */
+export const objectFromEntries = createArrowFunction(
+  Object.fromEntries,
+  { name: "objectFromEntries" },
+);
+
+/**
+ * Marks the provided object as non·extensible, and returns the
+ * object.
+ *
+ * ※ This is effectively an alias for `Object.preventExtensions`.
+ */
+export const preventExtensions = createArrowFunction(
+  Object.preventExtensions,
+);
+
+/**
+ * Marks the provided object as non·extensible and marks all its
+ * properties as nonconfigurable, and returns the object.
+ *
+ * ※ This is effectively an alias for `Object.seal`.
+ */
+export const seal = createArrowFunction(Object.seal);
+
+/**
+ * Sets the values of the enumerable own properties of the provided
+ * additional objects on the provided object.
+ *
+ * ※ This is effectively an alias for `Object.assign`.
+ */
+export const setPropertyValues = createArrowFunction(Object.assign, {
+  name: "setPropertyValues",
+});
 
 /**
  * Returns the property key (symbol or string) corresponding to the
@@ -489,5 +943,5 @@ export const toPrimitive = ($, preferredType) => {
  */
 export const toPropertyKey = ($) => {
   const key = toPrimitive($, "string");
-  return typeof key == "symbol" ? key : `${key}`;
+  return typeof key === "symbol" ? key : `${key}`;
 };
This page took 0.059452 seconds and 4 git commands to generate.