/* @internal */ namespace ts { type GetIteratorCallback = | ReadonlyMapShim | undefined>(iterable: I) => IteratorShim< I extends ReadonlyMapShim ? [K, V] : I extends ReadonlySetShim ? T : I extends readonly (infer T)[] ? T : I extends undefined ? undefined : never>; type IteratorResultShim = | { value: T, done?: false } | { value: void, done: true }; interface IteratorShim { next(): IteratorResultShim; } interface ReadonlyMapShim { readonly size: number; get(key: K): V | undefined; has(key: K): boolean; keys(): IteratorShim; values(): IteratorShim; entries(): IteratorShim<[K, V]>; forEach(action: (value: V, key: K) => void): void; } interface MapShim extends ReadonlyMapShim { set(key: K, value: V): this; delete(key: K): boolean; clear(): void; } type MapShimConstructor = new (iterable?: readonly (readonly [K, V])[] | ReadonlyMapShim) => MapShim; interface ReadonlySetShim { readonly size: number; has(value: T): boolean; keys(): IteratorShim; values(): IteratorShim; entries(): IteratorShim<[T, T]>; forEach(action: (value: T, key: T) => void): void; } interface SetShim extends ReadonlySetShim { add(value: T): this; delete(value: T): boolean; clear(): void; } type SetShimConstructor = new (iterable?: readonly T[] | ReadonlySetShim) => SetShim; interface MapData { size: number; readonly head: MapEntry; tail: MapEntry; } interface MapEntry { readonly key?: K; value?: V; /** * Specifies the next entry in the linked list. */ next?: MapEntry; /** * Specifies the previous entry in the linked list. * Must be set when the entry is part of a Map/Set. * When 'undefined', iterators should skip the next entry. * This will be set to 'undefined' when an entry is deleted. * See https://github.com/Microsoft/TypeScript/pull/27292 for more information. */ prev?: MapEntry; } interface IteratorData { current?: MapEntry; selector: (key: K, value: V) => U; } function createMapData(): MapData { const sentinel: MapEntry = {}; sentinel.prev = sentinel; return { head: sentinel, tail: sentinel, size: 0 }; } function createMapEntry(key: K, value: V): MapEntry { return { key, value, next: undefined, prev: undefined }; } function sameValueZero(x: unknown, y: unknown) { // Treats -0 === 0 and NaN === NaN return x === y || x !== x && y !== y; } function getPrev(entry: MapEntry) { const prev = entry.prev; // Entries without a 'prev' have been removed from the map. // An entry whose 'prev' points to itself is the head of the list and is invalid here. if (!prev || prev === entry) throw new Error("Illegal state"); return prev; } function getNext(entry: MapEntry | undefined) { while (entry) { // Entries without a 'prev' have been removed from the map. Their 'next' // pointer should point to the previous entry prior to deletion and // that entry should be skipped to resume iteration. const skipNext = !entry.prev; entry = entry.next; if (skipNext) { continue; } return entry; } } function getEntry(data: MapData, key: K): MapEntry | undefined { // We walk backwards from 'tail' to prioritize recently added entries. // We skip 'head' because it is an empty entry used to track iteration start. for (let entry = data.tail; entry !== data.head; entry = getPrev(entry)) { if (sameValueZero(entry.key, key)) { return entry; } } } function addOrUpdateEntry(data: MapData, key: K, value: V): MapEntry | undefined { const existing = getEntry(data, key); if (existing) { existing.value = value; return; } const entry = createMapEntry(key, value); entry.prev = data.tail; data.tail.next = entry; data.tail = entry; data.size++; return entry; } function deleteEntry(data: MapData, key: K): MapEntry | undefined { // We walk backwards from 'tail' to prioritize recently added entries. // We skip 'head' because it is an empty entry used to track iteration start. for (let entry = data.tail; entry !== data.head; entry = getPrev(entry)) { // all entries in the map should have a 'prev' pointer. if (entry.prev === undefined) throw new Error("Illegal state"); if (sameValueZero(entry.key, key)) { if (entry.next) { entry.next.prev = entry.prev; } else { // an entry in the map without a 'next' pointer must be the 'tail'. if (data.tail !== entry) throw new Error("Illegal state"); data.tail = entry.prev; } entry.prev.next = entry.next; entry.next = entry.prev; entry.prev = undefined; data.size--; return entry; } } } function clearEntries(data: MapData) { let node = data.tail; while (node !== data.head) { const prev = getPrev(node); node.next = data.head; node.prev = undefined; node = prev; } data.head.next = undefined; data.tail = data.head; data.size = 0; } function forEachEntry(data: MapData, action: (value: V, key: K) => void) { let entry: MapEntry | undefined = data.head; while (entry) { entry = getNext(entry); if (entry) { action(entry.value!, entry.key!); } } } function forEachIteration(iterator: IteratorShim | undefined, action: (value: any) => void) { if (iterator) { for (let step = iterator.next(); !step.done; step = iterator.next()) { action(step.value); } } } function createIteratorData(data: MapData, selector: (key: K, value: V) => U): IteratorData { return { current: data.head, selector }; } function iteratorNext(data: IteratorData): IteratorResultShim { // Navigate to the next entry. data.current = getNext(data.current); if (data.current) { return { value: data.selector(data.current.key!, data.current.value!), done: false }; } else { return { value: undefined as never, done: true }; } } /* @internal */ export namespace ShimCollections { export function createMapShim(getIterator: GetIteratorCallback): MapShimConstructor { class MapIterator { private _data: IteratorData; constructor(data: MapData, selector: (key: K, value: V) => U) { this._data = createIteratorData(data, selector); } next() { return iteratorNext(this._data); } } return class Map implements MapShim { private _mapData = createMapData(); constructor(iterable?: readonly (readonly [K, V])[] | ReadonlyMapShim) { forEachIteration(getIterator(iterable), ([key, value]) => this.set(key, value)); } get size() { return this._mapData.size; } get(key: K): V | undefined { return getEntry(this._mapData, key)?.value; } set(key: K, value: V): this { return addOrUpdateEntry(this._mapData, key, value), this; } has(key: K): boolean { return !!getEntry(this._mapData, key); } delete(key: K): boolean { return !!deleteEntry(this._mapData, key); } clear(): void { clearEntries(this._mapData); } keys(): IteratorShim { return new MapIterator(this._mapData, (key, _value) => key); } values(): IteratorShim { return new MapIterator(this._mapData, (_key, value) => value); } entries(): IteratorShim<[K, V]> { return new MapIterator(this._mapData, (key, value) => [key, value]); } forEach(action: (value: V, key: K) => void): void { forEachEntry(this._mapData, action); } }; } export function createSetShim(getIterator: GetIteratorCallback): SetShimConstructor { class SetIterator { private _data: IteratorData; constructor(data: MapData, selector: (key: K, value: V) => U) { this._data = createIteratorData(data, selector); } next() { return iteratorNext(this._data); } } return class Set implements SetShim { private _mapData = createMapData(); constructor(iterable?: readonly T[] | ReadonlySetShim) { forEachIteration(getIterator(iterable), value => this.add(value)); } get size() { return this._mapData.size; } add(value: T): this { return addOrUpdateEntry(this._mapData, value, value), this; } has(value: T): boolean { return !!getEntry(this._mapData, value); } delete(value: T): boolean { return !!deleteEntry(this._mapData, value); } clear(): void { clearEntries(this._mapData); } keys(): IteratorShim { return new SetIterator(this._mapData, (key, _value) => key); } values(): IteratorShim { return new SetIterator(this._mapData, (_key, value) => value); } entries(): IteratorShim<[T, T]> { return new SetIterator(this._mapData, (key, value) => [key, value]); } forEach(action: (value: T, key: T) => void): void { forEachEntry(this._mapData, action); } }; } } }