Skip to content

Commit 7579194

Browse files
committed
(transformation): rewrite try-catch-finally implementation to correctly handle returns and re-throws
(test): - enable previously skipped re-throw tests - add tests for complex try-catch-finally return and throw scenarios
1 parent 4855d04 commit 7579194

2 files changed

Lines changed: 318 additions & 82 deletions

File tree

src/transformation/visitors/errors.ts

Lines changed: 119 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,9 @@ export const transformTryStatement: FunctionVisitor<ts.TryStatement> = (statemen
151151
return transformAsyncTry(statement, context);
152152
}
153153

154-
const [tryBlock, tryScope] = transformScopeBlock(context, statement.tryBlock, ScopeType.Try);
154+
const tsTryBlock = statement.tryBlock;
155+
const tsCatchClause = statement.catchClause;
156+
const [tryBlock] = transformScopeBlock(context, tsTryBlock, ScopeType.Try);
155157

156158
if (
157159
(context.options.luaTarget === LuaTarget.Lua50 || context.options.luaTarget === LuaTarget.Lua51) &&
@@ -163,104 +165,142 @@ export const transformTryStatement: FunctionVisitor<ts.TryStatement> = (statemen
163165
return tryBlock.statements;
164166
}
165167

166-
const tryResultIdentifier = lua.createIdentifier("____try");
167-
const returnValueIdentifier = lua.createIdentifier("____returnValue");
168+
const trySuccessIdentifier = lua.createIdentifier("____trySuccess", tsTryBlock);
169+
const catchSuccessIdentifier = lua.createIdentifier("____catchSuccess", tsCatchClause);
170+
const hasReturnOrErrorIdentifier = lua.createIdentifier("____hasReturnOrError", tsTryBlock);
171+
const returnValueIdentifier = lua.createIdentifier("____returnValue", tsTryBlock);
168172

169173
const result: lua.Statement[] = [];
170174

171-
const returnedIdentifier = lua.createIdentifier("____hasReturned");
172-
let returnCondition: lua.Expression | undefined;
173-
174-
const pCall = lua.createIdentifier("pcall");
175-
const tryCall = lua.createCallExpression(pCall, [lua.createFunctionExpression(tryBlock)]);
176-
177-
if (statement.catchClause && statement.catchClause.block.statements.length > 0) {
178-
// try with catch
179-
const [catchFunction, catchScope] = transformCatchClause(context, statement.catchClause);
180-
const catchIdentifier = lua.createIdentifier("____catch");
181-
result.push(lua.createVariableDeclarationStatement(catchIdentifier, catchFunction));
182-
183-
const hasReturn = tryScope.functionReturned ?? catchScope.functionReturned;
184-
185-
const tryReturnIdentifiers = [tryResultIdentifier]; // ____try
186-
if (hasReturn || statement.catchClause.variableDeclaration) {
187-
tryReturnIdentifiers.push(returnedIdentifier); // ____returned
188-
if (hasReturn) {
189-
tryReturnIdentifiers.push(returnValueIdentifier); // ____returnValue
190-
returnCondition = lua.cloneIdentifier(returnedIdentifier);
191-
}
192-
}
193-
result.push(lua.createVariableDeclarationStatement(tryReturnIdentifiers, tryCall));
175+
// pcall(function() ... end)
176+
const tryPCall = lua.createIdentifier("pcall", tsTryBlock);
177+
const tryCall = lua.createCallExpression(tryPCall, [lua.createFunctionExpression(tryBlock)], tsTryBlock);
178+
// ____trySuccess, ____hasReturnOrError, ____returnValue
179+
const tryReturnIdentifiers = [trySuccessIdentifier, hasReturnOrErrorIdentifier, returnValueIdentifier];
180+
result.push(lua.createVariableDeclarationStatement(tryReturnIdentifiers, tryCall, tsTryBlock));
181+
182+
const hasCatch = tsCatchClause && tsCatchClause.block.statements.length > 0;
183+
if (hasCatch) {
184+
// local ____catchSuccess
185+
result.push(lua.createVariableDeclarationStatement(catchSuccessIdentifier, undefined, tsCatchClause));
186+
187+
const [catchFunction] = transformCatchClause(context, tsCatchClause);
188+
189+
const catchIdentifier = lua.createIdentifier("____catch", tsCatchClause);
190+
result.push(lua.createVariableDeclarationStatement(catchIdentifier, catchFunction, tsCatchClause));
191+
192+
// pcall(____catch, ____hasReturnOrError)
193+
const catchPCall = lua.createIdentifier("pcall", tsCatchClause);
194+
const catchPcall = lua.createCallExpression(
195+
catchPCall,
196+
[catchIdentifier, lua.cloneIdentifier(hasReturnOrErrorIdentifier, tsCatchClause)],
197+
tsTryBlock
198+
);
194199

195-
const catchCall = lua.createCallExpression(
196-
catchIdentifier,
197-
statement.catchClause.variableDeclaration ? [lua.cloneIdentifier(returnedIdentifier)] : []
200+
// ____catchSuccess, ____hasReturnOrError, ____returnValue
201+
// = pcall(____catch, ____hasReturnOrError)
202+
const catchAssign = lua.createAssignmentStatement(
203+
[
204+
lua.cloneIdentifier(catchSuccessIdentifier, tsCatchClause),
205+
lua.cloneIdentifier(hasReturnOrErrorIdentifier, tsCatchClause),
206+
lua.cloneIdentifier(returnValueIdentifier, tsCatchClause),
207+
],
208+
catchPcall,
209+
tsCatchClause
198210
);
199-
const catchCallStatement = hasReturn
200-
? lua.createAssignmentStatement(
201-
[lua.cloneIdentifier(returnedIdentifier), lua.cloneIdentifier(returnValueIdentifier)],
202-
catchCall
203-
)
204-
: lua.createExpressionStatement(catchCall);
205-
206-
const notTryCondition = lua.createUnaryExpression(tryResultIdentifier, lua.SyntaxKind.NotOperator);
207-
result.push(lua.createIfStatement(notTryCondition, lua.createBlock([catchCallStatement])));
208-
} else if (tryScope.functionReturned) {
209-
// try with return, but no catch
210-
// returnedIdentifier = lua.createIdentifier("____returned");
211-
const returnedVariables = [tryResultIdentifier, returnedIdentifier, returnValueIdentifier];
212-
result.push(lua.createVariableDeclarationStatement(returnedVariables, tryCall));
213-
214-
// change return condition from '____returned' to '____try and ____returned'
215-
returnCondition = lua.createBinaryExpression(
216-
lua.cloneIdentifier(tryResultIdentifier),
217-
returnedIdentifier,
218-
lua.SyntaxKind.AndOperator
211+
212+
// if not ____trySuccess then
213+
// ____catchSuccess, ____hasReturnOrError, ____returnValue
214+
// = pcall(____catch, ____hasReturnOrError)
215+
// end
216+
const notTryCondition = lua.createUnaryExpression(trySuccessIdentifier, lua.SyntaxKind.NotOperator, tsTryBlock);
217+
result.push(
218+
lua.createIfStatement(notTryCondition, lua.createBlock([catchAssign], tsCatchClause), undefined, tsTryBlock)
219219
);
220-
} else if (statement.finallyBlock) {
221-
// try without catch, but with finally — need to capture error for re-throw
222-
const errorIdentifier = lua.createIdentifier("____error");
223-
result.push(lua.createVariableDeclarationStatement([tryResultIdentifier, errorIdentifier], tryCall));
224-
} else {
225-
// try without return or catch
226-
result.push(lua.createExpressionStatement(tryCall));
227220
}
228221

229222
if (statement.finallyBlock && statement.finallyBlock.statements.length > 0) {
230223
result.push(...context.transformStatements(statement.finallyBlock));
231224
}
232225

233-
// Re-throw error if try had no catch but had a finally.
234-
// 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) {
237-
const notTryCondition = lua.createUnaryExpression(
238-
lua.cloneIdentifier(tryResultIdentifier),
239-
lua.SyntaxKind.NotOperator
240-
);
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])));
226+
// if ____hasReturnOrError then
227+
// return ____returnValue
228+
// end
229+
const tryReturnValues: lua.Expression[] = [];
230+
if (isInMultiReturnFunction(context, statement)) {
231+
tryReturnValues.push(createUnpackCall(context, lua.cloneIdentifier(returnValueIdentifier, statement)));
232+
} else {
233+
tryReturnValues.push(lua.cloneIdentifier(returnValueIdentifier, statement));
248234
}
249235

250-
if (returnCondition && returnedIdentifier) {
251-
const returnValues: lua.Expression[] = [];
252-
236+
const returnStatement = createReturnStatement(context, tryReturnValues, statement);
237+
const ifTryReturnStatement = lua.createIfStatement(
238+
lua.cloneIdentifier(hasReturnOrErrorIdentifier, statement),
239+
lua.createBlock([returnStatement], statement),
240+
undefined,
241+
statement
242+
);
243+
const trySuccessBlock = lua.createBlock([ifTryReturnStatement], statement);
244+
245+
// error(____hasReturnOrError, 0)
246+
const rethrow = lua.createExpressionStatement(
247+
lua.createCallExpression(
248+
lua.createIdentifier("error"),
249+
[lua.cloneIdentifier(hasReturnOrErrorIdentifier, statement), lua.createNumericLiteral(0)],
250+
statement.catchClause ?? statement.tryBlock
251+
),
252+
statement.tryBlock
253+
);
254+
const throwBlock = lua.createBlock([rethrow], statement);
255+
256+
let ifCatchSuccessStatement: lua.IfStatement | undefined;
257+
if (hasCatch) {
258+
// if ____hasReturnOrError then
259+
// return ____returnValue
260+
// end
261+
const catchReturnValues: lua.Expression[] = [];
253262
if (isInMultiReturnFunction(context, statement)) {
254-
returnValues.push(createUnpackCall(context, lua.cloneIdentifier(returnValueIdentifier)));
263+
catchReturnValues.push(createUnpackCall(context, lua.cloneIdentifier(returnValueIdentifier, statement)));
255264
} else {
256-
returnValues.push(lua.cloneIdentifier(returnValueIdentifier));
265+
catchReturnValues.push(lua.cloneIdentifier(returnValueIdentifier, statement));
257266
}
258267

259-
const returnStatement = createReturnStatement(context, returnValues, statement);
260-
const ifReturnedStatement = lua.createIfStatement(returnCondition, lua.createBlock([returnStatement]));
261-
result.push(ifReturnedStatement);
268+
const catchReturnStatement = createReturnStatement(context, catchReturnValues, statement);
269+
const ifCatchReturnStatement = lua.createIfStatement(
270+
lua.cloneIdentifier(hasReturnOrErrorIdentifier, statement),
271+
lua.createBlock([catchReturnStatement], statement),
272+
undefined,
273+
statement
274+
);
275+
const catchSuccessBlock = lua.createBlock([ifCatchReturnStatement], statement);
276+
277+
ifCatchSuccessStatement = lua.createIfStatement(
278+
lua.cloneIdentifier(catchSuccessIdentifier, statement),
279+
catchSuccessBlock,
280+
throwBlock,
281+
statement
282+
);
262283
}
263284

285+
// if ____trySuccess then
286+
// if ____hasReturnOrError then
287+
// return ____returnValue
288+
// end
289+
// elseif ____catchSuccess then
290+
// if ____hasReturnOrError then
291+
// return ____returnValue
292+
// end
293+
// else
294+
// error(____hasReturnOrError, 0)
295+
// end
296+
const ifTrySuccessStatement = lua.createIfStatement(
297+
lua.cloneIdentifier(trySuccessIdentifier, statement),
298+
trySuccessBlock,
299+
ifCatchSuccessStatement ?? (tsCatchClause ? undefined : throwBlock),
300+
statement
301+
);
302+
result.push(ifTrySuccessStatement);
303+
264304
return lua.createDoStatement(result, statement);
265305
};
266306

0 commit comments

Comments
 (0)