Skip to content

Commit 5238499

Browse files
committed
Fix try/catch/finally flow
Closes: #1137
1 parent 60cb2e1 commit 5238499

2 files changed

Lines changed: 60 additions & 24 deletions

File tree

src/transformation/visitors/errors.ts

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ export const transformTryStatement: FunctionVisitor<ts.TryStatement> = (statemen
165165

166166
const tryResultIdentifier = lua.createIdentifier("____try");
167167
const returnValueIdentifier = lua.createIdentifier("____returnValue");
168+
const rethrowIdentifier = lua.createIdentifier("____rethrow");
168169

169170
const result: lua.Statement[] = [];
170171

@@ -191,20 +192,36 @@ export const transformTryStatement: FunctionVisitor<ts.TryStatement> = (statemen
191192
}
192193
}
193194
result.push(lua.createVariableDeclarationStatement(tryReturnIdentifiers, tryCall));
195+
if (statement.finallyBlock) {
196+
result.push(lua.createVariableDeclarationStatement(rethrowIdentifier));
197+
}
194198

195199
const catchCall = lua.createCallExpression(
196200
catchIdentifier,
197201
statement.catchClause.variableDeclaration ? [lua.cloneIdentifier(returnedIdentifier)] : []
198202
);
199203
const catchCallStatement = hasReturn
200204
? lua.createAssignmentStatement(
201-
[lua.cloneIdentifier(returnedIdentifier), lua.cloneIdentifier(returnValueIdentifier)],
202-
catchCall
203-
)
205+
[lua.cloneIdentifier(returnedIdentifier), lua.cloneIdentifier(returnValueIdentifier)],
206+
catchCall
207+
)
204208
: lua.createExpressionStatement(catchCall);
205209

210+
const catchCallFunction = lua.createFunctionExpression(lua.createBlock([catchCallStatement]));
211+
212+
const tryCatchCall = lua.createCallExpression(pCall, [catchCallFunction]);
213+
214+
const tryCatchCallStatement = lua.createAssignmentStatement(
215+
[lua.cloneIdentifier(tryResultIdentifier), lua.cloneIdentifier(rethrowIdentifier)],
216+
tryCatchCall
217+
);
218+
206219
const notTryCondition = lua.createUnaryExpression(tryResultIdentifier, lua.SyntaxKind.NotOperator);
207-
result.push(lua.createIfStatement(notTryCondition, lua.createBlock([catchCallStatement])));
220+
result.push(lua.createIfStatement(notTryCondition, lua.createBlock(
221+
statement.finallyBlock
222+
? [tryCatchCallStatement]
223+
: [catchCallStatement]
224+
)));
208225
} else if (tryScope.functionReturned) {
209226
// try with return, but no catch
210227
// returnedIdentifier = lua.createIdentifier("____returned");
@@ -230,21 +247,31 @@ export const transformTryStatement: FunctionVisitor<ts.TryStatement> = (statemen
230247
result.push(...context.transformStatements(statement.finallyBlock));
231248
}
232249

233-
// Re-throw error if try had no catch but had a finally.
250+
// Re-throw error if try had a finally.
234251
// On pcall failure the error is the second return value, which lands in
235-
// ____hasReturned (when functionReturned) or ____error (otherwise).
236-
if (!statement.catchClause && statement.finallyBlock) {
252+
// ____hasReturned (when functionReturned), ____rethrow
253+
// (catch throw/re-throw), or ____error (otherwise).
254+
if (statement.finallyBlock) {
237255
const notTryCondition = lua.createUnaryExpression(
238256
lua.cloneIdentifier(tryResultIdentifier),
239257
lua.SyntaxKind.NotOperator
240258
);
241-
const errorIdentifier = tryScope.functionReturned
242-
? lua.cloneIdentifier(returnedIdentifier)
243-
: lua.createIdentifier("____error");
244-
const rethrow = lua.createExpressionStatement(
245-
lua.createCallExpression(lua.createIdentifier("error"), [errorIdentifier, lua.createNumericLiteral(0)])
246-
);
247-
result.push(lua.createIfStatement(notTryCondition, lua.createBlock([rethrow])));
259+
260+
if (!statement.catchClause) {
261+
const errorIdentifier = tryScope.functionReturned
262+
? lua.cloneIdentifier(returnedIdentifier)
263+
: lua.createIdentifier("____error");
264+
const rethrow = lua.createExpressionStatement(
265+
lua.createCallExpression(lua.createIdentifier("error"), [errorIdentifier, lua.createNumericLiteral(0)])
266+
);
267+
result.push(lua.createIfStatement(notTryCondition, lua.createBlock([rethrow])));
268+
} else if (statement.catchClause.block.statements.length > 0) {
269+
// Re-throw is possible only from non-empty blocks.
270+
const rethrow = lua.createExpressionStatement(
271+
lua.createCallExpression(lua.createIdentifier("error"), [rethrowIdentifier, lua.createNumericLiteral(0)])
272+
);
273+
result.push(lua.createIfStatement(notTryCondition, lua.createBlock([rethrow])));
274+
}
248275
}
249276

250277
if (returnCondition && returnedIdentifier) {

test/unit/error.spec.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,40 @@ test("throwString", () => {
77
`.expectToEqual(new util.ExecutionError("Some Error"));
88
});
99

10-
// TODO: Finally does not behave like it should, see #1137
11-
// eslint-disable-next-line jest/no-disabled-tests
12-
test.skip.each([0, 1, 2])("re-throw (%p)", i => {
10+
test.each([
11+
{ innerTry: false, innerFinally: false, outerFinally: false },
12+
{ innerTry: false, innerFinally: false, outerFinally: true },
13+
{ innerTry: false, innerFinally: true, outerFinally: false },
14+
{ innerTry: false, innerFinally: true, outerFinally: true },
15+
{ innerTry: true, innerFinally: false, outerFinally: false },
16+
{ innerTry: true, innerFinally: false, outerFinally: true },
17+
{ innerTry: true, innerFinally: true, outerFinally: false },
18+
{ innerTry: true, innerFinally: true, outerFinally: true },
19+
])("re-throw (%j)", o => {
1320
util.testFunction`
14-
const i: number = ${i};
21+
const innerTry: boolean = ${o.innerTry};
22+
const innerFinally: boolean = ${o.innerFinally};
23+
const outerFinally: boolean = ${o.outerFinally};
1524
function foo() {
1625
try {
1726
try {
18-
if (i === 0) { throw "z"; }
27+
if (innerTry) { throw "inner.try"; }
1928
} catch (e) {
20-
throw "a";
29+
throw (e as string) + "->" + "inner.catch";
2130
} finally {
22-
if (i === 1) { throw "b"; }
31+
if (innerFinally) { throw "inner.finally"; }
2332
}
2433
} catch (e) {
25-
throw (e as string).toUpperCase();
34+
throw (e as string) + "->" + "outer.catch";
2635
} finally {
27-
throw "C";
36+
if (outerFinally) { throw "outer.finally"; }
2837
}
2938
}
3039
let result: string = "x";
3140
try {
3241
foo();
3342
} catch (e) {
34-
result = (e as string)[(e as string).length - 1];
43+
result = e as string;
3544
}
3645
return result;
3746
`.expectToMatchJsResult();

0 commit comments

Comments
 (0)