+
+/**
+ * Returns the primitive value of the provided object per its
+ * `toString` and `valueOf` methods.
+ *
+ * If the provided hint is "string", then `toString` takes precedence;
+ * otherwise, `valueOf` does.
+ *
+ * Throws an error if both of these methods are not callable or do not
+ * return a primitive.
+ */
+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");
+};
+
+/**
+ * Returns the provided value converted to a primitive, or throws if
+ * no such conversion is possible.
+ *
+ * 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.
+ */
+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 $;
+ }
+};
+
+/**
+ * Returns the property key (symbol or string) corresponding to the
+ * provided value.
+ */
+export const toPropertyKey = ($) => {
+ const key = toPrimitive($, "string");
+ return typeof key == "symbol" ? key : `${key}`;
+};