Skip to content

Fix try/catch/finally flow#1729

Merged
Perryvw merged 3 commits into
TypeScriptToLua:masterfrom
SuperPaintman:1137-fix-try-catch-finally
Jun 30, 2026
Merged

Fix try/catch/finally flow#1729
Perryvw merged 3 commits into
TypeScriptToLua:masterfrom
SuperPaintman:1137-fix-try-catch-finally

Conversation

@SuperPaintman

@SuperPaintman SuperPaintman commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

TL;DR

This PR fixes the issue described in #1137.

try/catch/finally blocks didn't behave correctly when the catch block threw (or re-threw) an error. The finally block would never execute because the error from catch propagated immediately.

Example:

InputOutput (-before, +after)
declare function compute(this: void): void

function foo() {
    try {
        compute()
    } catch (err) {
        print("Reporting the error ...")
        throw err;
    } finally {
        print("Cleaning up resources...")
    }
}
 function foo(self)
     do
         local function ____catch(err)
             print("Reporting the error ...")
             error(err, 0)
         end
         local ____try, ____hasReturned = pcall(function()
             compute()
         end)
+        local ____rethrow
         if not ____try then
-            ____catch(____hasReturned)
+            ____try, ____rethrow = pcall(function()
+                ____catch(____hasReturned)
+            end)
         end
         do
             print("Cleaning up resources...")
         end
+        if not ____try then
+            error(____rethrow, 0)
+        end
     end
 end

Logs (-before, +after)

 Reporting the error ...
+Cleaning up resources...
 stdin:8: attempt to call global 'compute' (a nil value)
 stack traceback:
 	[C]: in function 'error'
-	stdin:5: in function '____catch'
-	stdin:11: in function 'foo'
+	stdin:20: in function 'foo'
 	stdin:1: in main chunk

How the fix works

Note: This change only affects try/catch/finally blocks. try/catch and try/finally are unchanged.

We capture any errors from catch block into a ____rethrow variable, then let the finally execute, and only then re-throw the error.

A few more examples

From unit tests (was broken) ("re-throw (%p)")
--- ./testdata-old/testcase-0002.lua
+++ ./testdata/testcase-0002.lua
@@ -1,55 +1,67 @@
 local ____exports = {}
 function ____exports.__main(self)
     local innerTry = false
     local innerFinally = false
     local outerFinally = false
     local function foo(self)
         do
             local function ____catch(e)
                 error((e .. "->") .. "outer.catch", 0)
             end
             local ____try, ____hasReturned = pcall(function()
                 do
                     local function ____catch(e)
                         error((e .. "->") .. "inner.catch", 0)
                     end
                     local ____try, ____hasReturned = pcall(function()
                         if innerTry then
                             error("inner.try", 0)
                         end
                     end)
+                    local ____rethrow
                     if not ____try then
-                        ____catch(____hasReturned)
+                        ____try, ____rethrow = pcall(function()
+                            ____catch(____hasReturned)
+                        end)
                     end
                     do
                         if innerFinally then
                             error("inner.finally", 0)
                         end
                     end
+                    if not ____try then
+                        error(____rethrow, 0)
+                    end
                 end
             end)
+            local ____rethrow
             if not ____try then
-                ____catch(____hasReturned)
+                ____try, ____rethrow = pcall(function()
+                    ____catch(____hasReturned)
+                end)
             end
             do
                 if outerFinally then
                     error("outer.finally", 0)
                 end
             end
+            if not ____try then
+                error(____rethrow, 0)
+            end
         end
     end
     local result = "x"
     do
         local function ____catch(e)
             result = e
         end
         local ____try, ____hasReturned = pcall(function()
             foo(nil)
         end)
         if not ____try then
             ____catch(____hasReturned)
         end
     end
     return result
 end
 return ____exports
From unit tests ("return from catch->finally")
--- ./testdata-old/testcase-0020.lua
+++ ./testdata/testcase-0020.lua
@@ -1,29 +1,35 @@
 local ____exports = {}
 function ____exports.__main(self)
     local x = "unevaluated"
     local function evaluate(self, arg)
         x = "evaluated"
         return arg
     end
     local function foobar(self)
         do
             local function ____catch(e)
                 return true, evaluate(nil, e)
             end
             local ____try, ____hasReturned, ____returnValue = pcall(function()
                 error("foobar", 0)
             end)
+            local ____rethrow
             if not ____try then
-                ____hasReturned, ____returnValue = ____catch(____hasReturned)
+                ____try, ____rethrow = pcall(function()
+                    ____hasReturned, ____returnValue = ____catch(____hasReturned)
+                end)
             end
             do
                 return "finally"
             end
+            if not ____try then
+                error(____rethrow, 0)
+            end
             if ____hasReturned then
                 return ____returnValue
             end
         end
     end
     return (tostring(foobar(nil)) .. " ") .. x
 end
 return ____exports

Resources

Closes: #1137

@SuperPaintman SuperPaintman marked this pull request as ready for review June 28, 2026 05:11
@SuperPaintman

Copy link
Copy Markdown
Contributor Author

CC: @Perryvw as the author of #1137

@Perryvw Perryvw left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks fine, just needs a npm run fix:prettier

@SuperPaintman

Copy link
Copy Markdown
Contributor Author

@Perryvw should be formatted now

@Perryvw Perryvw merged commit b248e41 into TypeScriptToLua:master Jun 30, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

try/finally does not behave as expected when rethrowing

2 participants