Skip to content

Commit f73d906

Browse files
author
Andy Hanson
committed
Use a single ShimMap class, and indicate that iteration always yields string keys (which it did before too)
1 parent 14701e3 commit f73d906

2 files changed

Lines changed: 71 additions & 90 deletions

File tree

src/compiler/collections.ts

Lines changed: 66 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
/* @internal */
33
namespace ts {
44
// The global Map object. This may not be available, so we must test for it.
5-
declare const Map: NumberMapConstructor & StringMapConstructor | undefined;
5+
declare const Map: { new<K, V>(pairs?: [K, V][]): Map<K, V> } | undefined;
66
const usingNativeMaps = typeof Map !== "undefined";
77
// tslint:disable-next-line:no-in-operator
88
const fullyFeaturedMaps = usingNativeMaps && "keys" in Map.prototype && "values" in Map.prototype && "entries" in Map.prototype;
@@ -19,99 +19,63 @@ namespace ts {
1919
next(): { value: T, done: false } | { value: never, done: true };
2020
}
2121

22-
export interface NumberMapConstructor {
23-
/**
24-
* Creates a new Map with number keys.
25-
* If `pairs` is provided, each [key, value] pair will be added to the map.
26-
*/
27-
new<K extends number, V>(pairs?: [K, V][]): Map<K, V>;
28-
}
29-
3022
/**
31-
* In runtimes without Maps, this is implemented using a sparse array.
32-
* This is generic over the key type because it is usually an enum.
23+
* Provides Map-like functionality for ES5 runtimes.
24+
* This is intentionally *not* a full Map shim, and doesn't provide iterators (which aren't available for IE Maps anyway).
25+
* We can only efficiently support strings and number keys, and iteration will always yield stringified keys.
3326
*/
34-
export const NumberMap: NumberMapConstructor = usingNativeMaps ? Map : class ShimNumberMap<K extends number, V> implements Map<K, V> {
35-
private data: { [key: number]: V } = [];
27+
class ShimMap<K extends string | number, V> implements Map<K, V> {
28+
private data = createDictionaryModeObject<V>();
29+
30+
/*
31+
So long as `K extends string | number`, we can cast `key as string` and insert it into the map.
32+
However, `forEach` will iterate over strings because values are stringified before being put in the map.
33+
*/
3634

3735
constructor(pairs?: [K, V][]) {
3836
if (pairs) {
3937
for (const [key, value] of pairs) {
40-
this.data[key as number] = value;
38+
this.data[key as string] = value;
4139
}
4240
}
4341
}
4442

45-
clear() {
46-
this.data = [];
43+
clear(): void {
44+
this.data = createDictionaryModeObject<V>();
4745
}
4846

49-
delete(key: K) {
50-
delete this.data[key as number];
47+
delete(key: K): void {
48+
delete this.data[key as string];
5149
}
5250

53-
get(key: K) {
54-
return this.data[key as number];
51+
get(key: K): V {
52+
return this.data[key as string];
5553
}
5654

57-
has(key: K) {
55+
has(key: K): boolean {
5856
// tslint:disable-next-line:no-in-operator
59-
return (key as number) in this.data;
57+
return (key as string) in this.data;
6058
}
6159

62-
set(key: K, value: V) {
63-
this.data[key as number] = value;
60+
set(key: K, value: V): void {
61+
this.data[key as string] = value;
6462
}
6563

66-
forEach(action: (value: V, key: K) => void) {
64+
forEach(action: (value: V, key: string) => void): void {
6765
for (const key in this.data) {
68-
action(this.data[key], key as any as K);
66+
action(this.data[key], key);
6967
}
7068
}
71-
};
72-
73-
export interface StringMapConstructor {
74-
new<T>(pairs?: [string, T][]): Map<string, T>;
7569
}
76-
/** In runtimes without Maps, this is implemented using an object. */
77-
export const StringMap: StringMapConstructor = usingNativeMaps ? Map : class ShimStringMap<T> implements Map<string, T> {
78-
private data = createDictionaryModeObject<T>();
79-
80-
constructor(pairs?: [string, T][]) {
81-
if (pairs) {
82-
for (const [key, value] of pairs) {
83-
this.data[key] = value;
84-
}
85-
}
86-
}
87-
88-
clear() {
89-
this.data = createDictionaryModeObject<T>();
90-
}
91-
92-
delete(key: string) {
93-
delete this.data[key];
94-
}
95-
96-
get(key: string) {
97-
return this.data[key];
98-
}
9970

100-
has(key: string) {
101-
// tslint:disable-next-line:no-in-operator
102-
return key in this.data;
103-
}
104-
105-
set(key: string, value: T) {
106-
this.data[key] = value;
107-
}
71+
/**
72+
* In runtimes without Maps, this is implemented using an object.
73+
* `pairs` is an optional list of entries to add to the new map.
74+
*/
75+
export const StringMap: { new<T>(pairs?: [string, T][]): Map<string, T>; } = usingNativeMaps ? Map : ShimMap;
10876

109-
forEach(f: (value: T, key: string) => void) {
110-
for (const key in this.data) {
111-
f(this.data[key], key);
112-
}
113-
}
114-
};
77+
/** This is generic over the key type because it is usually an enum. */
78+
export const NumberMap: { new<K extends number, V>(pairs?: [K, V][]): Map<K, V> } = usingNativeMaps ? Map : ShimMap;
11579

11680
const createObject = Object.create;
11781
function createDictionaryModeObject<T>(): MapLike<T> {
@@ -126,9 +90,12 @@ namespace ts {
12690
return map;
12791
}
12892

129-
/** Iterates over entries in the map, returning the first output of `getResult` that is not `undefined`. */
130-
export const findInMap: <K, V, U>(map: Map<K, V>, getResult: (value: V, key: K) => U | undefined) => U | undefined = fullyFeaturedMaps
131-
? <K, V, U>(map: ES6Map<K, V>, f: (value: V, key: K) => U | undefined) => {
93+
/**
94+
* Iterates over entries in the map, returning the first output of `getResult` that is not `undefined`.
95+
* Only works for strings because shims iterate with `for-in`.
96+
*/
97+
export const findInMap: <V, U>(map: Map<string, V>, getResult: (value: V, key: string) => U | undefined) => U | undefined = fullyFeaturedMaps
98+
? <V, U>(map: ES6Map<string, V>, f: (value: V, key: string) => U | undefined) => {
13299
const iter = map.entries();
133100
while (true) {
134101
const { value: pair, done } = iter.next();
@@ -142,7 +109,7 @@ namespace ts {
142109
}
143110
}
144111
}
145-
: <K, V, U>(map: Map<K, V>, f: (value: V, key: K) => U | undefined) => {
112+
: <V, U>(map: Map<string, V>, f: (value: V, key: string) => U | undefined) => {
146113
let result: U | undefined;
147114
map.forEach((value, key) => {
148115
if (result === undefined)
@@ -151,22 +118,28 @@ namespace ts {
151118
return result;
152119
};
153120

154-
/** Whether `predicate` is true for at least one entry in the map. */
155-
export const someInMap: <K, V>(map: Map<K, V>, predicate: (value: V, key: K) => boolean) => boolean = fullyFeaturedMaps
156-
? <K, V>(map: ES6Map<K, V>, predicate: (value: V, key: K) => boolean) =>
121+
/**
122+
* Whether `predicate` is true for at least one entry in the map.
123+
* Only works for strings because shims iterate with `for-in`.
124+
*/
125+
export const someInMap: <V>(map: Map<string, V>, predicate: (value: V, key: string) => boolean) => boolean = fullyFeaturedMaps
126+
? <V>(map: ES6Map<string, V>, predicate: (value: V, key: string) => boolean) =>
157127
someInIterator(map.entries(), ([key, value]) => predicate(value, key))
158-
: <K, V>(map: Map<K, V>, predicate: (value: V, key: K) => boolean) => {
128+
: <V>(map: Map<string, V>, predicate: (value: V, key: string) => boolean) => {
159129
let found = false;
160130
map.forEach((value, key) => {
161131
found = found || predicate(value, key);
162132
});
163133
return found;
164134
};
165135

166-
/** Whether `predicate` is true for at least one key in the map. */
167-
export const someKeyInMap: <K>(map: Map<K, any>, predicate: (key: K) => boolean) => boolean = fullyFeaturedMaps
168-
? <K>(map: ES6Map<K, any>, predicate: (key: K) => boolean) => someInIterator(map.keys(), predicate)
169-
: <K>(map: Map<K, any>, predicate: (key: K) => boolean) =>
136+
/**
137+
* Whether `predicate` is true for at least one key in the map.
138+
* Only works for strings because shims iterate with `for-in`.
139+
*/
140+
export const someKeyInMap: (map: Map<string, any>, predicate: (key: string) => boolean) => boolean = fullyFeaturedMaps
141+
? (map: ES6Map<string, any>, predicate: (key: string) => boolean) => someInIterator(map.keys(), predicate)
142+
: (map: Map<string, any>, predicate: (key: string) => boolean) =>
170143
someInMap(map, (_value, key) => predicate(key));
171144

172145
/** Whether `predicate` is true for at least one value in the map. */
@@ -190,19 +163,20 @@ namespace ts {
190163
/**
191164
* Equivalent to the ES6 code:
192165
* `for (const key of map.keys()) action(key);`
166+
* Only works for strings because shims iterate with `for-in`.
193167
*/
194-
export const forEachKeyInMap: <K>(map: Map<K, any>, action: (key: K) => void) => void = fullyFeaturedMaps
195-
? <K>(map: ES6Map<K, any>, f: (key: K) => void) => {
196-
const iter: Iterator<K> = map.keys();
168+
export const forEachKeyInMap: (map: Map<string, any>, action: (key: string) => void) => void = fullyFeaturedMaps
169+
? (map: ES6Map<string, any>, action: (key: string) => void) => {
170+
const iter: Iterator<string> = map.keys();
197171
while (true) {
198172
const { value: key, done } = iter.next();
199173
if (done) {
200174
return;
201175
}
202-
f(key);
176+
action(key);
203177
}
204178
}
205-
: <K>(map: Map<K, any>, action: (key: K) => void) => {
179+
: (map: Map<string, any>, action: (key: string) => void) => {
206180
map.forEach((_value, key) => action(key));
207181
};
208182

@@ -310,14 +284,17 @@ namespace ts {
310284
* @param target A map to which properties should be copied.
311285
*/
312286
export function copyMapEntriesFromTo<K, V>(source: Map<K, V>, target: Map<K, V>): void {
313-
source.forEach((value, key) => {
287+
source.forEach((value: V, key: K) => {
314288
target.set(key, value);
315289
});
316290
}
317291

318-
/** Equivalent to `Array.from(map.keys())`. */
319-
export function keysOfMap<K>(map: Map<K, any>): K[] {
320-
const keys: K[] = [];
292+
/**
293+
* Equivalent to `Array.from(map.keys())`.
294+
* Only works for strings because shims iterate with `for-in`.
295+
*/
296+
export function keysOfMap(map: Map<string, any>): string[] {
297+
const keys: string[] = [];
321298
forEachKeyInMap(map, key => { keys.push(key); });
322299
return keys;
323300
}
@@ -442,7 +419,7 @@ namespace ts {
442419
}
443420

444421
/** True if the maps have the same keys and values. */
445-
export function mapsAreEqual<K, V>(left: Map<K, V>, right: Map<K, V>, valuesAreEqual?: (left: V, right: V) => boolean): boolean {
422+
export function mapsAreEqual<V>(left: Map<string, V>, right: Map<string, V>, valuesAreEqual?: (left: V, right: V) => boolean): boolean {
446423
if (left === right) return true;
447424
if (!left || !right) return false;
448425
const someInLeftHasNoMatch = someInMap(left, (leftValue, leftKey) => {

src/compiler/types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ namespace ts {
1818
export interface Map<K, V> {
1919
clear(): void;
2020
delete(key: K): void;
21-
forEach(action: (value: V, key: K) => void): void;
21+
/**
22+
* Call `action` for each entry in the map.
23+
* Since we use a `for-in` loop for our shims, `key` may be a string.
24+
*/
25+
forEach(action: (value: V, key: K | string) => void): void;
2226
get(key: K): V;
2327
/**
2428
* Whether the key is in the map.

0 commit comments

Comments
 (0)