Skip to content

Commit 85050fd

Browse files
authored
Handling return from try and catch (#642)
* handling return from try and catch * tupleReturn+finally tests * clarifying some comments
1 parent 8fd1175 commit 85050fd

File tree

2 files changed

+359
-32
lines changed

2 files changed

+359
-32
lines changed

src/LuaTransformer.ts

Lines changed: 123 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ export enum ScopeType {
1717
Loop = 0x8,
1818
Conditional = 0x10,
1919
Block = 0x20,
20+
Try = 0x40,
21+
Catch = 0x80,
2022
}
2123

2224
interface SymbolInfo {
@@ -37,6 +39,7 @@ interface Scope {
3739
functionDefinitions?: Map<tstl.SymbolId, FunctionDefinitionInfo>;
3840
importStatements?: tstl.Statement[];
3941
loopContinued?: boolean;
42+
functionReturned?: boolean;
4043
}
4144

4245
export interface EmitResolver {
@@ -2132,23 +2135,33 @@ export class LuaTransformer {
21322135
}
21332136

21342137
public transformReturnStatement(statement: ts.ReturnStatement): StatementVisitResult {
2138+
// Bubble up explicit return flag and check if we're inside a try/catch block
2139+
let insideTryCatch = false;
2140+
for (let i = this.scopeStack.length - 1; i >= 0; --i) {
2141+
const scope = this.scopeStack[i];
2142+
scope.functionReturned = true;
2143+
2144+
if (scope.type === ScopeType.Function) {
2145+
break;
2146+
}
2147+
2148+
insideTryCatch = insideTryCatch || scope.type === ScopeType.Try || scope.type === ScopeType.Catch;
2149+
}
2150+
2151+
let results: tstl.Expression[];
2152+
21352153
if (statement.expression) {
2154+
const expressionType = this.checker.getTypeAtLocation(statement.expression);
21362155
const returnType = tsHelper.getContainingFunctionReturnType(statement, this.checker);
21372156
if (returnType) {
2138-
const expressionType = this.checker.getTypeAtLocation(statement.expression);
21392157
this.validateFunctionAssignment(statement, expressionType, returnType);
21402158
}
21412159
if (tsHelper.isInTupleReturnFunction(statement, this.checker)) {
21422160
// Parent function is a TupleReturn function
21432161
if (ts.isArrayLiteralExpression(statement.expression)) {
21442162
// If return expression is an array literal, leave out brackets.
2145-
return tstl.createReturnStatement(
2146-
statement.expression.elements.map(elem => this.transformExpression(elem))
2147-
);
2148-
}
2149-
2150-
const expressionType = this.checker.getTypeAtLocation(statement.expression);
2151-
if (
2163+
results = statement.expression.elements.map(elem => this.transformExpression(elem));
2164+
} else if (
21522165
!tsHelper.isTupleReturnCall(statement.expression, this.checker) &&
21532166
tsHelper.isArrayType(expressionType, this.checker, this.program)
21542167
) {
@@ -2157,15 +2170,28 @@ export class LuaTransformer {
21572170
this.transformExpression(statement.expression),
21582171
statement.expression
21592172
);
2160-
return tstl.createReturnStatement([expression]);
2173+
results = [expression];
2174+
} else {
2175+
results = [this.transformExpression(statement.expression)];
21612176
}
2177+
2178+
// Wrap tupleReturn results when returning inside try/catch
2179+
if (insideTryCatch) {
2180+
results = [this.wrapInTable(...results)];
2181+
}
2182+
} else {
2183+
results = [this.transformExpression(statement.expression)];
21622184
}
2163-
const returnExpressions = [this.transformExpression(statement.expression)];
2164-
return tstl.createReturnStatement(returnExpressions, statement);
21652185
} else {
21662186
// Empty return
2167-
return tstl.createReturnStatement([], statement);
2187+
results = [];
21682188
}
2189+
2190+
if (insideTryCatch) {
2191+
results.unshift(tstl.createBooleanLiteral(true));
2192+
}
2193+
2194+
return tstl.createReturnStatement(results, statement);
21692195
}
21702196

21712197
public transformIfStatement(statement: ts.IfStatement): StatementVisitResult {
@@ -2592,39 +2618,104 @@ export class LuaTransformer {
25922618
}
25932619
}
25942620

2621+
protected transformScopeBlock(block: ts.Block, scopeType: ScopeType): [tstl.Block, Scope] {
2622+
this.pushScope(scopeType);
2623+
const statements = this.performHoisting(this.transformStatements(block.statements));
2624+
const scope = this.popScope();
2625+
return [tstl.createBlock(statements, block), scope];
2626+
}
2627+
25952628
public transformTryStatement(statement: ts.TryStatement): StatementVisitResult {
2596-
const pCall = tstl.createIdentifier("pcall");
2597-
const tryBlock = this.transformBlock(statement.tryBlock);
2598-
const tryCall = tstl.createCallExpression(pCall, [tstl.createFunctionExpression(tryBlock)]);
2629+
const [tryBlock, tryScope] = this.transformScopeBlock(statement.tryBlock, ScopeType.Try);
2630+
2631+
const tryResultIdentfier = tstl.createIdentifier("____TS_try");
2632+
const returnValueIdentifier = tstl.createIdentifier("____TS_returnValue");
25992633

26002634
const result: tstl.Statement[] = [];
26012635

2602-
if (statement.catchClause) {
2603-
const tryResult = tstl.createIdentifier("____TS_try");
2636+
let returnedIdentifier: tstl.Identifier | undefined;
2637+
let returnCondition: tstl.Expression | undefined;
26042638

2605-
const returnVariables =
2606-
statement.catchClause && statement.catchClause.variableDeclaration
2607-
? [
2608-
tryResult,
2609-
this.transformIdentifier(statement.catchClause.variableDeclaration.name as ts.Identifier),
2610-
]
2611-
: [tryResult];
2639+
const pCall = tstl.createIdentifier("pcall");
2640+
const tryCall = tstl.createCallExpression(pCall, [tstl.createFunctionExpression(tryBlock)]);
26122641

2613-
const catchAssignment = tstl.createVariableDeclarationStatement(returnVariables, tryCall);
2642+
if (statement.catchClause && statement.catchClause.block.statements.length > 0) {
2643+
// try with catch
2644+
let [catchBlock, catchScope] = this.transformScopeBlock(statement.catchClause.block, ScopeType.Catch);
2645+
if (statement.catchClause.variableDeclaration) {
2646+
// Replace ____TS_returned with catch variable
2647+
returnedIdentifier = this.transformIdentifier(statement.catchClause.variableDeclaration
2648+
.name as ts.Identifier);
2649+
} else if (tryScope.functionReturned || catchScope.functionReturned) {
2650+
returnedIdentifier = tstl.createIdentifier("____TS_returned");
2651+
}
26142652

2615-
result.push(catchAssignment);
2653+
const tryReturnIdentifiers = [tryResultIdentfier]; // ____TS_try
2654+
if (returnedIdentifier) {
2655+
tryReturnIdentifiers.push(returnedIdentifier); // ____TS_returned or catch variable
2656+
if (tryScope.functionReturned || catchScope.functionReturned) {
2657+
tryReturnIdentifiers.push(returnValueIdentifier); // ____TS_returnValue
2658+
returnCondition = tstl.cloneIdentifier(returnedIdentifier);
2659+
}
2660+
}
2661+
result.push(tstl.createVariableDeclarationStatement(tryReturnIdentifiers, tryCall));
26162662

2617-
const notTryResult = tstl.createUnaryExpression(
2618-
tstl.createParenthesizedExpression(tryResult),
2663+
if ((tryScope.functionReturned || catchScope.functionReturned) && returnedIdentifier) {
2664+
// Wrap catch in function if try or catch has return
2665+
const catchCall = tstl.createCallExpression(
2666+
tstl.createParenthesizedExpression(tstl.createFunctionExpression(catchBlock))
2667+
);
2668+
const catchAssign = tstl.createAssignmentStatement(
2669+
[tstl.cloneIdentifier(returnedIdentifier), tstl.cloneIdentifier(returnValueIdentifier)],
2670+
catchCall
2671+
);
2672+
catchBlock = tstl.createBlock([catchAssign]);
2673+
}
2674+
const notTryCondition = tstl.createUnaryExpression(
2675+
tstl.createParenthesizedExpression(tryResultIdentfier),
26192676
tstl.SyntaxKind.NotOperator
26202677
);
2621-
result.push(tstl.createIfStatement(notTryResult, this.transformBlock(statement.catchClause.block)));
2678+
result.push(tstl.createIfStatement(notTryCondition, catchBlock));
2679+
} else if (tryScope.functionReturned) {
2680+
// try with return, but no catch
2681+
returnedIdentifier = tstl.createIdentifier("____TS_returned");
2682+
const returnedVariables = [tryResultIdentfier, returnedIdentifier, returnValueIdentifier];
2683+
result.push(tstl.createVariableDeclarationStatement(returnedVariables, tryCall));
2684+
2685+
// change return condition from '____TS_returned' to '____TS_try and ____TS_returned'
2686+
returnCondition = tstl.createBinaryExpression(
2687+
tstl.cloneIdentifier(tryResultIdentfier),
2688+
returnedIdentifier,
2689+
tstl.SyntaxKind.AndOperator
2690+
);
26222691
} else {
2692+
// try without return or catch
26232693
result.push(tstl.createExpressionStatement(tryCall));
26242694
}
26252695

2626-
if (statement.finallyBlock) {
2627-
result.push(tstl.createDoStatement(this.transformBlock(statement.finallyBlock).statements));
2696+
if (statement.finallyBlock && statement.finallyBlock.statements.length > 0) {
2697+
result.push(...this.statementVisitResultToArray(this.transformBlockAsDoStatement(statement.finallyBlock)));
2698+
}
2699+
2700+
if (returnCondition && returnedIdentifier) {
2701+
// With catch clause:
2702+
// if ____TS_returned then return ____TS_returnValue end
2703+
// No catch clause:
2704+
// if ____TS_try and ____TS_returned then return ____TS_returnValue end
2705+
const returnValues: tstl.Expression[] = [];
2706+
const parentTryCatch = this.findScope(ScopeType.Function | ScopeType.Try | ScopeType.Catch);
2707+
if (parentTryCatch && parentTryCatch.type !== ScopeType.Function) {
2708+
// Nested try/catch needs to prefix a 'true' return value
2709+
returnValues.push(tstl.createBooleanLiteral(true));
2710+
}
2711+
if (tsHelper.isInTupleReturnFunction(statement, this.checker)) {
2712+
returnValues.push(this.createUnpackCall(tstl.cloneIdentifier(returnValueIdentifier)));
2713+
} else {
2714+
returnValues.push(tstl.cloneIdentifier(returnValueIdentifier));
2715+
}
2716+
const returnStatement = tstl.createReturnStatement(returnValues);
2717+
const ifReturnedStatement = tstl.createIfStatement(returnCondition, tstl.createBlock([returnStatement]));
2718+
result.push(ifReturnedStatement);
26282719
}
26292720

26302721
return tstl.createDoStatement(result, statement);
@@ -4980,7 +5071,7 @@ export class LuaTransformer {
49805071
return tstl.createCallExpression(tstl.createParenthesizedExpression(iife), [], tsOriginal);
49815072
}
49825073

4983-
protected createUnpackCall(expression: tstl.Expression | undefined, tsOriginal: ts.Node): tstl.Expression {
5074+
protected createUnpackCall(expression: tstl.Expression | undefined, tsOriginal?: ts.Node): tstl.Expression {
49845075
switch (this.luaTarget) {
49855076
case LuaTarget.Lua51:
49865077
case LuaTarget.LuaJIT:

0 commit comments

Comments
 (0)