From: Lady Date: Thu, 28 Apr 2022 00:56:34 +0000 (-0700) Subject: toPrimitive and toPropertyKey X-Git-Tag: 0.1.0~14 X-Git-Url: https://git.ladys.computer/Pisces/commitdiff_plain/1f05716e1051f4f92528a755f1ec4cc8e8a62f78?hp=8c8953d378b20e194535e22214750367961b6963 toPrimitive and toPropertyKey --- diff --git a/object.js b/object.js index 502207e..d08f43a 100644 --- a/object.js +++ b/object.js @@ -40,3 +40,91 @@ export const isObject = ($) => { export const ordinaryHasInstance = Function.prototype.call.bind( Function.prototype[Symbol.hasInstance], ); + +/** + * 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}`; +};