Skip to content

Commit a7c06f1

Browse files
committed
fix Set iterator not reflecting mutations after creation (#1671)
Set.values(), keys(), and entries() eagerly captured firstKey at iterator creation time. If the Set was mutated (e.g. via delete) before the iterator was consumed, the iterator still saw the stale firstKey. Read firstKey lazily on the first next() call instead.
1 parent 1bdbed8 commit a7c06f1

File tree

2 files changed

+43
-12
lines changed

2 files changed

+43
-12
lines changed

src/lualib/Set.ts

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -102,46 +102,64 @@ export class Set<T extends AnyNotNil> {
102102
}
103103

104104
public entries(): IterableIterator<[T, T]> {
105+
const getFirstKey = () => this.firstKey;
105106
const nextKey = this.nextKey;
106-
let key: T = this.firstKey!;
107+
let key: T | undefined;
108+
let started = false;
107109
return {
108110
[Symbol.iterator](): IterableIterator<[T, T]> {
109111
return this;
110112
},
111113
next(): IteratorResult<[T, T]> {
112-
const result = { done: !key, value: [key, key] as [T, T] };
113-
key = nextKey.get(key);
114-
return result;
114+
if (!started) {
115+
started = true;
116+
key = getFirstKey();
117+
} else {
118+
key = nextKey.get(key!);
119+
}
120+
return { done: !key, value: [key!, key!] as [T, T] };
115121
},
116122
};
117123
}
118124

119125
public keys(): IterableIterator<T> {
126+
const getFirstKey = () => this.firstKey;
120127
const nextKey = this.nextKey;
121-
let key: T = this.firstKey!;
128+
let key: T | undefined;
129+
let started = false;
122130
return {
123131
[Symbol.iterator](): IterableIterator<T> {
124132
return this;
125133
},
126134
next(): IteratorResult<T> {
127-
const result = { done: !key, value: key };
128-
key = nextKey.get(key);
129-
return result;
135+
if (!started) {
136+
started = true;
137+
key = getFirstKey();
138+
} else {
139+
key = nextKey.get(key!);
140+
}
141+
return { done: !key, value: key! };
130142
},
131143
};
132144
}
133145

134146
public values(): IterableIterator<T> {
147+
const getFirstKey = () => this.firstKey;
135148
const nextKey = this.nextKey;
136-
let key: T = this.firstKey!;
149+
let key: T | undefined;
150+
let started = false;
137151
return {
138152
[Symbol.iterator](): IterableIterator<T> {
139153
return this;
140154
},
141155
next(): IteratorResult<T> {
142-
const result = { done: !key, value: key };
143-
key = nextKey.get(key);
144-
return result;
156+
if (!started) {
157+
started = true;
158+
key = getFirstKey();
159+
} else {
160+
key = nextKey.get(key!);
161+
}
162+
return { done: !key, value: key! };
145163
},
146164
};
147165
}

test/unit/builtins/set.spec.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,19 @@ describe.each(iterationMethods)("set.%s() preserves insertion order", iterationM
195195
});
196196
});
197197

198+
test("set iterator persists after delete", () => {
199+
util.testFunction`
200+
const set1 = new Set<string | number>();
201+
set1.add(42);
202+
set1.add("forty two");
203+
204+
const iterator1 = set1.values();
205+
set1.delete(42);
206+
207+
return iterator1.next().value;
208+
`.expectToMatchJsResult();
209+
});
210+
198211
test("instanceof Set without creating set", () => {
199212
util.testFunction`
200213
const myset = 3 as any;

0 commit comments

Comments
 (0)