From 9e25767d705b6feba22d2c35afe0933e4b645d53 Mon Sep 17 00:00:00 2001 From: Cold Fry Date: Wed, 18 Mar 2026 19:27:47 +0000 Subject: [PATCH 1/2] fix try/finally not re-throwing errors when there is no catch clause (#1667) When a try block had a finally but no catch, pcall's error result was discarded, silently swallowing the exception. Now the error is captured and re-thrown after the finally block executes. --- src/transformation/visitors/errors.ts | 20 ++++++++++++++++++++ test/unit/error.spec.ts | 19 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/transformation/visitors/errors.ts b/src/transformation/visitors/errors.ts index f81519e4c..7233d709c 100644 --- a/src/transformation/visitors/errors.ts +++ b/src/transformation/visitors/errors.ts @@ -146,6 +146,10 @@ export const transformTryStatement: FunctionVisitor = (statemen returnedIdentifier, lua.SyntaxKind.AndOperator ); + } else if (statement.finallyBlock) { + // try without catch, but with finally — need to capture error for re-throw + const errorIdentifier = lua.createIdentifier("____error"); + result.push(lua.createVariableDeclarationStatement([tryResultIdentifier, errorIdentifier], tryCall)); } else { // try without return or catch result.push(lua.createExpressionStatement(tryCall)); @@ -155,6 +159,22 @@ export const transformTryStatement: FunctionVisitor = (statemen result.push(...context.transformStatements(statement.finallyBlock)); } + // Re-throw error if try had no catch but had a finally + if (!statement.catchClause && statement.finallyBlock) { + const notTryCondition = lua.createUnaryExpression( + lua.cloneIdentifier(tryResultIdentifier), + lua.SyntaxKind.NotOperator + ); + const errorIdentifier = lua.createIdentifier("____error"); + const rethrow = lua.createExpressionStatement( + lua.createCallExpression(lua.createIdentifier("error"), [ + lua.cloneIdentifier(errorIdentifier), + lua.createNumericLiteral(0), + ]) + ); + result.push(lua.createIfStatement(notTryCondition, lua.createBlock([rethrow]))); + } + if (returnCondition && returnedIdentifier) { const returnValues: lua.Expression[] = []; diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index af69d7e1d..592fd7939 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -256,6 +256,25 @@ test("multi return from catch->finally", () => { .expectToMatchJsResult(); }); +test("throw propagates through finally to outer catch", () => { + util.testFunction` + function test() { + try { + throw "Test error"; + } finally { + } + } + + let result = 0; + try { + test(); + } catch (e) { + result += 1; + } + return result; + `.expectToMatchJsResult(); +}); + test("return from nested finally", () => { util.testFunction` let x = ""; From 1afe73b8833f548578cc3d9cd560c75c30ebc12f Mon Sep 17 00:00:00 2001 From: Cold Fry Date: Wed, 18 Mar 2026 19:44:24 +0000 Subject: [PATCH 2/2] Nicer test return values --- test/unit/error.spec.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index 592fd7939..78444c60a 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -258,20 +258,23 @@ test("multi return from catch->finally", () => { test("throw propagates through finally to outer catch", () => { util.testFunction` - function test() { + function thrower() { try { - throw "Test error"; + throw "Error"; } finally { } } - let result = 0; - try { - test(); - } catch (e) { - result += 1; + function caller() { + try { + thrower(); + return "NoCatch"; + } catch (e) { + return e; + } } - return result; + + return caller(); `.expectToMatchJsResult(); });