Skip to content

Commit d38e4d2

Browse files
authored
Merge branch 'TypeScriptToLua:master' into fix/dead-code-after-break
2 parents 2ff4e87 + c3dc829 commit d38e4d2

File tree

6 files changed

+182
-24
lines changed

6 files changed

+182
-24
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
}

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
}

src/transformation/visitors/errors.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ export const transformTryStatement: FunctionVisitor<ts.TryStatement> = (statemen
146146
returnedIdentifier,
147147
lua.SyntaxKind.AndOperator
148148
);
149+
} else if (statement.finallyBlock) {
150+
// try without catch, but with finally — need to capture error for re-throw
151+
const errorIdentifier = lua.createIdentifier("____error");
152+
result.push(lua.createVariableDeclarationStatement([tryResultIdentifier, errorIdentifier], tryCall));
149153
} else {
150154
// try without return or catch
151155
result.push(lua.createExpressionStatement(tryCall));
@@ -155,6 +159,22 @@ export const transformTryStatement: FunctionVisitor<ts.TryStatement> = (statemen
155159
result.push(...context.transformStatements(statement.finallyBlock));
156160
}
157161

162+
// Re-throw error if try had no catch but had a finally
163+
if (!statement.catchClause && statement.finallyBlock) {
164+
const notTryCondition = lua.createUnaryExpression(
165+
lua.cloneIdentifier(tryResultIdentifier),
166+
lua.SyntaxKind.NotOperator
167+
);
168+
const errorIdentifier = lua.createIdentifier("____error");
169+
const rethrow = lua.createExpressionStatement(
170+
lua.createCallExpression(lua.createIdentifier("error"), [
171+
lua.cloneIdentifier(errorIdentifier),
172+
lua.createNumericLiteral(0),
173+
])
174+
);
175+
result.push(lua.createIfStatement(notTryCondition, lua.createBlock([rethrow])));
176+
}
177+
158178
if (returnCondition && returnedIdentifier) {
159179
const returnValues: lua.Expression[] = [];
160180

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`

test/unit/builtins/set.spec.ts

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

198+
describe.each(iterationMethods)("set.%s() handles mutation", iterationMethod => {
199+
test("iterator persists after delete", () => {
200+
util.testFunction`
201+
const set1 = new Set<string | number>();
202+
set1.add(42);
203+
set1.add("forty two");
204+
205+
const iterator1 = set1.${iterationMethod}();
206+
set1.delete(42);
207+
208+
return iterator1.next().value;
209+
`.expectToMatchJsResult();
210+
});
211+
212+
test("iterator with delete and add between iterations", () => {
213+
util.testFunction`
214+
const set = new Set([1, 2, 3]);
215+
const iter = set.${iterationMethod}();
216+
iter.next(); // 1
217+
set.delete(2);
218+
set.add(4);
219+
const results: IteratorResult<any>[] = [];
220+
let r = iter.next();
221+
while (!r.done) { results.push({ done: r.done, value: r.value }); r = iter.next(); }
222+
return results;
223+
`.expectToMatchJsResult();
224+
});
225+
226+
test("iterator does not restart after exhaustion", () => {
227+
util.testFunction`
228+
const set = new Set([1, 2]);
229+
const iter = set.${iterationMethod}();
230+
const results: boolean[] = [];
231+
results.push(iter.next().done!);
232+
results.push(iter.next().done!);
233+
results.push(iter.next().done!); // should be done
234+
results.push(iter.next().done!); // should still be done, not restart
235+
return results;
236+
`.expectToMatchJsResult();
237+
});
238+
});
239+
198240
test("instanceof Set without creating set", () => {
199241
util.testFunction`
200242
const myset = 3 as any;

test/unit/error.spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,28 @@ test("multi return from catch->finally", () => {
256256
.expectToMatchJsResult();
257257
});
258258

259+
test("throw propagates through finally to outer catch", () => {
260+
util.testFunction`
261+
function thrower() {
262+
try {
263+
throw "Error";
264+
} finally {
265+
}
266+
}
267+
268+
function caller() {
269+
try {
270+
thrower();
271+
return "NoCatch";
272+
} catch (e) {
273+
return e;
274+
}
275+
}
276+
277+
return caller();
278+
`.expectToMatchJsResult();
279+
});
280+
259281
test("return from nested finally", () => {
260282
util.testFunction`
261283
let x = "";

0 commit comments

Comments
 (0)