X-Git-Url: https://git.ladys.computer/Pisces/blobdiff_plain/0fb1e53a1e78a6bf490cd95cf92a744d3ac6171f..50ab30fc3a257e46da6b8bdc0dad054f729e16e1:/object.js diff --git a/object.js b/object.js index 9c0c973..aa3538d 100644 --- a/object.js +++ b/object.js @@ -7,15 +7,12 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at . +import { bind, call, createArrowFunction } from "./function.js"; import { - bind, - call, - createArrowFunction, - toFunctionName, -} from "./function.js"; -import { + IS_CONCAT_SPREADABLE, ITERATOR, SPECIES, + toFunctionName, toLength, toPrimitive, type, @@ -102,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. @@ -810,6 +496,32 @@ export const isArraylikeObject = ($) => { } }; +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 is extensible. *