]> Lady’s Gitweb - Pisces/commitdiff
De‐classify property descriptors; move to value.js
authorLady <redacted>
Fri, 24 Nov 2023 16:37:08 +0000 (11:37 -0500)
committerLady <redacted>
Fri, 24 Nov 2023 17:35:03 +0000 (12:35 -0500)
The conversion to and from property descriptors doesn’t only look at
own properties, so it is convenient to create ones with a null
prototype (to prevent attempts at hacking into, e·g,
`Object.prototype`). This means the existing class‐based approach
isn’t viable and a more functional approach is better.

Many of the property descriptor functions explicitly handle undefined,
so treating them as values and not objects makes sense, even if some
(`completePropertyDescriptor`, `toPropertyDescriptor`) throw if their
argument is not an object.

dev-deps.js
object.js
object.test.js
value.js
value.test.js

index f2ed7c9708eaa05b21411866fa98947c920e112f..3c4981e50dde26cb6faa2d34e140730c40e86416 100644 (file)
 export {
   assert,
   assertEquals,
+  assertNotStrictEquals,
   assertStrictEquals,
   assertThrows,
-} from "https://deno.land/std@0.188.0/testing/asserts.ts";
+} from "https://deno.land/std@0.208.0/testing/asserts.ts";
 export {
   describe,
   it,
-} from "https://deno.land/std@0.188.0/testing/bdd.ts";
+} from "https://deno.land/std@0.208.0/testing/bdd.ts";
 export {
   assertSpyCall,
   assertSpyCalls,
   spy,
-} from "https://deno.land/std@0.188.0/testing/mock.ts";
+} from "https://deno.land/std@0.208.0/testing/mock.ts";
index 87040ac26f927bfe29aec5d5de6b750589af1b41..aa3538d28cf821d91c72f16244db4668f6f3fda7 100644 (file)
--- a/object.js
+++ b/object.js
@@ -99,317 +99,6 @@ export class LazyLoader extends null {
   }
 }
 
-/**
- * A property descriptor object.
- *
- * Actually constructing a property descriptor object using this class
- * is only necessary if you need strict guarantees about the types of
- * its properties; the resulting object is proxied to ensure the types
- * match what one would expect from composing FromPropertyDescriptor
- * and ToPropertyDescriptor in the Ecmascript specification.
- *
- * Otherwise, the instance properties and methods are generic.
- */
-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
-     * boolean).
-     */
-    constructor(O) {
-      if (type(O) !== "object") {
-        // The provided value is not an object.
-        throw new TypeError(
-          `Piscēs: Cannot convert primitive to property descriptor: ${O}.`,
-        );
-      } else {
-        // The provided value is an object.
-        const desc = objectCreate(propertyDescriptorPrototype);
-        if ("enumerable" in O) {
-          // An enumerable property is specified.
-          desc.enumerable = !!O.enumerable;
-        } else {
-          // An enumerable property is not specified.
-          /* do nothing */
-        }
-        if ("configurable" in O) {
-          // A configurable property is specified.
-          desc.configurable = !!O.configurable;
-        } else {
-          // A configurable property is not specified.
-          /* do nothing */
-        }
-        if ("value" in O) {
-          // A value property is specified.
-          desc.value = O.value;
-        } else {
-          // A value property is not specified.
-          /* do nothing */
-        }
-        if ("writable" in O) {
-          // A writable property is specified.
-          desc.writable = !!O.writable;
-        } else {
-          // A writable property is not specified.
-          /* do nothing */
-        }
-        if ("get" in O) {
-          // A get property is specified.
-          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 {
-            // The getter is callable.
-            desc.get = getter;
-          }
-        } else {
-          // A get property is not specified.
-          /* do nothing */
-        }
-        if ("set" in O) {
-          // A set property is specified.
-          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 {
-            // The setter is callable.
-            desc.set = setter;
-          }
-        } else {
-          // A set property is not specified.
-          /* do nothing */
-        }
-        if (
-          ("get" in desc || "set" in desc) &&
-          ("value" in desc || "writable" in desc)
-        ) {
-          // Both accessor and data attributes have been defined.
-          throw new TypeError(
-            "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
-          );
-        } else {
-          // The property descriptor is valid.
-          return new Proxy(desc, propertyDescriptorProxyHandler);
-        }
-      }
-    }
-
-    /**
-     * Completes this property descriptor by setting missing values to
-     * their defaults.
-     *
-     * This method modifies this object and returns undefined.
-     */
-    complete() {
-      if (this !== undefined && !("get" in this || "set" in this)) {
-        // This is a generic or data descriptor.
-        if (!("value" in this)) {
-          // `value` is not defined on this.
-          this.value = undefined;
-        } else {
-          // `value` is already defined on this.
-          /* do nothing */
-        }
-        if (!("writable" in this)) {
-          // `writable` is not defined on this.
-          this.writable = false;
-        } else {
-          // `writable` is already defined on this.
-          /* do nothing */
-        }
-      } else {
-        // This is not a generic or data descriptor.
-        if (!("get" in this)) {
-          // `get` is not defined on this.
-          this.get = undefined;
-        } else {
-          // `get` is already defined on this.
-          /* do nothing */
-        }
-        if (!("set" in this)) {
-          // `set` is not defined on this.
-          this.set = undefined;
-        } else {
-          // `set` is already defined on this.
-          /* do nothing */
-        }
-      }
-      if (!("enumerable" in this)) {
-        // `enumerable` is not defined on this.
-        this.enumerable = false;
-      } else {
-        // `enumerable` is already defined on this.
-        /* do nothing */
-      }
-      if (!("configurable" in this)) {
-        // `configurable` is not defined on this.
-        this.configurable = false;
-      } else {
-        // `configurable` is already defined on this.
-        /* do nothing */
-      }
-    }
-
-    /** Gets whether this is an accessor descrtiptor. */
-    get isAccessorDescriptor() {
-      return this !== undefined && ("get" in this || "set" in this);
-    }
-
-    /** Gets whether this is a data descrtiptor. */
-    get isDataDescriptor() {
-      return this !== undefined &&
-        ("value" in this || "writable" in this);
-    }
-
-    /** Gets whether this is a fully‐populated property descriptor. */
-    get isFullyPopulated() {
-      return this !== undefined &&
-        ("value" in this && "writable" in this ||
-          "get" in this && "set" in this) &&
-        "enumerable" in this && "configurable" in this;
-    }
-
-    /**
-     * Gets whether this is a generic (not accessor or data)
-     * descrtiptor.
-     */
-    get isGenericDescriptor() {
-      return this !== undefined &&
-        !("get" in this || "set" in this || "value" in this ||
-          "writable" in this);
-    }
-  }
-
-  const coercePropertyDescriptorValue = (P, V) => {
-    switch (P) {
-      case "configurable":
-      case "enumerable":
-      case "writable":
-        return !!V;
-      case "value":
-        return V;
-      case "get":
-        if (V !== undefined && typeof V !== "function") {
-          throw new TypeError(
-            "Piscēs: Getters must be callable.",
-          );
-        } else {
-          return V;
-        }
-      case "set":
-        if (V !== undefined && typeof V !== "function") {
-          throw new TypeError(
-            "Piscēs: Setters must be callable.",
-          );
-        } else {
-          return V;
-        }
-      default:
-        return V;
-    }
-  };
-
-  const {
-    prototype: propertyDescriptorPrototype,
-  } = PropertyDescriptor;
-
-  const propertyDescriptorProxyHandler = Object.assign(
-    Object.create(null),
-    {
-      defineProperty(O, P, Desc) {
-        if (
-          P === "configurable" || P === "enumerable" ||
-          P === "writable" || P === "value" ||
-          P === "get" || P === "set"
-        ) {
-          // P is a property descriptor attribute.
-          const desc = new PropertyDescriptor(Desc);
-          if ("get" in desc || "set" in desc) {
-            // Desc is an accessor property descriptor.
-            throw new TypeError(
-              "Piscēs: Property descriptor attributes must be data properties.",
-            );
-          } 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.
-            /* do nothing */
-          }
-          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.
-            return defineOwnProperty(O, P, desc);
-          }
-        } else {
-          // P is not a property descriptor attribute.
-          return defineOwnProperty(O, P, Desc);
-        }
-      },
-      set(O, P, V, Receiver) {
-        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 {
-          return setPropertyValue(O, P, V, Receiver);
-        }
-      },
-      setPrototypeOf(O, V) {
-        if (V !== propertyDescriptorPrototype) {
-          // V is not the property descriptor prototype.
-          return false;
-        } else {
-          // V is the property descriptor prototype.
-          return setPrototype(O, V);
-        }
-      },
-    },
-  );
-
-  return { PropertyDescriptor };
-})();
-
 /**
  * Defines an own property on the provided object on the provided
  * property key using the provided property descriptor.
index a2e718b189a93ba31179f29dc12b7f9086752f35..38d333f75d273c61abbfc0e51a7fdb6b620ce560 100644 (file)
@@ -47,7 +47,6 @@ import {
   objectCreate,
   objectFromEntries,
   preventExtensions,
-  PropertyDescriptor,
   seal,
   setPropertyValue,
   setPropertyValues,
@@ -310,579 +309,6 @@ describe("LazyLoader", () => {
   });
 });
 
-describe("PropertyDescriptor", () => {
-  it("[[Call]] throws an error", () => {
-    assertThrows(() => PropertyDescriptor({}));
-  });
-
-  it("[[Construct]] creates a new PropertyDescriptor", () => {
-    assertStrictEquals(
-      Object.getPrototypeOf(new PropertyDescriptor({})),
-      PropertyDescriptor.prototype,
-    );
-  });
-
-  it("[[Construct]] throws for primitives", () => {
-    assertThrows(() => new PropertyDescriptor("failure"));
-  });
-
-  describe(".length", () => {
-    it("[[Get]] returns the correct length", () => {
-      assertStrictEquals(PropertyDescriptor.length, 1);
-    });
-  });
-
-  describe(".name", () => {
-    it("[[Get]] returns the correct name", () => {
-      assertStrictEquals(
-        PropertyDescriptor.name,
-        "PropertyDescriptor",
-      );
-    });
-  });
-
-  describe("::complete", () => {
-    it("[[Call]] completes a generic descriptor", () => {
-      const desc = {};
-      PropertyDescriptor.prototype.complete.call(desc);
-      assertEquals(desc, {
-        configurable: false,
-        enumerable: false,
-        value: undefined,
-        writable: false,
-      });
-    });
-
-    it("[[Call]] completes a data descriptor", () => {
-      const desc = { value: undefined };
-      PropertyDescriptor.prototype.complete.call(desc);
-      assertEquals(desc, {
-        configurable: false,
-        enumerable: false,
-        value: undefined,
-        writable: false,
-      });
-    });
-
-    it("[[Call]] completes an accessor descriptor", () => {
-      const desc = { get: undefined };
-      PropertyDescriptor.prototype.complete.call(desc);
-      assertEquals(desc, {
-        configurable: false,
-        enumerable: false,
-        get: undefined,
-        set: undefined,
-      });
-    });
-
-    describe(".length", () => {
-      it("[[Get]] returns the correct length", () => {
-        assertStrictEquals(
-          PropertyDescriptor.prototype.complete.length,
-          0,
-        );
-      });
-    });
-
-    describe(".name", () => {
-      it("[[Get]] returns the correct name", () => {
-        assertStrictEquals(
-          PropertyDescriptor.prototype.complete.name,
-          "complete",
-        );
-      });
-    });
-  });
-
-  describe("::isAccessorDescriptor", () => {
-    it("[[Get]] returns false for a generic descriptor", () => {
-      assertStrictEquals(
-        Reflect.get(
-          PropertyDescriptor.prototype,
-          "isAccessorDescriptor",
-          {},
-        ),
-        false,
-      );
-    });
-
-    it("[[Get]] returns false for a data descriptor", () => {
-      assertStrictEquals(
-        Reflect.get(
-          PropertyDescriptor.prototype,
-          "isAccessorDescriptor",
-          { value: undefined },
-        ),
-        false,
-      );
-    });
-
-    it("[[Get]] returns true for an accessor descriptor", () => {
-      assertStrictEquals(
-        Reflect.get(
-          PropertyDescriptor.prototype,
-          "isAccessorDescriptor",
-          { get: undefined },
-        ),
-        true,
-      );
-    });
-
-    describe("[[GetOwnProperty]].get.length", () => {
-      it("[[Get]] returns the correct length", () => {
-        assertStrictEquals(
-          Object.getOwnPropertyDescriptor(
-            PropertyDescriptor.prototype,
-            "isAccessorDescriptor",
-          ).get.length,
-          0,
-        );
-      });
-    });
-
-    describe("[[GetOwnProperty]].get.name", () => {
-      it("[[Get]] returns the correct name", () => {
-        assertStrictEquals(
-          Object.getOwnPropertyDescriptor(
-            PropertyDescriptor.prototype,
-            "isAccessorDescriptor",
-          ).get.name,
-          "get isAccessorDescriptor",
-        );
-      });
-    });
-  });
-
-  describe("::isDataDescriptor", () => {
-    it("[[Get]] returns false for a generic descriptor", () => {
-      assertStrictEquals(
-        Reflect.get(
-          PropertyDescriptor.prototype,
-          "isDataDescriptor",
-          {},
-        ),
-        false,
-      );
-    });
-
-    it("[[Get]] returns true for a data descriptor", () => {
-      assertStrictEquals(
-        Reflect.get(
-          PropertyDescriptor.prototype,
-          "isDataDescriptor",
-          { value: undefined },
-        ),
-        true,
-      );
-    });
-
-    it("[[Get]] returns false for an accessor descriptor", () => {
-      assertStrictEquals(
-        Reflect.get(
-          PropertyDescriptor.prototype,
-          "isDataDescriptor",
-          { get: undefined },
-        ),
-        false,
-      );
-    });
-
-    describe("[[GetOwnProperty]].get.length", () => {
-      it("[[Get]] returns the correct length", () => {
-        assertStrictEquals(
-          Object.getOwnPropertyDescriptor(
-            PropertyDescriptor.prototype,
-            "isDataDescriptor",
-          ).get.length,
-          0,
-        );
-      });
-    });
-
-    describe("[[GetOwnProperty]].get.name", () => {
-      it("[[Get]] returns the correct name", () => {
-        assertStrictEquals(
-          Object.getOwnPropertyDescriptor(
-            PropertyDescriptor.prototype,
-            "isDataDescriptor",
-          ).get.name,
-          "get isDataDescriptor",
-        );
-      });
-    });
-  });
-
-  describe("::isFullyPopulated", () => {
-    it("[[Get]] returns false for a generic descriptor", () => {
-      assertStrictEquals(
-        Reflect.get(
-          PropertyDescriptor.prototype,
-          "isFullyPopulated",
-          {},
-        ),
-        false,
-      );
-    });
-
-    it("[[Get]] returns false for a non‐fully‐populated data descriptor", () => {
-      assertStrictEquals(
-        Reflect.get(
-          PropertyDescriptor.prototype,
-          "isFullyPopulated",
-          { value: undefined },
-        ),
-        false,
-      );
-    });
-
-    it("[[Get]] returns true for a fully‐populated data descriptor", () => {
-      assertStrictEquals(
-        Reflect.get(PropertyDescriptor.prototype, "isFullyPopulated", {
-          configurable: true,
-          enumerable: true,
-          value: undefined,
-          writable: true,
-        }),
-        true,
-      );
-    });
-
-    it("[[Get]] returns false for a non‐fully‐populated accessor descriptor", () => {
-      assertStrictEquals(
-        Reflect.get(
-          PropertyDescriptor.prototype,
-          "isFullyPopulated",
-          { get: undefined },
-        ),
-        false,
-      );
-    });
-
-    it("[[Get]] returns true for a fully‐populated accessor descriptor", () => {
-      assertStrictEquals(
-        Reflect.get(PropertyDescriptor.prototype, "isFullyPopulated", {
-          configurable: true,
-          enumerable: true,
-          get: undefined,
-          set: undefined,
-        }),
-        true,
-      );
-    });
-
-    describe("[[GetOwnProperty]].get.length", () => {
-      it("[[Get]] returns the correct length", () => {
-        assertStrictEquals(
-          Object.getOwnPropertyDescriptor(
-            PropertyDescriptor.prototype,
-            "isFullyPopulated",
-          ).get.length,
-          0,
-        );
-      });
-    });
-
-    describe("[[GetOwnProperty]].get.name", () => {
-      it("[[Get]] returns the correct name", () => {
-        assertStrictEquals(
-          Object.getOwnPropertyDescriptor(
-            PropertyDescriptor.prototype,
-            "isFullyPopulated",
-          ).get.name,
-          "get isFullyPopulated",
-        );
-      });
-    });
-  });
-
-  describe("::isGenericDescriptor", () => {
-    it("[[Get]] returns true for a generic descriptor", () => {
-      assertStrictEquals(
-        Reflect.get(
-          PropertyDescriptor.prototype,
-          "isGenericDescriptor",
-          {},
-        ),
-        true,
-      );
-    });
-
-    it("[[Get]] returns true for a data descriptor", () => {
-      assertStrictEquals(
-        Reflect.get(
-          PropertyDescriptor.prototype,
-          "isGenericDescriptor",
-          { value: undefined },
-        ),
-        false,
-      );
-    });
-
-    it("[[Get]] returns false for an accessor descriptor", () => {
-      assertStrictEquals(
-        Reflect.get(
-          PropertyDescriptor.prototype,
-          "isGenericDescriptor",
-          { get: undefined },
-        ),
-        false,
-      );
-    });
-
-    describe("[[GetOwnProperty]].get.length", () => {
-      it("[[Get]] returns the correct length", () => {
-        assertStrictEquals(
-          Object.getOwnPropertyDescriptor(
-            PropertyDescriptor.prototype,
-            "isGenericDescriptor",
-          ).get.length,
-          0,
-        );
-      });
-    });
-
-    describe("[[GetOwnProperty]].get.name", () => {
-      it("[[Get]] returns the correct name", () => {
-        assertStrictEquals(
-          Object.getOwnPropertyDescriptor(
-            PropertyDescriptor.prototype,
-            "isGenericDescriptor",
-          ).get.name,
-          "get isGenericDescriptor",
-        );
-      });
-    });
-  });
-
-  describe("~configurable", () => {
-    it("[[DefineOwnProperty]] coerces to a boolean", () => {
-      const desc = new PropertyDescriptor({});
-      Object.defineProperty(desc, "configurable", {});
-      assertStrictEquals(desc.configurable, false);
-    });
-
-    it("[[DefineOwnProperty]] throws for accessor properties", () => {
-      const desc = new PropertyDescriptor({});
-      assertThrows(() =>
-        Object.defineProperty(desc, "configurable", { get: undefined })
-      );
-    });
-
-    it("[[Set]] coerces to a boolean", () => {
-      const desc = new PropertyDescriptor({});
-      desc.configurable = undefined;
-      assertStrictEquals(desc.configurable, false);
-    });
-
-    it("[[Delete]] works", () => {
-      const desc = new PropertyDescriptor({ configurable: false });
-      delete desc.configurable;
-      assert(!("configurable" in desc));
-    });
-  });
-
-  describe("~enumerable", () => {
-    it("[[DefineOwnProperty]] coerces to a boolean", () => {
-      const desc = new PropertyDescriptor({});
-      Object.defineProperty(desc, "enumerable", {});
-      assertStrictEquals(desc.enumerable, false);
-    });
-
-    it("[[DefineOwnProperty]] throws for accessor properties", () => {
-      const desc = new PropertyDescriptor({});
-      assertThrows(() =>
-        Object.defineProperty(desc, "enumerable", { get: undefined })
-      );
-    });
-
-    it("[[Set]] coerces to a boolean", () => {
-      const desc = new PropertyDescriptor({});
-      desc.enumerable = undefined;
-      assertStrictEquals(desc.enumerable, false);
-    });
-
-    it("[[Delete]] works", () => {
-      const desc = new PropertyDescriptor({ enumerable: false });
-      delete desc.enumerable;
-      assert(!("enumerable" in desc));
-    });
-  });
-
-  describe("~get", () => {
-    it("[[DefineOwnProperty]] works", () => {
-      const desc = new PropertyDescriptor({});
-      Object.defineProperty(desc, "get", {});
-      assertStrictEquals(desc.get, undefined);
-    });
-
-    it("[[DefineOwnProperty]] throws for accessor properties", () => {
-      const desc = new PropertyDescriptor({});
-      assertThrows(() =>
-        Object.defineProperty(desc, "get", { get: undefined })
-      );
-    });
-
-    it("[[DefineOwnProperty]] throws if not callable or undefined", () => {
-      const desc = new PropertyDescriptor({});
-      assertThrows(
-        () => Object.defineProperty(desc, "get", { value: null }),
-      );
-    });
-
-    it("[[DefineOwnProperty]] throws if a data property is defined", () => {
-      const desc = new PropertyDescriptor({ value: undefined });
-      assertThrows(() => Object.defineProperty(desc, "get", {}));
-    });
-
-    it("[[Set]] works", () => {
-      const desc = new PropertyDescriptor({});
-      const fn = () => {};
-      desc.get = fn;
-      assertStrictEquals(desc.get, fn);
-    });
-
-    it("[[Set]] throws if not callable or undefined", () => {
-      const desc = new PropertyDescriptor({});
-      assertThrows(() => desc.get = null);
-    });
-
-    it("[[Set]] throws if a data property is defined", () => {
-      const desc = new PropertyDescriptor({ value: undefined });
-      assertThrows(() => desc.get = undefined);
-    });
-
-    it("[[Delete]] works", () => {
-      const desc = new PropertyDescriptor({ get: undefined });
-      delete desc.get;
-      assert(!("get" in desc));
-    });
-  });
-
-  describe("~set", () => {
-    it("[[DefineOwnProperty]] works", () => {
-      const desc = new PropertyDescriptor({});
-      Object.defineProperty(desc, "set", {});
-      assertStrictEquals(desc.set, undefined);
-    });
-
-    it("[[DefineOwnProperty]] throws for accessor properties", () => {
-      const desc = new PropertyDescriptor({});
-      assertThrows(() =>
-        Object.defineProperty(desc, "set", { get: undefined })
-      );
-    });
-
-    it("[[DefineOwnProperty]] throws if not callable or undefined", () => {
-      const desc = new PropertyDescriptor({});
-      assertThrows(
-        () => Object.defineProperty(desc, "set", { value: null }),
-      );
-    });
-
-    it("[[DefineOwnProperty]] throws if a data property is defined", () => {
-      const desc = new PropertyDescriptor({ value: undefined });
-      assertThrows(() => Object.defineProperty(desc, "set", {}));
-    });
-
-    it("[[Set]] works", () => {
-      const desc = new PropertyDescriptor({});
-      const fn = (_) => {};
-      desc.set = fn;
-      assertStrictEquals(desc.set, fn);
-    });
-
-    it("[[Set]] throws if not callable or undefined", () => {
-      const desc = new PropertyDescriptor({});
-      assertThrows(() => desc.set = null);
-    });
-
-    it("[[Set]] throws if a data property is defined", () => {
-      const desc = new PropertyDescriptor({ value: undefined });
-      assertThrows(() => desc.set = undefined);
-    });
-
-    it("[[Delete]] works", () => {
-      const desc = new PropertyDescriptor({ set: undefined });
-      delete desc.set;
-      assert(!("set" in desc));
-    });
-  });
-
-  describe("~value", () => {
-    it("[[DefineOwnProperty]] works", () => {
-      const desc = new PropertyDescriptor({});
-      Object.defineProperty(desc, "value", {});
-      assertStrictEquals(desc.value, undefined);
-    });
-
-    it("[[DefineOwnProperty]] throws for accessor properties", () => {
-      const desc = new PropertyDescriptor({});
-      assertThrows(() =>
-        Object.defineProperty(desc, "value", { get: undefined })
-      );
-    });
-
-    it("[[DefineOwnProperty]] throws if an accessor property is defined", () => {
-      const desc = new PropertyDescriptor({ get: undefined });
-      assertThrows(() => Object.defineProperty(desc, "value", {}));
-    });
-
-    it("[[Set]] works", () => {
-      const desc = new PropertyDescriptor({});
-      desc.value = "success";
-      assertStrictEquals(desc.value, "success");
-    });
-
-    it("[[Set]] throws if an accessor property is defined", () => {
-      const desc = new PropertyDescriptor({ get: undefined });
-      assertThrows(() => desc.value = null);
-    });
-
-    it("[[Delete]] works", () => {
-      const desc = new PropertyDescriptor({ value: undefined });
-      delete desc.value;
-      assert(!("value" in desc));
-    });
-  });
-
-  describe("~writable", () => {
-    it("[[DefineOwnProperty]] coerces to a boolean", () => {
-      const desc = new PropertyDescriptor({});
-      Object.defineProperty(desc, "writable", {});
-      assertStrictEquals(desc.writable, false);
-    });
-
-    it("[[DefineOwnProperty]] throws for accessor properties", () => {
-      const desc = new PropertyDescriptor({});
-      assertThrows(() =>
-        Object.defineProperty(desc, "writable", { get: undefined })
-      );
-    });
-
-    it("[[DefineOwnProperty]] throws if an accessor property is defined", () => {
-      const desc = new PropertyDescriptor({ get: undefined });
-      assertThrows(() => Object.defineProperty(desc, "writable", {}));
-    });
-
-    it("[[Set]] coerces to a boolean", () => {
-      const desc = new PropertyDescriptor({});
-      desc.writable = undefined;
-      assertStrictEquals(desc.writable, false);
-    });
-
-    it("[[Set]] throws if an accessor property is defined", () => {
-      const desc = new PropertyDescriptor({ get: undefined });
-      assertThrows(() => desc.writable = false);
-    });
-
-    it("[[Delete]] works", () => {
-      const desc = new PropertyDescriptor({ writable: false });
-      delete desc.writable;
-      assert(!("writable" in desc));
-    });
-  });
-});
-
 describe("defineOwnProperty", () => {
   it("[[Call]] defines the property", () => {
     const obj = {};
index 6471ea3142f66f2a40ac899415a51e9d7391b5bd..1039a3ca9617d7a76b8493360eaf9a5e8456baea 100644 (file)
--- a/value.js
+++ b/value.js
@@ -173,6 +173,343 @@ export const POSITIVE_ZERO = 0;
 /** The undefined primitive. */
 export const UNDEFINED = undefined;
 
+/**
+ * Completes the provided property descriptor by setting missing values
+ * to their defaults.
+ *
+ * ※ This method modifies the provided object and returns undefined.
+ */
+export const completePropertyDescriptor = (Desc) => {
+  if (Desc === UNDEFINED) {
+    throw new TypeError(
+      "Piscēs: Cannot complete undefined property descriptor.",
+    );
+  } else if (!("get" in Desc || "set" in Desc)) {
+    // This is a generic or data descriptor.
+    if (!("value" in Desc)) {
+      // `value` is not defined on this.
+      Desc.value = UNDEFINED;
+    } else {
+      // `value` is already defined on this.
+      /* do nothing */
+    }
+    if (!("writable" in Desc)) {
+      // `writable` is not defined on this.
+      Desc.writable = false;
+    } else {
+      // `writable` is already defined on this.
+      /* do nothing */
+    }
+  } else {
+    // This is not a generic or data descriptor.
+    if (!("get" in Desc)) {
+      // `get` is not defined on this.
+      Desc.get = UNDEFINED;
+    } else {
+      // `get` is already defined on this.
+      /* do nothing */
+    }
+    if (!("set" in Desc)) {
+      // `set` is not defined on this.
+      Desc.set = UNDEFINED;
+    } else {
+      // `set` is already defined on this.
+      /* do nothing */
+    }
+  }
+  if (!("enumerable" in Desc)) {
+    // `enumerable` is not defined on this.
+    Desc.enumerable = false;
+  } else {
+    // `enumerable` is already defined on this.
+    /* do nothing */
+  }
+  if (!("configurable" in Desc)) {
+    // `configurable` is not defined on this.
+    Desc.configurable = false;
+  } else {
+    // `configurable` is already defined on this.
+    /* do nothing */
+  }
+};
+
+/** Gets whether the provided value is an accessor descrtiptor. */
+export const isAccessorDescriptor = (Desc) =>
+  Desc !== UNDEFINED && ("get" in Desc || "set" in Desc);
+
+/** Gets whether the provided value is a data descrtiptor. */
+export const isDataDescriptor = (Desc) =>
+  Desc !== UNDEFINED && ("value" in Desc || "writable" in Desc);
+
+/**
+ * Gets whether the provided value is a fully‐populated property
+ * descriptor.
+ */
+export const isFullyPopulatedDescriptor = (Desc) =>
+  Desc !== UNDEFINED &&
+  ("value" in Desc && "writable" in Desc ||
+    "get" in Desc && "set" in Desc) &&
+  "enumerable" in Desc && "configurable" in Desc;
+
+/**
+ * Gets whether the provided value is a generic (not accessor or data)
+ * descrtiptor.
+ */
+export const isGenericDescriptor = (Desc) =>
+  Desc !== UNDEFINED &&
+  !("get" in Desc || "set" in Desc || "value" in Desc ||
+    "writable" in Desc);
+
+export const {
+  /**
+   * Returns whether the provided value is a property descriptor record
+   * as created by `toPropertyDescriptor`.
+   *
+   * ※ This function is provided to enable inspection of whether an
+   * object uses the property descriptor record proxy implementation,
+   * not as a general test of whether an object satisfies the
+   * requirements for property descriptors. In most cases, a more
+   * specific test, like `isAccessorDescriptor`, `isDataDescriptor`, or
+   * `isGenericDescriptor`, is preferrable.
+   */
+  isPropertyDescriptorRecord,
+
+  /**
+   * Converts the provided value to a property descriptor record.
+   *
+   * ※ The prototype of a property descriptor record is always `null`.
+   *
+   * ※ Actually constructing a property descriptor object using this
+   * class is only necessary if you need strict guarantees about the
+   * types of its properties; the resulting object is proxied to ensure
+   * the types match what one would expect from composing
+   * FromPropertyDescriptor and ToPropertyDescriptor in the Ecmascript
+   * specification.
+   */
+  toPropertyDescriptor,
+} = (() => {
+  const {
+    assign: setPropertyValues,
+    create: objectCreate,
+    defineProperty: defineOwnProperty,
+  } = Object;
+  const {
+    apply: call,
+    defineProperty: reflectDefineProperty,
+    setPrototypeOf: reflectSetPrototypeOf,
+  } = Reflect;
+  const proxyConstructor = Proxy;
+  const { add: weakSetAdd, has: weakSetHas } = WeakSet.prototype;
+  const propertyDescriptorRecords = new WeakSet();
+  const coercePropertyDescriptorValue = (P, V) => {
+    switch (P) {
+      case "configurable":
+      case "enumerable":
+      case "writable":
+        return !!V;
+      case "value":
+        return V;
+      case "get":
+        if (V !== undefined && typeof V !== "function") {
+          throw new TypeError(
+            "Piscēs: Getters must be callable.",
+          );
+        } else {
+          return V;
+        }
+      case "set":
+        if (V !== undefined && typeof V !== "function") {
+          throw new TypeError(
+            "Piscēs: Setters must be callable.",
+          );
+        } else {
+          return V;
+        }
+      default:
+        return V;
+    }
+  };
+  const propertyDescriptorProxyHandler = Object.freeze(
+    Object.assign(
+      Object.create(null),
+      {
+        defineProperty(O, P, Desc) {
+          if (
+            P === "configurable" || P === "enumerable" ||
+            P === "writable" || P === "value" ||
+            P === "get" || P === "set"
+          ) {
+            // P is a property descriptor attribute.
+            const desc = setPropertyValues(objectCreate(null), Desc);
+            if ("get" in desc || "set" in desc) {
+              // Desc is an accessor property descriptor.
+              throw new TypeError(
+                "Piscēs: Property descriptor attributes must be data properties.",
+              );
+            } 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, but an existing value is present on O.
+              /* do nothing */
+            }
+            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.
+              return reflectDefineProperty(O, P, desc);
+            }
+          } else {
+            // P is not a property descriptor attribute.
+            return reflectDefineProperty(O, P, Desc);
+          }
+        },
+        setPrototypeOf(O, V) {
+          if (V !== null) {
+            // V is not the property descriptor prototype.
+            return false;
+          } else {
+            // V is the property descriptor prototype.
+            return reflectSetPrototypeOf(O, V);
+          }
+        },
+      },
+    ),
+  );
+
+  return {
+    isPropertyDescriptorRecord: ($) =>
+      call(weakSetHas, propertyDescriptorRecords, [$]),
+    toPropertyDescriptor: (Obj) => {
+      if (type(Obj) !== "object") {
+        // The provided value is not an object.
+        throw new TypeError(
+          `Piscēs: Cannot convert primitive to property descriptor: ${O}.`,
+        );
+      } else {
+        // The provided value is an object.
+        const desc = objectCreate(null);
+        if ("enumerable" in Obj) {
+          // An enumerable property is specified.
+          defineOwnProperty(desc, "enumerable", {
+            configurable: true,
+            enumerable: true,
+            value: !!Obj.enumerable,
+            writable: true,
+          });
+        } else {
+          // An enumerable property is not specified.
+          /* do nothing */
+        }
+        if ("configurable" in Obj) {
+          // A configurable property is specified.
+          defineOwnProperty(desc, "configurable", {
+            configurable: true,
+            enumerable: true,
+            value: !!Obj.configurable,
+            writable: true,
+          });
+        } else {
+          // A configurable property is not specified.
+          /* do nothing */
+        }
+        if ("value" in Obj) {
+          // A value property is specified.
+          defineOwnProperty(desc, "value", {
+            configurable: true,
+            enumerable: true,
+            value: Obj.value,
+            writable: true,
+          });
+        } else {
+          // A value property is not specified.
+          /* do nothing */
+        }
+        if ("writable" in Obj) {
+          // A writable property is specified.
+          defineOwnProperty(desc, "writable", {
+            configurable: true,
+            enumerable: true,
+            value: !!Obj.writable,
+            writable: true,
+          });
+        } else {
+          // A writable property is not specified.
+          /* do nothing */
+        }
+        if ("get" in Obj) {
+          // A get property is specified.
+          const getter = Obj.get;
+          if (getter !== UNDEFINED && typeof getter !== "function") {
+            // The getter is not callable.
+            throw new TypeError("Piscēs: Getters must be callable.");
+          } else {
+            // The getter is callable.
+            defineOwnProperty(desc, "get", {
+              configurable: true,
+              enumerable: true,
+              value: getter,
+              writable: true,
+            });
+          }
+        } else {
+          // A get property is not specified.
+          /* do nothing */
+        }
+        if ("set" in Obj) {
+          // A set property is specified.
+          const setter = Obj.set;
+          if (setter !== UNDEFINED && typeof setter !== "function") {
+            // The setter is not callable.
+            throw new TypeError("Piscēs: Setters must be callable.");
+          } else {
+            // The setter is callable.
+            defineOwnProperty(desc, "set", {
+              configurable: true,
+              enumerable: true,
+              value: setter,
+              writable: true,
+            });
+          }
+        } else {
+          // A set property is not specified.
+          /* do nothing */
+        }
+        if (
+          ("get" in desc || "set" in desc) &&
+          ("value" in desc || "writable" in desc)
+        ) {
+          // Both accessor and data attributes have been defined.
+          throw new TypeError(
+            "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
+          );
+        } else {
+          // The property descriptor is valid.
+          const record = new proxyConstructor(
+            desc,
+            propertyDescriptorProxyHandler,
+          );
+          call(weakSetAdd, propertyDescriptorRecords, [record]);
+          return record;
+        }
+      }
+    },
+  };
+})();
+
 export const {
   /**
    * Returns the primitive value of the provided object per its
index 9121af479c46f3e2a190ec64742bf82ffd31ae34..0f95f22fda345a9841fc695a4c47b6dbaa8649a3 100644 (file)
@@ -8,6 +8,9 @@
 // file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
 
 import {
+  assert,
+  assertEquals,
+  assertNotStrictEquals,
   assertStrictEquals,
   assertThrows,
   describe,
@@ -15,8 +18,14 @@ import {
 } from "./dev-deps.js";
 import {
   ASYNC_ITERATOR,
+  completePropertyDescriptor,
   HAS_INSTANCE,
   IS_CONCAT_SPREADABLE,
+  isAccessorDescriptor,
+  isDataDescriptor,
+  isFullyPopulatedDescriptor,
+  isGenericDescriptor,
+  isPropertyDescriptorRecord,
   ITERATOR,
   LN10,
   LN2,
@@ -48,6 +57,7 @@ import {
   toIndex,
   toLength,
   toPrimitive,
+  toPropertyDescriptor,
   type,
   UNDEFINED,
   UNSCOPABLES,
@@ -239,21 +249,298 @@ describe("UNSCOPABLES", () => {
   });
 });
 
-describe("Ε", () => {
-  it("[[Get]] is ε", () => {
-    assertStrictEquals(Ε, Number.EPSILON);
+describe("completePropertyDescriptor", () => {
+  it("[[Call]] completes a generic descriptor", () => {
+    const desc = {};
+    completePropertyDescriptor(desc);
+    assertEquals(desc, {
+      configurable: false,
+      enumerable: false,
+      value: undefined,
+      writable: false,
+    });
+  });
+
+  it("[[Call]] completes a data descriptor", () => {
+    const desc = { value: undefined };
+    completePropertyDescriptor(desc);
+    assertEquals(desc, {
+      configurable: false,
+      enumerable: false,
+      value: undefined,
+      writable: false,
+    });
+  });
+
+  it("[[Call]] completes an accessor descriptor", () => {
+    const desc = { get: undefined };
+    completePropertyDescriptor(desc);
+    assertEquals(desc, {
+      configurable: false,
+      enumerable: false,
+      get: undefined,
+      set: undefined,
+    });
+  });
+
+  it("[[Call]] throws an error when the descriptor is undefined", () => {
+    assertThrows(() => new completePropertyDescriptor(undefined));
+  });
+
+  it("[[Construct]] throws an error", () => {
+    assertThrows(() => new completePropertyDescriptor({}));
+  });
+
+  describe(".length", () => {
+    it("[[Get]] returns the correct length", () => {
+      assertStrictEquals(completePropertyDescriptor.length, 1);
+    });
+  });
+
+  describe(".name", () => {
+    it("[[Get]] returns the correct name", () => {
+      assertStrictEquals(
+        completePropertyDescriptor.name,
+        "completePropertyDescriptor",
+      );
+    });
   });
 });
 
-describe("Π", () => {
-  it("[[Get]] is π", () => {
-    assertStrictEquals(Π, Math.PI);
+describe("isAccessorDescriptor", () => {
+  it("[[Call]] returns false for a generic descriptor", () => {
+    assertStrictEquals(isAccessorDescriptor({}), false);
+  });
+
+  it("[[Get]] returns false for a data descriptor", () => {
+    assertStrictEquals(
+      isAccessorDescriptor({ value: undefined }),
+      false,
+    );
+    assertStrictEquals(
+      isAccessorDescriptor({ writable: undefined }),
+      false,
+    );
+  });
+
+  it("[[Get]] returns true for an accessor descriptor", () => {
+    assertStrictEquals(isAccessorDescriptor({ get: undefined }), true);
+    assertStrictEquals(isAccessorDescriptor({ set: undefined }), true);
+  });
+
+  it("[[Get]] returns false for undefined", () => {
+    assertStrictEquals(isAccessorDescriptor(undefined), false);
+  });
+
+  it("[[Construct]] throws an error", () => {
+    assertThrows(() => new isAccessorDescriptor({}));
+  });
+
+  describe(".length", () => {
+    it("[[Get]] returns the correct length", () => {
+      assertStrictEquals(isAccessorDescriptor.length, 1);
+    });
+  });
+
+  describe(".name", () => {
+    it("[[Get]] returns the correct name", () => {
+      assertStrictEquals(
+        isAccessorDescriptor.name,
+        "isAccessorDescriptor",
+      );
+    });
   });
 });
 
-describe("ℇ", () => {
-  it("[[Get]] is ℇ", () => {
-    assertStrictEquals(ℇ, Math.E);
+describe("isDataDescriptor", () => {
+  it("[[Call]] returns false for a generic descriptor", () => {
+    assertStrictEquals(isDataDescriptor({}), false);
+  });
+
+  it("[[Get]] returns true for a data descriptor", () => {
+    assertStrictEquals(isDataDescriptor({ value: undefined }), true);
+    assertStrictEquals(isDataDescriptor({ writable: true }), true);
+  });
+
+  it("[[Get]] returns false for an accessor descriptor", () => {
+    assertStrictEquals(isDataDescriptor({ get: undefined }), false);
+    assertStrictEquals(isDataDescriptor({ set: undefined }), false);
+  });
+
+  it("[[Get]] returns false for undefined", () => {
+    assertStrictEquals(isDataDescriptor(undefined), false);
+  });
+
+  it("[[Construct]] throws an error", () => {
+    assertThrows(() => new isDataDescriptor({}));
+  });
+
+  describe(".length", () => {
+    it("[[Get]] returns the correct length", () => {
+      assertStrictEquals(isDataDescriptor.length, 1);
+    });
+  });
+
+  describe(".name", () => {
+    it("[[Get]] returns the correct name", () => {
+      assertStrictEquals(isDataDescriptor.name, "isDataDescriptor");
+    });
+  });
+});
+
+describe("isFullyPopulatedDescriptor", () => {
+  it("[[Call]] returns false for a generic descriptor", () => {
+    assertStrictEquals(isFullyPopulatedDescriptor({}), false);
+  });
+
+  it("[[Get]] returns false for a non‐fully‐populated data descriptor", () => {
+    assertStrictEquals(
+      isFullyPopulatedDescriptor({ value: undefined }),
+      false,
+    );
+    assertStrictEquals(
+      isFullyPopulatedDescriptor({ writable: true }),
+      false,
+    );
+  });
+
+  it("[[Get]] returns true for a fully‐populated data descriptor", () => {
+    assertStrictEquals(
+      isFullyPopulatedDescriptor({
+        configurable: true,
+        enumerable: true,
+        value: undefined,
+        writable: true,
+      }),
+      true,
+    );
+  });
+
+  it("[[Get]] returns false for a non‐fully‐populated accessor descriptor", () => {
+    assertStrictEquals(
+      isFullyPopulatedDescriptor({ get: undefined }),
+      false,
+    );
+    assertStrictEquals(
+      isFullyPopulatedDescriptor({ set: undefined }),
+      false,
+    );
+  });
+
+  it("[[Get]] returns true for a fully‐populated accessor descriptor", () => {
+    assertStrictEquals(
+      isFullyPopulatedDescriptor({
+        configurable: true,
+        enumerable: true,
+        get: undefined,
+        set: undefined,
+      }),
+      true,
+    );
+  });
+
+  it("[[Get]] returns false for undefined", () => {
+    assertStrictEquals(isFullyPopulatedDescriptor(undefined), false);
+  });
+
+  it("[[Construct]] throws an error", () => {
+    assertThrows(() => new isFullyPopulatedDescriptor({}));
+  });
+
+  describe(".length", () => {
+    it("[[Get]] returns the correct length", () => {
+      assertStrictEquals(isFullyPopulatedDescriptor.length, 1);
+    });
+  });
+
+  describe(".name", () => {
+    it("[[Get]] returns the correct name", () => {
+      assertStrictEquals(
+        isFullyPopulatedDescriptor.name,
+        "isFullyPopulatedDescriptor",
+      );
+    });
+  });
+});
+
+describe("isGenericDescriptor", () => {
+  it("[[Call]] returns true for a generic descriptor", () => {
+    assertStrictEquals(isGenericDescriptor({}), true);
+  });
+
+  it("[[Get]] returns false for a data descriptor", () => {
+    assertStrictEquals(
+      isGenericDescriptor({ value: undefined }),
+      false,
+    );
+    assertStrictEquals(isGenericDescriptor({ writable: true }), false);
+  });
+
+  it("[[Get]] returns false for an accessor descriptor", () => {
+    assertStrictEquals(isGenericDescriptor({ get: undefined }), false);
+    assertStrictEquals(isGenericDescriptor({ set: undefined }), false);
+  });
+
+  it("[[Get]] returns false for undefined", () => {
+    assertStrictEquals(isGenericDescriptor(undefined), false);
+  });
+
+  it("[[Construct]] throws an error", () => {
+    assertThrows(() => new isGenericDescriptor({}));
+  });
+
+  describe(".length", () => {
+    it("[[Get]] returns the correct length", () => {
+      assertStrictEquals(isGenericDescriptor.length, 1);
+    });
+  });
+
+  describe(".name", () => {
+    it("[[Get]] returns the correct name", () => {
+      assertStrictEquals(
+        isGenericDescriptor.name,
+        "isGenericDescriptor",
+      );
+    });
+  });
+});
+
+describe("isPropertyDescriptorRecord", () => {
+  it("[[Call]] returns true for objects created by toPropertyDescriptor", () => {
+    assertStrictEquals(
+      isPropertyDescriptorRecord(toPropertyDescriptor({})),
+      true,
+    );
+  });
+
+  it("[[Get]] returns false for other objects", () => {
+    assertStrictEquals(
+      isPropertyDescriptorRecord(Object.create(null)),
+      false,
+    );
+  });
+
+  it("[[Get]] returns false for undefined", () => {
+    assertStrictEquals(isPropertyDescriptorRecord(undefined), false);
+  });
+
+  it("[[Construct]] throws an error", () => {
+    assertThrows(() => new isPropertyDescriptorRecord({}));
+  });
+
+  describe(".length", () => {
+    it("[[Get]] returns the correct length", () => {
+      assertStrictEquals(isPropertyDescriptorRecord.length, 1);
+    });
+  });
+
+  describe(".name", () => {
+    it("[[Get]] returns the correct name", () => {
+      assertStrictEquals(
+        isPropertyDescriptorRecord.name,
+        "isPropertyDescriptorRecord",
+      );
+    });
   });
 });
 
@@ -747,6 +1034,278 @@ describe("toPrimitive", () => {
   });
 });
 
+describe("toPropertyDescriptor", () => {
+  it("[[Call]] creates a new property descriptor record", () => {
+    const obj = {};
+    const desc = toPropertyDescriptor(obj);
+    assertEquals(obj, desc);
+    assertNotStrictEquals(obj, desc);
+  });
+
+  it("[[Call]] coerces the values", () => {
+    assertEquals(
+      toPropertyDescriptor({
+        configurable: undefined,
+        enumerable: undefined,
+        writable: undefined,
+      }),
+      { configurable: false, enumerable: false, writable: false },
+    );
+  });
+
+  it("[[Construct]] throws for primitives", () => {
+    assertThrows(() => toPropertyDescriptor(undefined));
+    assertThrows(() => toPropertyDescriptor("failure"));
+  });
+
+  it("[[Construct]] throws an error", () => {
+    assertThrows(() => new toPropertyDescriptor({}));
+  });
+
+  describe(".length", () => {
+    it("[[Get]] returns the correct length", () => {
+      assertStrictEquals(toPropertyDescriptor.length, 1);
+    });
+  });
+
+  describe(".name", () => {
+    it("[[Get]] returns the correct name", () => {
+      assertStrictEquals(
+        toPropertyDescriptor.name,
+        "toPropertyDescriptor",
+      );
+    });
+  });
+
+  describe("~configurable", () => {
+    it("[[DefineOwnProperty]] coerces to a boolean", () => {
+      const desc = toPropertyDescriptor({});
+      Object.defineProperty(desc, "configurable", {});
+      assertStrictEquals(desc.configurable, false);
+    });
+
+    it("[[DefineOwnProperty]] throws for accessor properties", () => {
+      const desc = toPropertyDescriptor({});
+      assertThrows(() =>
+        Object.defineProperty(desc, "configurable", { get: undefined })
+      );
+    });
+
+    it("[[Set]] coerces to a boolean", () => {
+      const desc = toPropertyDescriptor({});
+      desc.configurable = undefined;
+      assertStrictEquals(desc.configurable, false);
+    });
+
+    it("[[Delete]] works", () => {
+      const desc = toPropertyDescriptor({ configurable: false });
+      delete desc.configurable;
+      assert(!("configurable" in desc));
+    });
+  });
+
+  describe("~enumerable", () => {
+    it("[[DefineOwnProperty]] coerces to a boolean", () => {
+      const desc = toPropertyDescriptor({});
+      Object.defineProperty(desc, "enumerable", {});
+      assertStrictEquals(desc.enumerable, false);
+    });
+
+    it("[[DefineOwnProperty]] throws for accessor properties", () => {
+      const desc = toPropertyDescriptor({});
+      assertThrows(() =>
+        Object.defineProperty(desc, "enumerable", { get: undefined })
+      );
+    });
+
+    it("[[Set]] coerces to a boolean", () => {
+      const desc = toPropertyDescriptor({});
+      desc.enumerable = undefined;
+      assertStrictEquals(desc.enumerable, false);
+    });
+
+    it("[[Delete]] works", () => {
+      const desc = toPropertyDescriptor({ enumerable: false });
+      delete desc.enumerable;
+      assert(!("enumerable" in desc));
+    });
+  });
+
+  describe("~get", () => {
+    it("[[DefineOwnProperty]] works", () => {
+      const desc = toPropertyDescriptor({});
+      Object.defineProperty(desc, "get", {});
+      assertStrictEquals(desc.get, undefined);
+    });
+
+    it("[[DefineOwnProperty]] throws for accessor properties", () => {
+      const desc = toPropertyDescriptor({});
+      assertThrows(() =>
+        Object.defineProperty(desc, "get", { get: undefined })
+      );
+    });
+
+    it("[[DefineOwnProperty]] throws if not callable or undefined", () => {
+      const desc = toPropertyDescriptor({});
+      assertThrows(
+        () => Object.defineProperty(desc, "get", { value: null }),
+      );
+    });
+
+    it("[[DefineOwnProperty]] throws if a data property is defined", () => {
+      const desc = toPropertyDescriptor({ value: undefined });
+      assertThrows(() => Object.defineProperty(desc, "get", {}));
+    });
+
+    it("[[Set]] works", () => {
+      const desc = toPropertyDescriptor({});
+      const fn = () => {};
+      desc.get = fn;
+      assertStrictEquals(desc.get, fn);
+    });
+
+    it("[[Set]] throws if not callable or undefined", () => {
+      const desc = toPropertyDescriptor({});
+      assertThrows(() => desc.get = null);
+    });
+
+    it("[[Set]] throws if a data property is defined", () => {
+      const desc = toPropertyDescriptor({ value: undefined });
+      assertThrows(() => desc.get = undefined);
+    });
+
+    it("[[Delete]] works", () => {
+      const desc = toPropertyDescriptor({ get: undefined });
+      delete desc.get;
+      assert(!("get" in desc));
+    });
+  });
+
+  describe("~set", () => {
+    it("[[DefineOwnProperty]] works", () => {
+      const desc = toPropertyDescriptor({});
+      Object.defineProperty(desc, "set", {});
+      assertStrictEquals(desc.set, undefined);
+    });
+
+    it("[[DefineOwnProperty]] throws for accessor properties", () => {
+      const desc = toPropertyDescriptor({});
+      assertThrows(() =>
+        Object.defineProperty(desc, "set", { get: undefined })
+      );
+    });
+
+    it("[[DefineOwnProperty]] throws if not callable or undefined", () => {
+      const desc = toPropertyDescriptor({});
+      assertThrows(
+        () => Object.defineProperty(desc, "set", { value: null }),
+      );
+    });
+
+    it("[[DefineOwnProperty]] throws if a data property is defined", () => {
+      const desc = toPropertyDescriptor({ value: undefined });
+      assertThrows(() => Object.defineProperty(desc, "set", {}));
+    });
+
+    it("[[Set]] works", () => {
+      const desc = toPropertyDescriptor({});
+      const fn = (_) => {};
+      desc.set = fn;
+      assertStrictEquals(desc.set, fn);
+    });
+
+    it("[[Set]] throws if not callable or undefined", () => {
+      const desc = toPropertyDescriptor({});
+      assertThrows(() => desc.set = null);
+    });
+
+    it("[[Set]] throws if a data property is defined", () => {
+      const desc = toPropertyDescriptor({ value: undefined });
+      assertThrows(() => desc.set = undefined);
+    });
+
+    it("[[Delete]] works", () => {
+      const desc = toPropertyDescriptor({ set: undefined });
+      delete desc.set;
+      assert(!("set" in desc));
+    });
+  });
+
+  describe("~value", () => {
+    it("[[DefineOwnProperty]] works", () => {
+      const desc = toPropertyDescriptor({});
+      Object.defineProperty(desc, "value", {});
+      assertStrictEquals(desc.value, undefined);
+    });
+
+    it("[[DefineOwnProperty]] throws for accessor properties", () => {
+      const desc = toPropertyDescriptor({});
+      assertThrows(() =>
+        Object.defineProperty(desc, "value", { get: undefined })
+      );
+    });
+
+    it("[[DefineOwnProperty]] throws if an accessor property is defined", () => {
+      const desc = toPropertyDescriptor({ get: undefined });
+      assertThrows(() => Object.defineProperty(desc, "value", {}));
+    });
+
+    it("[[Set]] works", () => {
+      const desc = toPropertyDescriptor({});
+      desc.value = "success";
+      assertStrictEquals(desc.value, "success");
+    });
+
+    it("[[Set]] throws if an accessor property is defined", () => {
+      const desc = toPropertyDescriptor({ get: undefined });
+      assertThrows(() => desc.value = null);
+    });
+
+    it("[[Delete]] works", () => {
+      const desc = toPropertyDescriptor({ value: undefined });
+      delete desc.value;
+      assert(!("value" in desc));
+    });
+  });
+
+  describe("~writable", () => {
+    it("[[DefineOwnProperty]] coerces to a boolean", () => {
+      const desc = toPropertyDescriptor({});
+      Object.defineProperty(desc, "writable", {});
+      assertStrictEquals(desc.writable, false);
+    });
+
+    it("[[DefineOwnProperty]] throws for accessor properties", () => {
+      const desc = toPropertyDescriptor({});
+      assertThrows(() =>
+        Object.defineProperty(desc, "writable", { get: undefined })
+      );
+    });
+
+    it("[[DefineOwnProperty]] throws if an accessor property is defined", () => {
+      const desc = toPropertyDescriptor({ get: undefined });
+      assertThrows(() => Object.defineProperty(desc, "writable", {}));
+    });
+
+    it("[[Set]] coerces to a boolean", () => {
+      const desc = toPropertyDescriptor({});
+      desc.writable = undefined;
+      assertStrictEquals(desc.writable, false);
+    });
+
+    it("[[Set]] throws if an accessor property is defined", () => {
+      const desc = toPropertyDescriptor({ get: undefined });
+      assertThrows(() => desc.writable = false);
+    });
+
+    it("[[Delete]] works", () => {
+      const desc = toPropertyDescriptor({ writable: false });
+      delete desc.writable;
+      assert(!("writable" in desc));
+    });
+  });
+});
+
 describe("type", () => {
   it('[[Call]] returns "null" for null', () => {
     assertStrictEquals(type(null), "null");
@@ -784,3 +1343,21 @@ describe("type", () => {
     });
   });
 });
+
+describe("Ε", () => {
+  it("[[Get]] is ε", () => {
+    assertStrictEquals(Ε, Number.EPSILON);
+  });
+});
+
+describe("Π", () => {
+  it("[[Get]] is π", () => {
+    assertStrictEquals(Π, Math.PI);
+  });
+});
+
+describe("ℇ", () => {
+  it("[[Get]] is ℇ", () => {
+    assertStrictEquals(ℇ, Math.E);
+  });
+});
This page took 0.143811 seconds and 4 git commands to generate.