Exception Annotations: Lay of the Land
Embarrassingly, I hadn’t considered WhileHandling then thinking about the rethrow behaviours. I was thinking about the possibility of ending up with duplicate Backtraces annotations in the same annotation list.
I’ve created the this repo to experiment (using GHC 9.14.1)
It has three cases:
catchAndThrow :: IO a -> IO a
catchAndThrow action = catch @SomeException action throwIO
-- like 'catchAndThrow', but with a type of exception for which 'toException'
-- preserves context, here we used 'ExceptionWithContext'
catchAndThrow' :: IO a -> IO a
catchAndThrow' action = catch @(ExceptionWithContext SomeException) action throwIO
-- after migrating to 'catchNoPropagate' + 'rethrowIO'
catchAndRethrow :: IO a -> IO a
catchAndRethrow action = catchNoPropagate @(ExceptionWithContext SomeException) action rethrowIO
In the first case, the exception structure looks like
IOException
|
+- WhileHandling
| |
| `- IOException
| |
| `- Backtrace: HasCallStack backtrace:/ throwIO, called at app/Main.hs:30:7 ...
|
`- Backtrace: HasCallStack backtrace:/ throwIO, called at app/Main.hs:15:62 ...
The original exception is inside the WhileHandling annotation, with the original backtrace. The rethrown exception has the backtrace of the rethrow.
For the second case (the exception whose toException doesn’t clean the context) we have
IOException
|
+- WhileHandling
| |
| `- IOException
| |
| `- Backtrace: HasCallStack backtrace:/ throwIO, called at app/Main.hs:30:7 ...
|
+- Backtrace: HasCallStack backtrace:/ throwIO, called at app/Main.hs:20:84 ...
|
`- Backtrace: HasCallStack backtrace:/ throwIO, called at app/Main.hs:30:7 ...
The WhileHandling is still there, but the rethrown exception (confusingly?) has two Backtraces annotations.
In the third case, the rethrow is not visible in the exception, we only have the original Backtraces:
IOException
|
`- Backtrace: HasCallStack backtrace:/ throwIO, called at app/Main.hs:30:7 ...
Discussion in the ATmosphere