This proposal introduces built-in composites: frozen, ordinary objects with a null prototype that are interned within a realm, so that two composites created from the same set of named values are the same object (identical under SameValue, and therefore under ===).
Because composites are interned, they can be used as Map keys and Set values with value-based equality without any change to Map, Set, or the equality operators:
A composite object is an ordinary object with a [[Composite]] internal slot whose value is undefined. The only specified use of [[Composite]] is to distinguish composite objects from other objects. Composite objects are only created by FindOrCreateComposite (3.5), which is the only operation that creates an object with this slot. An object's status as a composite object cannot be forged: because only the presence of the internal slot matters, an object such as a Proxy exotic object whose target is a composite object is not itself a composite object.
Every composite object has a [[Prototype]] of null, has [[Extensible]] set to false, and has only own data properties whose [[Writable]] and [[Configurable]] attributes are false and whose [[Enumerable]] attribute is true. All keys of a composite object are Strings.
Composite objects are interned: within a realm, there is at most one composite object for any given set of key/value pairs (see 3.7). Two composite objects created by the same realm that would have the same key/value pairs are therefore the same object.
Values are read from arg in the order the keys are reported by arg.[[OwnPropertyKeys]](). Getters are therefore invoked exactly once each, eagerly, during this operation. The order in which the resulting properties appear on the composite object does not depend on this order; see FindOrCreateComposite (3.5).
Note 2
Only own enumerable properties of arg contribute to the composite; inherited and non-enumerable properties are ignored. An own enumerable Symbol-keyed property throws a TypeError; a non-enumerable one is ignored.
Note 3
A composite object from another realm is interned like any other object, giving this realm's equivalent. Interning is shallow: composite values within arg are not themselves re-interned.
Note 4
Step 4 is unobservable; the remaining steps would return arg anyway. It is stated so that having such an optimization is explicitly compliant.
Note 5
By default, the Composite function normalizes -0𝔽 to +0𝔽, unless arg is itself already a composite; in which case the default changes to preserve -0𝔽. The preserveNegativeZero property of the options object allows the caller to explicitly select if -0𝔽 should be preserved or not.
The abstract operation IsComposite takes argument value (an ECMAScript language value) and returns a Boolean. It returns true if value is a composite object. It performs the following steps when called:
If value has a [[Composite]] internal slot, return true.
Return false.
Note
Composite Registries are per-realm (3.7), but this operation is not: it returns true for a composite object created by any realm, not only the current one.
The abstract operation CanonicalizeCompositeValue takes arguments value (an ECMAScript language value) and preserveNegativeZero (a Boolean) and returns an ECMAScript language value. It returns a canonical representative of value. It performs the following steps when called:
If value is -0𝔽 and preserveNegativeZero is false, return +0𝔽.
If value is NaN, return an implementation chosen NaN value. An implementation must return the same NaN value for every NaN argument, so that all NaN values returned from this operations are not implementation distinguishable (see NumericToRawBytes) from each other.
Return value.
Note
This specification recognizes a single NaN value, so step two is observable only where an implementation preserves distinct NaN bit patterns (see NumericToRawBytes). Without this step, comparing the bytes of a returned composite's NaN against those supplied could reveal whether the composite was newly created or already interned, exposing information about the internal registry; canonicalizing NaN keeps that status private.
3.3 Composite Entry Records
A Composite Entry Record is a Record value used to describe a single key/value pair of a composite object.
Composite Entry Records have the fields listed in Table 2.
The abstract operation CompositeCanonicalOrder takes argument entries (a List of Composite Entry Records) and returns a List of Composite Entry Records. It returns entries ordered into the canonical key order used by composite objects. This order does not depend on the order in which the entries were provided, giving composites a canonical form. It performs the following steps when called:
This is the same key order ordinary objects use for their own String keys (array-index keys in ascending numeric order, then the rest), except that composites order the remaining String keys lexicographically rather than by creation order.
3.5 FindOrCreateComposite ( entries )
The abstract operation FindOrCreateComposite takes argument entries (a List of Composite Entry Records) and returns a composite object. It returns the current Realm Record's interned composite object for the key/value pairs described by entries, creating it if it does not already exist. It is assumed that no two elements of entries have the same [[Key]], and that each element's [[Value]] is already the result of CanonicalizeCompositeValue (3.2). It performs the following steps when called:
The current Realm Record is that of the Composite function that was called, not that of its caller.
Note 2
The search over registry is only intended to describe the required observable result of interning; it is not intended to be a viable implementation model. Interning must be implemented using either hash tables or other mechanisms that, on average, provide interning times that are sublinear on the number of composite objects in the registry.
3.6 CompositeMatch ( composite, sortedEntries )
The abstract operation CompositeMatch takes arguments composite (a composite object) and sortedEntries (a List of Composite Entry Records) and returns a Boolean. It returns true if the existing composite objectcomposite has exactly the key/value pairs described by sortedEntries. It performs the following steps when called:
If SameValue(current, entry.[[Value]]) is false, return false.
Set i to i + 1.
Return true.
3.7 The Composite Registry
Each realm has an associated Composite Registry: a List of Objects, initially empty. The objects it contains are only composite objects created by that realm (2). Objects are added to it only by FindOrCreateComposite (3.5).
Note 1
Membership in the registry does not keep a composite object alive; an implementation may remove one once it is no longer live. This removal is unobservable, since composite objects cannot be held weakly (see 4.1) and so cannot be placed in a WeakMap, WeakRef, WeakSet, or FinalizationRegistry.
Note 2
The Composite Registry is per-realm, not per-agent: two realms of the same agent (for example, two same-origin iframes) never produce the same composite object, unlike symbols obtained from Symbol.for. A composite object has a [[Prototype]] of null and so bears no relationship to the realm that created it; passing one to another realm's Composite function gives that realm's equivalent (2.1.1).
4 Modifications to Existing Abstract Operations
4.1 CanBeHeldWeakly ( arg )
The abstract operation CanBeHeldWeakly takes argument arg (an ECMAScript language value) and returns a Boolean. It returns true if and only if arg is suitable to be used as a key of a WeakMap, added to a WeakSet, used as the target or unregister token of a FinalizationRegistry, or held by a WeakRef. Composite objects are excluded because they are interned: holding one weakly would, counter-intuitively, keep it alive in the registry, which could leak memory. It performs the following steps when called:
Because WeakMap, WeakSet, WeakRef, and FinalizationRegistry all gate their arguments through CanBeHeldWeakly, this single change causes each of them to reject composite objects with a TypeError.
All Software contained in this document ("Software") is protected by copyright and is being made available under the "BSD License", included below. This Software may be subject to third party rights (rights from parties other than Ecma International), including patent rights, and no licenses under such third party rights are granted under this license even if the third party concerned is a member of Ecma International. SEE THE ECMA CODE OF CONDUCT IN PATENT MATTERS AVAILABLE AT https://ecma-international.org/memento/codeofconduct.htm FOR INFORMATION REGARDING THE LICENSING OF PATENT CLAIMS THAT ARE REQUIRED TO IMPLEMENT ECMA INTERNATIONAL STANDARDS.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
Neither the name of the authors nor Ecma International may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE ECMA INTERNATIONAL "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ECMA INTERNATIONAL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.