This proposal introduces built-in composites: frozen, ordinary objects with a null prototype that are interned, 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:
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 an agent, there is at most one composite object for any given set of key/value pairs (see 3.7). Two composite objects 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
If arg is itself a composite object, the result is a composite object with the same key/value pairs, which by interning is arg itself. The options argument does not change the result.
Note 4
By default, the Composite function normalizes -0𝔽 to +0𝔽. If the options object provides a truthy preserveNegativeZero property, then -0𝔽 is preserved instead.
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:
This operation is defined in terms of searching the Composite Registry, but an implementation is free to use any faster mechanism that avoids such a search as long as the observable behaviour is equivalent.
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 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 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 ( c, sortedEntries )
The abstract operation CompositeMatch takes arguments c (a composite object) and sortedEntries (a List of Composite Entry Records) and returns a Boolean. It returns true if the existing composite objectc 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 agent has an associated Composite Registry: a List of Objects, initially empty. The objects it contains are exactly the composite objects of that agent (2). Objects are added to it only by FindOrCreateComposite (3.5) and are shared across all realms within the agent.
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-agent, not per-realm. As a result, composites are equal across realms within the same agent (for example, across same-origin iframes), similar to symbols obtained from Symbol.for. A composite object created via one realm's Composite function has a [[Prototype]] of null and so bears no relationship to the realm that created it; only the key/value pairs matter.
4 Modifications to Existing Abstract Operations
4.1 CanBeHeldWeakly ( v )
The abstract operation CanBeHeldWeakly takes argument v (an ECMAScript language value) and returns a Boolean. It returns true if and only if v 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.