Skip to content

Commit a508dcc

Browse files
committed
Adjust the iteration behavior of the shimMap (used for IE 11) to visit values that are added while forEach is running.
Fixes microsoft#26090
1 parent d2909a1 commit a508dcc

File tree

1 file changed

+89
-20
lines changed

1 file changed

+89
-20
lines changed

src/compiler/core.ts

Lines changed: 89 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -121,29 +121,62 @@ namespace ts {
121121
function shimMap(): new <T>() => Map<T> {
122122

123123
class MapIterator<T, U extends (string | T | [string, T])> {
124-
private data: MapLike<T>;
125-
private keys: ReadonlyArray<string>;
126-
private index = 0;
124+
index = 0;
125+
126+
private shimMap: ShimMap<T>;
127127
private selector: (data: MapLike<T>, key: string) => U;
128-
constructor(data: MapLike<T>, selector: (data: MapLike<T>, key: string) => U) {
129-
this.data = data;
128+
129+
constructor(shimMap: ShimMap<T>, selector: (data: MapLike<T>, key: string) => U) {
130+
this.shimMap = shimMap;
130131
this.selector = selector;
131-
this.keys = Object.keys(data);
132+
133+
if (!shimMap.iteratorState) {
134+
// Create the initial iterator state.
135+
shimMap.iteratorState = {
136+
iterators: [],
137+
keys: Object.keys(shimMap.data)
138+
};
139+
}
140+
141+
// Add ourselves to the list of iterators.
142+
shimMap.iteratorState.iterators.push(this);
132143
}
133144

134145
public next(): { value: U, done: false } | { value: never, done: true } {
135-
const index = this.index;
136-
if (index < this.keys.length) {
137-
this.index++;
138-
return { value: this.selector(this.data, this.keys[index]), done: false };
146+
const iteratorState = this.shimMap.iteratorState!;
147+
if (this.index != -1 && this.index < iteratorState.keys.length) {
148+
const index = this.index++;
149+
return { value: this.selector(this.shimMap.data, iteratorState.keys[index]), done: false };
150+
}
151+
else {
152+
// Ensure subsequent invocations will always return done.
153+
this.index = -1;
154+
155+
// Remove ourselves from the list of iterators.
156+
iteratorState.iterators.splice(
157+
iteratorState.iterators.indexOf(this), 1);
158+
159+
if (iteratorState.iterators.length == 0) {
160+
// No other iterator is active, so clear the iterator state.
161+
this.shimMap.iteratorState = undefined;
162+
}
163+
164+
return { value: undefined as never, done: true };
139165
}
140-
return { value: undefined as never, done: true };
141166
}
142167
}
143168

144-
return class <T> implements Map<T> {
145-
private data = createDictionaryObject<T>();
146-
public size = 0;
169+
class ShimMap<T> implements Map<T> {
170+
size = 0;
171+
172+
data = createDictionaryObject<T>();
173+
174+
iteratorState: {
175+
readonly keys: string[];
176+
readonly iterators: {
177+
index: number;
178+
}[];
179+
} | undefined;
147180

148181
get(key: string): T | undefined {
149182
return this.data[key];
@@ -152,6 +185,11 @@ namespace ts {
152185
set(key: string, value: T): this {
153186
if (!this.has(key)) {
154187
this.size++;
188+
189+
if (this.iteratorState) {
190+
// Add the new entry.
191+
this.iteratorState.keys.push(key);
192+
}
155193
}
156194
this.data[key] = value;
157195
return this;
@@ -166,6 +204,22 @@ namespace ts {
166204
if (this.has(key)) {
167205
this.size--;
168206
delete this.data[key];
207+
208+
if (this.iteratorState) {
209+
// Remove the key and adjust the iterator indexes.
210+
// Note that this operation isn't very performant as we need to
211+
// iterate over the "keys" array; however, we expect that no one
212+
// will delete entries while iterators are still active.
213+
const keys = this.iteratorState.keys;
214+
const keyIndex = keys.indexOf(key);
215+
keys.splice(keyIndex, 1);
216+
217+
const iterators = this.iteratorState.iterators;
218+
for (let i = 0; i < iterators.length; i++)
219+
if (iterators[i].index > keyIndex)
220+
iterators[i].index--;
221+
}
222+
169223
return true;
170224
}
171225
return false;
@@ -174,26 +228,41 @@ namespace ts {
174228
clear(): void {
175229
this.data = createDictionaryObject<T>();
176230
this.size = 0;
231+
232+
if (this.iteratorState) {
233+
this.iteratorState.keys.splice(0, this.iteratorState.keys.length);
234+
235+
const iterators = this.iteratorState.iterators;
236+
for (let i = 0; i < iterators.length; i++)
237+
iterators[i].index = 0;
238+
}
177239
}
178240

179241
keys(): Iterator<string> {
180-
return new MapIterator(this.data, (_data, key) => key);
242+
return new MapIterator(this, (_data, key) => key);
181243
}
182244

183245
values(): Iterator<T> {
184-
return new MapIterator(this.data, (data, key) => data[key]);
246+
return new MapIterator(this, (data, key) => data[key]);
185247
}
186248

187249
entries(): Iterator<[string, T]> {
188-
return new MapIterator(this.data, (data, key) => [key, data[key]] as [string, T]);
250+
return new MapIterator(this, (data, key) => [key, data[key]] as [string, T]);
189251
}
190252

191253
forEach(action: (value: T, key: string) => void): void {
192-
for (const key in this.data) {
193-
action(this.data[key], key);
254+
const iterator = this.entries();
255+
while (true) {
256+
const { value: entry, done } = iterator.next();
257+
if (done)
258+
break;
259+
260+
action(entry[1], entry[0]);
194261
}
195262
}
196-
};
263+
}
264+
265+
return ShimMap;
197266
}
198267

199268
export function length(array: ReadonlyArray<any> | undefined): number {

0 commit comments

Comments
 (0)