/** * Effectively immutable wrappers for built-in collection types (`Map`, `Set`). * * These wrappers preserve the public collection surface and `instanceof` * behavior, but they intentionally do not carry native `Map` / `Set` internal * slots. Their data lives in module-private backing stores keyed by the wrapper * instance. That makes intrinsic mutators like `Map.prototype.set.call(...)` * and `Set.prototype.add.call(...)` fail with an incompatible receiver instead * of mutating hidden internal state on a frozen object. */ type MapBacking = Map; type SetBacking = Set; type MapBuilder = { readonly wrapper: FrozenMap; set(key: K, value: V): void; finish(): FrozenMap; }; type SetBuilder = { readonly wrapper: FrozenSet; add(value: T): void; finish(): FrozenSet; }; const MAP_BACKING = new WeakMap>(); const SET_BACKING = new WeakMap>(); const INTERNAL_MAP_BUILDER = Symbol("FrozenMapBuilder"); const INTERNAL_SET_BUILDER = Symbol("FrozenSetBuilder"); /** Helper for the mutator methods, which throws to signal a frozen-mutation attempt. */ function throwFrozenMutation(typeName: string): never { throw new TypeError(`Cannot mutate a ${typeName}`); } /** Helper for builders, which throws to signal a post-`finish()` mutation attempt. */ function throwFinalizedBuilderMutation(typeName: string): never { throw new TypeError(`Cannot mutate a finalized ${typeName} builder`); } /** * Helper for the `FrozenMap` methods, which fetches the backing `Map` for the * given wrapper instance. Throws if `value` is not a recognized `FrozenMap` * receiver (e.g. when an intrinsic mutator was called via `call(...)`). */ function getMapBacking(value: object): MapBacking { const backing = MAP_BACKING.get(value); if (!backing) { throw new TypeError("Incompatible FrozenMap receiver"); } return backing as MapBacking; } /** * Helper for the `FrozenSet` methods, which fetches the backing `Set` for the * given wrapper instance. Throws if `value` is not a recognized `FrozenSet` * receiver. */ function getSetBacking(value: object): SetBacking { const backing = SET_BACKING.get(value); if (!backing) { throw new TypeError("Incompatible FrozenSet receiver"); } return backing as SetBacking; } /** * Helper for the set-algebra methods, which iterates the values of a * `ReadonlySetLike`, invoking `callback` for each. */ function forEachSetLikeValue( setLike: ReadonlySetLike, callback: (value: T) => void, ): void { const iterator = setLike.keys(); while (true) { const next = iterator.next(); if (next.done) { return; } callback(next.value); } } /** * Effectively-immutable `Map` wrapper. Read methods delegate to a * module-private backing `Map`; mutator methods (`set()`, `delete()`, `clear()`, * etc.) throw. Instances are frozen at construction time (or at builder * `finish()` time, see `createBuilder()`). */ export class FrozenMap implements Map { /** * Constructs an instance from the given entries. The instance is frozen * unless `builderToken` matches the module-private builder symbol (used by * `createBuilder()` to allow staged population before freezing). */ constructor( entries?: Iterable | null, builderToken?: symbol, ) { MAP_BACKING.set(this, new Map(entries ?? undefined)); if (builderToken !== INTERNAL_MAP_BUILDER) { Object.freeze(this); } } /** * Returns a builder that can be used to populate a `FrozenMap` incrementally * before freezing it. Call `set()` to add entries, then `finish()` to freeze * the wrapper and return it. */ static createBuilder(): MapBuilder { const wrapper = new FrozenMap(undefined, INTERNAL_MAP_BUILDER); let finalized = false; const assertOpen = (): void => { if (finalized) { throwFinalizedBuilderMutation("FrozenMap"); } }; return { wrapper, set(key: K, value: V): void { assertOpen(); getMapBacking(wrapper).set(key, value); }, finish(): FrozenMap { finalized = true; Object.freeze(wrapper); return wrapper; }, }; } /** Same as `Map.prototype.size`. */ get size(): number { return getMapBacking(this).size; } /** Same as `Map.prototype[Symbol.toStringTag]`; always `"Map"`. */ get [Symbol.toStringTag](): string { return "Map"; } /** Same as `Map.prototype.get`. */ get(key: K): V | undefined { return getMapBacking(this).get(key); } /** Same as `Map.prototype.has`. */ has(key: K): boolean { return getMapBacking(this).has(key); } /** Same as `Map.prototype.entries`. */ entries(): ReturnType["entries"]> { return getMapBacking(this).entries(); } /** Same as `Map.prototype.keys`. */ keys(): ReturnType["keys"]> { return getMapBacking(this).keys(); } /** Same as `Map.prototype.values`. */ values(): ReturnType["values"]> { return getMapBacking(this).values(); } /** Same as `Map.prototype.forEach`. */ forEach( callbackfn: (value: V, key: K, map: Map) => void, thisArg?: unknown, ): void { getMapBacking(this).forEach((value, key) => { callbackfn.call(thisArg, value, key, this); }); } /** Same as `Map.prototype[Symbol.iterator]`. */ [Symbol.iterator](): ReturnType[typeof Symbol.iterator]> { return this.entries(); } /** Always throws (instance is frozen). */ set(_key: K, _value: V): this { throwFrozenMutation("FrozenMap"); } /** Always throws (instance is frozen). */ getOrInsert(_key: K, _defaultValue: V): V { throwFrozenMutation("FrozenMap"); } /** Always throws (instance is frozen). */ getOrInsertComputed(_key: K, _callback: (key: K) => V): V { throwFrozenMutation("FrozenMap"); } /** Always throws (instance is frozen). */ delete(_key: K): boolean { throwFrozenMutation("FrozenMap"); } /** Always throws (instance is frozen). */ clear(): void { throwFrozenMutation("FrozenMap"); } } Object.setPrototypeOf(FrozenMap.prototype, Map.prototype); Object.setPrototypeOf(FrozenMap, Map); /** * Effectively-immutable `Set` wrapper. Read methods and set-algebra methods * delegate to a module-private backing `Set`; mutator methods (`add()`, * `delete()`, `clear()`) throw. Instances are frozen at construction time (or at * builder `finish()` time, see `createBuilder()`). */ export class FrozenSet implements Set { /** * Constructs an instance from the given values. The instance is frozen * unless `builderToken` matches the module-private builder symbol (used by * `createBuilder()` to allow staged population before freezing). */ constructor(values?: Iterable | null, builderToken?: symbol) { SET_BACKING.set(this, new Set(values ?? undefined)); if (builderToken !== INTERNAL_SET_BUILDER) { Object.freeze(this); } } /** * Returns a builder that can be used to populate a `FrozenSet` incrementally * before freezing it. Call `add()` to add values, then `finish()` to freeze * the wrapper and return it. */ static createBuilder(): SetBuilder { const wrapper = new FrozenSet(undefined, INTERNAL_SET_BUILDER); let finalized = false; const assertOpen = (): void => { if (finalized) { throwFinalizedBuilderMutation("FrozenSet"); } }; return { wrapper, add(value: T): void { assertOpen(); getSetBacking(wrapper).add(value); }, finish(): FrozenSet { finalized = true; Object.freeze(wrapper); return wrapper; }, }; } /** Same as `Set.prototype.size`. */ get size(): number { return getSetBacking(this).size; } /** Same as `Set.prototype[Symbol.toStringTag]`; always `"Set"`. */ get [Symbol.toStringTag](): string { return "Set"; } /** Same as `Set.prototype.has`. */ has(value: T): boolean { return getSetBacking(this).has(value); } /** Same as `Set.prototype.entries`. */ entries(): ReturnType["entries"]> { return getSetBacking(this).entries(); } /** Same as `Set.prototype.keys`. */ keys(): ReturnType["keys"]> { return getSetBacking(this).keys(); } /** Same as `Set.prototype.values`. */ values(): ReturnType["values"]> { return getSetBacking(this).values(); } /** Same as `Set.prototype.forEach`. */ forEach( callbackfn: (value: T, key: T, set: Set) => void, thisArg?: unknown, ): void { getSetBacking(this).forEach((value) => { callbackfn.call(thisArg, value, value, this as Set); }); } /** Same as `Set.prototype[Symbol.iterator]`. */ [Symbol.iterator](): ReturnType[typeof Symbol.iterator]> { return this.values(); } /** Same as `Set.prototype.union`. Returns a new (mutable) `Set`. */ union(other: ReadonlySetLike): Set { const result = new Set(this.values()); forEachSetLikeValue(other, (value) => { result.add(value); }); return result; } /** Same as `Set.prototype.intersection`. Returns a new (mutable) `Set`. */ intersection(other: ReadonlySetLike): Set { const result = new Set(); for (const value of this.values()) { if (other.has(value as unknown as U)) { result.add(value as T & U); } } return result; } /** Same as `Set.prototype.difference`. Returns a new (mutable) `Set`. */ difference(other: ReadonlySetLike): Set { const result = new Set(); for (const value of this.values()) { if (!other.has(value as unknown as U)) { result.add(value); } } return result; } /** Same as `Set.prototype.symmetricDifference`. Returns a new (mutable) `Set`. */ symmetricDifference(other: ReadonlySetLike): Set { const result = new Set(this.values()); forEachSetLikeValue(other, (value) => { if (result.has(value)) { result.delete(value); } else { result.add(value); } }); return result; } /** Same as `Set.prototype.isSubsetOf`. */ isSubsetOf(other: Parameters["isSubsetOf"]>[0]): boolean { for (const value of this.values()) { if (!other.has(value)) { return false; } } return true; } /** Same as `Set.prototype.isSupersetOf`. */ isSupersetOf(other: Parameters["isSupersetOf"]>[0]): boolean { let result = true; forEachSetLikeValue(other, (value) => { if (!this.has(value as T)) { result = false; } }); return result; } /** Same as `Set.prototype.isDisjointFrom`. */ isDisjointFrom(other: Parameters["isDisjointFrom"]>[0]): boolean { let result = true; forEachSetLikeValue(other, (value) => { if (this.has(value as T)) { result = false; } }); return result; } /** Always throws (instance is frozen). */ add(_value: T): this { throwFrozenMutation("FrozenSet"); } /** Always throws (instance is frozen). */ delete(_value: T): boolean { throwFrozenMutation("FrozenSet"); } /** Always throws (instance is frozen). */ clear(): void { throwFrozenMutation("FrozenSet"); } } Object.setPrototypeOf(FrozenSet.prototype, Set.prototype); Object.setPrototypeOf(FrozenSet, Set);