Skip to content

Commit e52e216

Browse files
committed
Fix Map iterator mutation and add tests for mutation and exhaustion behavior
1 parent 2bd60e8 commit e52e216

File tree

2 files changed

+68
-12
lines changed

2 files changed

+68
-12
lines changed

src/lualib/Map.ts

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -113,46 +113,64 @@ export class Map<K extends AnyNotNil, V> {
113113
}
114114

115115
public entries(): IterableIterator<[K, V]> {
116+
const getFirstKey = () => this.firstKey;
116117
const { items, nextKey } = this;
117-
let key = this.firstKey;
118+
let key: K | undefined;
119+
let started = false;
118120
return {
119121
[Symbol.iterator](): IterableIterator<[K, V]> {
120122
return this;
121123
},
122124
next(): IteratorResult<[K, V]> {
123-
const result = { done: !key, value: [key, items.get(key!)] as [K, V] };
124-
key = nextKey.get(key!);
125-
return result;
125+
if (!started) {
126+
started = true;
127+
key = getFirstKey();
128+
} else {
129+
key = nextKey.get(key!);
130+
}
131+
return { done: !key, value: [key!, items.get(key!)] as [K, V] };
126132
},
127133
};
128134
}
129135

130136
public keys(): IterableIterator<K> {
137+
const getFirstKey = () => this.firstKey;
131138
const nextKey = this.nextKey;
132-
let key = this.firstKey;
139+
let key: K | undefined;
140+
let started = false;
133141
return {
134142
[Symbol.iterator](): IterableIterator<K> {
135143
return this;
136144
},
137145
next(): IteratorResult<K> {
138-
const result = { done: !key, value: key };
139-
key = nextKey.get(key!);
140-
return result as IteratorResult<K>;
146+
if (!started) {
147+
started = true;
148+
key = getFirstKey();
149+
} else {
150+
key = nextKey.get(key!);
151+
}
152+
return { done: !key, value: key! };
141153
},
142154
};
143155
}
144156

145157
public values(): IterableIterator<V> {
158+
const getFirstKey = () => this.firstKey;
146159
const { items, nextKey } = this;
147-
let key = this.firstKey;
160+
let key: K | undefined;
161+
let started = false;
148162
return {
149163
[Symbol.iterator](): IterableIterator<V> {
150164
return this;
151165
},
152166
next(): IteratorResult<V> {
153-
const result = { done: !key, value: items.get(key!) };
154-
key = nextKey.get(key!);
155-
return result;
167+
if (!started) {
168+
started = true;
169+
key = getFirstKey();
170+
} else {
171+
key = nextKey.get(key!);
172+
}
173+
return { done: !key, value: items.get(key!) };
156174
},
157175
};
158176
}

test/unit/builtins/map.spec.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,44 @@ describe.each(iterationMethods)("map.%s() preserves insertion order", iterationM
210210
});
211211
});
212212

213+
describe.each(iterationMethods)("map.%s() handles mutation", iterationMethod => {
214+
test("iterator persists after delete", () => {
215+
util.testFunction`
216+
const map = new Map<number, string>([[1, "a"], [2, "b"]]);
217+
const iter = map.${iterationMethod}();
218+
map.delete(1);
219+
return iter.next().value;
220+
`.expectToMatchJsResult();
221+
});
222+
223+
test("iterator with delete and add between iterations", () => {
224+
util.testFunction`
225+
const map = new Map<number, string>([[1, "a"], [2, "b"], [3, "c"]]);
226+
const iter = map.${iterationMethod}();
227+
iter.next(); // 1
228+
map.delete(2);
229+
map.set(4, "d");
230+
const results: IteratorResult<any>[] = [];
231+
let r = iter.next();
232+
while (!r.done) { results.push({ done: r.done, value: r.value }); r = iter.next(); }
233+
return results;
234+
`.expectToMatchJsResult();
235+
});
236+
237+
test("iterator does not restart after exhaustion", () => {
238+
util.testFunction`
239+
const map = new Map<number, string>([[1, "a"], [2, "b"]]);
240+
const iter = map.${iterationMethod}();
241+
const results: boolean[] = [];
242+
results.push(iter.next().done!);
243+
results.push(iter.next().done!);
244+
results.push(iter.next().done!); // should be done
245+
results.push(iter.next().done!); // should still be done, not restart
246+
return results;
247+
`.expectToMatchJsResult();
248+
});
249+
});
250+
213251
describe("Map.groupBy", () => {
214252
test("empty", () => {
215253
util.testFunction`

0 commit comments

Comments
 (0)