Skip to content

Commit 3d14be8

Browse files
authored
fix await inside catch/finally in async try (#1659) (#1709)
* fix await inside catch/finally in async try * add test for awaited return value from catch in async try
1 parent 2fc2e99 commit 3d14be8

2 files changed

Lines changed: 141 additions & 18 deletions

File tree

src/transformation/visitors/errors.ts

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -40,35 +40,44 @@ const transformAsyncTry: FunctionVisitor<ts.TryStatement> = (statement, context)
4040
let catchScope: Scope | undefined;
4141
const chainCalls: lua.Statement[] = [];
4242

43-
if (statement.finallyBlock) {
44-
const awaiterFinally = lua.createTableIndexExpression(awaiterIdentifier, lua.createStringLiteral("finally"));
45-
const finallyFunction = lua.createFunctionExpression(
46-
lua.createBlock(context.transformStatements(statement.finallyBlock.statements))
47-
);
48-
const finallyCall = lua.createCallExpression(
49-
awaiterFinally,
50-
[awaiterIdentifier, finallyFunction],
51-
statement.finallyBlock
52-
);
53-
chainCalls.push(lua.createExpressionStatement(finallyCall));
54-
}
55-
5643
if (statement.catchClause) {
44+
// ____try = ____try.catch(<catch function>)
5745
const [catchFunction, cScope] = transformCatchClause(context, statement.catchClause);
5846
catchScope = cScope;
5947
if (catchFunction.params) {
6048
catchFunction.params.unshift(lua.createAnonymousIdentifier());
6149
}
6250

51+
const catchBodyStatements = catchFunction.body ? catchFunction.body.statements : [];
52+
const asyncWrappedCatch = wrapInAsyncAwaiter(context, [...catchBodyStatements], false);
53+
catchFunction.body = lua.createBlock([lua.createReturnStatement([asyncWrappedCatch])]);
54+
6355
const awaiterCatch = lua.createTableIndexExpression(awaiterIdentifier, lua.createStringLiteral("catch"));
6456
const catchCall = lua.createCallExpression(awaiterCatch, [awaiterIdentifier, catchFunction]);
65-
const promiseAwait = transformLuaLibFunction(context, LuaLibFeature.Await, statement, catchCall);
66-
chainCalls.push(lua.createExpressionStatement(promiseAwait, statement));
67-
} else {
68-
const promiseAwait = transformLuaLibFunction(context, LuaLibFeature.Await, statement, awaiterIdentifier);
69-
chainCalls.push(lua.createExpressionStatement(promiseAwait, statement));
57+
chainCalls.push(lua.createAssignmentStatement(lua.cloneIdentifier(awaiterIdentifier), catchCall));
7058
}
7159

60+
if (statement.finallyBlock) {
61+
// ____try = ____try.finally(<finally function>)
62+
const finallyStatements = context.transformStatements(statement.finallyBlock.statements);
63+
const asyncWrappedFinally = wrapInAsyncAwaiter(context, finallyStatements, false);
64+
const finallyFunction = lua.createFunctionExpression(
65+
lua.createBlock([lua.createReturnStatement([asyncWrappedFinally])])
66+
);
67+
68+
const awaiterFinally = lua.createTableIndexExpression(awaiterIdentifier, lua.createStringLiteral("finally"));
69+
const finallyCall = lua.createCallExpression(
70+
awaiterFinally,
71+
[awaiterIdentifier, finallyFunction],
72+
statement.finallyBlock
73+
);
74+
chainCalls.push(lua.createAssignmentStatement(lua.cloneIdentifier(awaiterIdentifier), finallyCall));
75+
}
76+
77+
// __TS__Await(____try)
78+
const promiseAwait = transformLuaLibFunction(context, LuaLibFeature.Await, statement, awaiterIdentifier);
79+
chainCalls.push(lua.createExpressionStatement(promiseAwait, statement));
80+
7281
const hasReturn = tryScope.asyncTryHasReturn ?? catchScope?.asyncTryHasReturn;
7382
const hasBreak = tryScope.asyncTryHasBreak ?? catchScope?.asyncTryHasBreak;
7483
const hasContinue = tryScope.asyncTryHasContinue ?? catchScope?.asyncTryHasContinue;

test/unit/builtins/async-await.spec.ts

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,120 @@ describe("try/catch in async function", () => {
816816
});
817817
});
818818

819+
// https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1659
820+
test("await inside catch handler resolves correctly (#1659)", () => {
821+
util.testFunction`
822+
let reject: (reason: string) => void = () => {};
823+
824+
async function failing() {
825+
return new Promise((_, rej) => { reject = rej; });
826+
}
827+
828+
async function run() {
829+
try {
830+
await failing();
831+
} catch (e) {
832+
log("catch");
833+
const a = await Promise.resolve(true);
834+
log("a", a);
835+
}
836+
}
837+
838+
run();
839+
reject("error");
840+
841+
return allLogs;
842+
`
843+
.setTsHeader(promiseTestLib)
844+
.expectToEqual(["catch", "a", true]);
845+
});
846+
847+
// https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1659
848+
test("await inside finally handler resolves correctly (#1659)", () => {
849+
util.testFunction`
850+
let reject: (reason: string) => void = () => {};
851+
852+
async function failing() {
853+
return new Promise((_, rej) => { reject = rej; });
854+
}
855+
856+
async function run() {
857+
try {
858+
await failing();
859+
} finally {
860+
log("finally");
861+
const a = await Promise.resolve(true);
862+
log("a", a);
863+
}
864+
}
865+
866+
run().catch(() => {});
867+
reject("error");
868+
869+
return allLogs;
870+
`
871+
.setTsHeader(promiseTestLib)
872+
.expectToEqual(["finally", "a", true]);
873+
});
874+
875+
// https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1659
876+
test("await inside both catch and finally handlers (#1659)", () => {
877+
util.testFunction`
878+
let reject: (reason: string) => void = () => {};
879+
880+
async function failing() {
881+
return new Promise((_, rej) => { reject = rej; });
882+
}
883+
884+
async function run() {
885+
try {
886+
await failing();
887+
} catch (e) {
888+
log("catch");
889+
const a = await Promise.resolve("caught");
890+
log("a", a);
891+
} finally {
892+
log("finally");
893+
const b = await Promise.resolve("done");
894+
log("b", b);
895+
}
896+
}
897+
898+
run();
899+
reject("error");
900+
901+
return allLogs;
902+
`
903+
.setTsHeader(promiseTestLib)
904+
.expectToEqual(["catch", "a", "caught", "finally", "b", "done"]);
905+
});
906+
907+
// https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1659
908+
test("awaited value in catch is returned from async function (#1659)", () => {
909+
util.testFunction`
910+
const failing = defer<string>();
911+
const recovery = defer<string>();
912+
913+
async function run() {
914+
try {
915+
await failing.promise;
916+
return "succeeded";
917+
} catch (e) {
918+
return await recovery.promise;
919+
}
920+
}
921+
922+
run().then(value => log("result", value));
923+
924+
failing.reject("error");
925+
recovery.resolve("recovered");
926+
927+
return allLogs;
928+
`
929+
.setTsHeader(promiseTestLib)
930+
.expectToEqual(["result", "recovered"]);
931+
});
932+
819933
// https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1706
820934
test("return inside try with deferred promise (#1706)", () => {
821935
util.testFunction`

0 commit comments

Comments
 (0)