packages feed

libclang-bindings-0.1.0.0: src/Clang/Internal/Exception.hs

{-# LANGUAGE CPP #-}
module Clang.Internal.Exception (
    ExactException(..)
  , throwExact
  , RunInIO
  , HandlerResult(..)
  , handleUnliftUsing
  ) where

import Control.Exception (Exception (..))
import Control.Exception qualified as Base
import Control.Monad.IO.Class (MonadIO (..))

{-------------------------------------------------------------------------------
  Internal: exception handling
-------------------------------------------------------------------------------}

-- | Newtype wrapper for throwing this /exact/ exception
--
-- In other words, including any exception annotations.
newtype ExactException = WrapExactException {
      unwrapExactException :: Base.SomeException
    }
  deriving stock (Show)

instance Exception ExactException where
  fromException    = Just . WrapExactException
  toException      = unwrapExactException
  displayException = displayException . unwrapExactException
#if MIN_VERSION_base(4,20,0)
  backtraceDesired = const False
#endif

-- | Type-specialized wrapper around throwIO, to avoid mistakes
--
-- Implementation note: does not need a 'HasCallStack' constraint, because
-- no new backtrace is added.
throwExact :: ExactException -> IO a
throwExact = Base.throwIO

type RunInIO m = forall a. m a -> IO a

-- | Exception handler result
--
-- This generalizes two functions:
--
-- * 'handle' through 'HandlerResult'
-- * 'onException' through 'HandlerRethrow'
--
-- See 'handleUnliftUsing'.
data HandlerResult a =
    -- | Handler dealt with the exception and computed a new result
    HandlerResult a

    -- | Handler dealt with the exception, perhaps freeing some resources,
    -- and wants to rethrow the original exception.
    --
    -- This mimicks the behaviour of 'onException'.
  | HandlerRethrow
  deriving stock (Show, Functor)

-- | Generalized 'handle'
--
-- If the exception handler throws an exception of its own, instead of returning
-- a result, a 'WhileHandling' annotation is added to that exception recording
-- the original exception that was being handled (in GHC >= 9.12).
--
-- Implementation note: We do not use @handle@ from @unlift@, as it excludes
-- async exceptions, and we want it to be up to the exception handler to decide
-- if it wants to deal with async exceptions or not.
handleUnliftUsing ::
     MonadIO n
  => RunInIO m
  -> (ExactException -> m (HandlerResult a))
  -> m a -> n a
handleUnliftUsing runInIO handler action = liftIO $
        Base.handle
          (\e -> Left . (e,) <$> runInIO (handler e))
          (Right <$> runInIO action)
    >>= aux
  where
    aux :: Either (ExactException, HandlerResult a) a -> IO a
    aux = \case
        Right a -> return a
        Left (e, handlerResult) ->
          case handlerResult of
            HandlerResult a -> return a
            HandlerRethrow  -> throwExact e