{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE ImplicitParams #-}
-- | Please see the README.md file in the safe-exceptions repo for
-- information on how to use this module. Relevant links:
--
-- * https://github.com/fpco/safe-exceptions#readme
--
-- * https://www.stackage.org/package/safe-exceptions
module Control.Exception.Safe
    ( -- * Throwing
      throw
    , throwIO
    , throwM
    , throwString
    , StringException (..)
    , throwTo
    , impureThrow
      -- * Catching (with recovery)
    , catch
    , catchIO
    , catchAny
    , catchDeep
    , catchAnyDeep
    , catchAsync
    , catchJust

    , handle
    , handleIO
    , handleAny
    , handleDeep
    , handleAnyDeep
    , handleAsync
    , handleJust

    , try
    , tryIO
    , tryAny
    , tryDeep
    , tryAnyDeep
    , tryAsync
    , tryJust

    , Handler(..)
    , catches
    , catchesDeep
    , catchesAsync

      -- * Cleanup (no recovery)
    , onException
    , bracket
    , bracket_
    , finally
    , withException
    , bracketOnError
    , bracketOnError_
    , bracketWithError

      -- * Coercion to sync and async
    , SyncExceptionWrapper (..)
    , toSyncException
    , AsyncExceptionWrapper (..)
    , toAsyncException

      -- * Check exception type
    , isSyncException
    , isAsyncException
      -- * Reexports
    , C.MonadThrow
    , C.MonadCatch
    , C.MonadMask (..)
    , C.mask_
    , C.uninterruptibleMask_
    , C.catchIOError
    , C.handleIOError
    -- FIXME , C.tryIOError
    , Exception (..)
    , Typeable
    , SomeException (..)
    , SomeAsyncException (..)
    , E.IOException
    , E.assert
#if !MIN_VERSION_base(4,8,0)
    , displayException
#endif
    ) where

import Control.Concurrent (ThreadId)
import Control.DeepSeq (($!!), NFData)
import Control.Exception (Exception (..), SomeException (..), SomeAsyncException (..))
import qualified Control.Exception as E
import qualified Control.Monad.Catch as C
import Control.Monad.Catch (Handler (..))
import Control.Monad (liftM, void)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.Typeable (Typeable, cast)

#if MIN_VERSION_base(4,9,0)
import GHC.Stack (prettySrcLoc)
import GHC.Stack.Types (HasCallStack, CallStack, getCallStack)
#endif

#if MIN_VERSION_base(4,9,0) && MIN_VERSION_exceptions(0,10,6)
import GHC.Stack (withFrozenCallStack)
#endif

-- The exceptions package that safe-exceptions is based on added HasCallStack
-- to many of its functions in 0.10.6:
--
-- https://github.com/ekmett/exceptions/pull/90
-- https://github.com/ekmett/exceptions/pull/92
--
-- We make the same change here. The following comment has been lifted
-- verbatim from exceptions:
--
-- We use the following bit of CPP to enable the use of HasCallStack
-- constraints without breaking the build for pre-8.0 GHCs, which did not
-- provide GHC.Stack. We are careful to always write constraints like this:
--
--   HAS_CALL_STACK => MonadThrow m => ...
--
-- Instead of like this:
--
--   (HAS_CALL_STACK, MonadThrow e) => ...
--
-- The latter is equivalent to (() :: Constraint, MonadThrow e) => ..., which
-- requires ConstraintKinds. More importantly, it's slightly less efficient,
-- since it requires passing an empty constraint tuple dictionary around.
--
-- Note that we do /not/ depend on the call-stack compatibility library to
-- provide HasCallStack on older GHCs. We tried this at one point, but we
-- discovered that downstream libraries failed to build because combining
-- call-stack with GeneralizedNewtypeDeriving on older GHCs would require the
-- use of ConstraintKinds/FlexibleContexts, which downstream libraries did not
-- enable. (See #91.) The CPP approach that we use now, while somewhat clunky,
-- avoids these issues by not requiring any additional language extensions for
-- downstream users.
#if MIN_VERSION_base(4,9,0) && MIN_VERSION_exceptions(0,10,6)
# define HAS_CALL_STACK HasCallStack
#else
# define HAS_CALL_STACK ()
withFrozenCallStack :: a -> a
withFrozenCallStack a = a
#endif

-- | Synchronously throw the given exception
--
-- @since 0.1.0.0
throw :: HAS_CALL_STACK => (C.MonadThrow m, Exception e) => e -> m a
throw = C.throwM . toSyncException

-- | Synonym for 'throw'
--
-- @since 0.1.0.0
throwIO :: HAS_CALL_STACK => (C.MonadThrow m, Exception e) => e -> m a
throwIO = withFrozenCallStack throw

-- | Synonym for 'throw'
--
-- @since 0.1.0.0
throwM :: HAS_CALL_STACK => (C.MonadThrow m, Exception e) => e -> m a
throwM = withFrozenCallStack throw

-- | A convenience function for throwing a user error. This is useful
-- for cases where it would be too high a burden to define your own
-- exception type.
--
-- This throws an exception of type 'StringException'. When GHC
-- supports it (base 4.9 and GHC 8.0 and onward), it includes a call
-- stack.
--
-- @since 0.1.5.0
#if MIN_VERSION_base(4,9,0)
throwString :: (C.MonadThrow m, HasCallStack) => String -> m a
throwString s = throwM (StringException s ?callStack)
#else
throwString :: C.MonadThrow m => String -> m a
throwString s = throwM (StringException s ())
#endif

-- | Exception type thrown by 'throwString'.
--
-- Note that the second field of the data constructor depends on
-- GHC/base version. For base 4.9 and GHC 8.0 and later, the second
-- field is a call stack. Previous versions of GHC and base do not
-- support call stacks, and the field is simply unit (provided to make
-- pattern matching across GHC versions easier).
--
-- @since 0.1.5.0
#if MIN_VERSION_base(4,9,0)
data StringException = StringException String CallStack
  deriving Typeable

instance Show StringException where
    show (StringException s cs) = concat
        $ "Control.Exception.Safe.throwString called with:\n\n"
        : s
        : "\nCalled from:\n"
        : map go (getCallStack cs)
      where
        go (x, y) = concat
          [ "  "
          , x
          , " ("
          , prettySrcLoc y
          , ")\n"
          ]
#else
data StringException = StringException String ()
  deriving Typeable

instance Show StringException where
    show (StringException s _) = "Control.Exception.Safe.throwString called with:\n\n" ++ s
#endif
instance Exception StringException

-- | Throw an asynchronous exception to another thread.
--
-- Synchronously typed exceptions will be wrapped into an
-- `AsyncExceptionWrapper`, see
-- <https://github.com/fpco/safe-exceptions#determining-sync-vs-async>
--
-- It's usually a better idea to use the async package, see
-- <https://github.com/fpco/safe-exceptions#quickstart>
--
-- @since 0.1.0.0
throwTo :: (Exception e, MonadIO m) => ThreadId -> e -> m ()
throwTo tid = liftIO . E.throwTo tid . toAsyncException

-- | Generate a pure value which, when forced, will synchronously
-- throw the given exception
--
-- Generally it's better to avoid using this function and instead use 'throw',
-- see <https://github.com/fpco/safe-exceptions#quickstart>
--
-- @since 0.1.0.0
impureThrow :: HAS_CALL_STACK => Exception e => e -> a
impureThrow = E.throw . toSyncException

-- | Same as upstream 'C.catch', but will not catch asynchronous
-- exceptions
--
-- @since 0.1.0.0
catch :: HAS_CALL_STACK => (C.MonadCatch m, Exception e) => m a -> (e -> m a) -> m a
catch f g = f `C.catch` \e ->
    if isSyncException e
        then g e
        -- intentionally rethrowing an async exception synchronously,
        -- since we want to preserve async behavior
        else C.throwM e

-- | 'C.catch' specialized to only catching 'E.IOException's
--
-- @since 0.1.3.0
catchIO :: HAS_CALL_STACK => C.MonadCatch m => m a -> (E.IOException -> m a) -> m a
catchIO = withFrozenCallStack C.catch

-- | 'catch' specialized to catch all synchronous exception
--
-- @since 0.1.0.0
catchAny :: HAS_CALL_STACK => C.MonadCatch m => m a -> (SomeException -> m a) -> m a
catchAny = withFrozenCallStack catch

-- | Same as 'catch', but fully force evaluation of the result value
-- to find all impure exceptions.
--
-- @since 0.1.1.0
catchDeep :: HAS_CALL_STACK => (C.MonadCatch m, MonadIO m, Exception e, NFData a)
          => m a -> (e -> m a) -> m a
catchDeep = withFrozenCallStack catch . evaluateDeep

-- | Internal helper function
evaluateDeep :: (MonadIO m, NFData a) => m a -> m a
evaluateDeep action = do
    res <- action
    liftIO (E.evaluate $!! res)

-- | 'catchDeep' specialized to catch all synchronous exception
--
-- @since 0.1.1.0
catchAnyDeep :: HAS_CALL_STACK => (C.MonadCatch m, MonadIO m, NFData a) => m a -> (SomeException -> m a) -> m a
catchAnyDeep = withFrozenCallStack catchDeep

-- | 'catch' without async exception safety
--
-- Generally it's better to avoid using this function since we do not want to
-- recover from async exceptions, see
-- <https://github.com/fpco/safe-exceptions#quickstart>
--
-- @since 0.1.0.0
catchAsync :: HAS_CALL_STACK => (C.MonadCatch m, Exception e) => m a -> (e -> m a) -> m a
catchAsync = C.catch

-- | 'catchJust' is like 'catch' but it takes an extra argument which
-- is an exception predicate, a function which selects which type of
-- exceptions we're interested in.
--
-- @since 0.1.4.0
catchJust :: HAS_CALL_STACK => (C.MonadCatch m, Exception e) => (e -> Maybe b) -> m a -> (b -> m a) -> m a
catchJust f a b = withFrozenCallStack catch a (\e -> maybe (throwM e) b $ f e)

-- | Flipped version of 'catch'
--
-- @since 0.1.0.0
handle :: HAS_CALL_STACK => (C.MonadCatch m, Exception e) => (e -> m a) -> m a -> m a
handle = flip (withFrozenCallStack catch)

-- | 'C.handle' specialized to only catching 'E.IOException's
--
-- @since 0.1.3.0
handleIO :: HAS_CALL_STACK => C.MonadCatch m => (E.IOException -> m a) -> m a -> m a
handleIO = withFrozenCallStack C.handle


-- | Flipped version of 'catchAny'
--
-- @since 0.1.0.0
handleAny :: HAS_CALL_STACK => C.MonadCatch m => (SomeException -> m a) -> m a -> m a
handleAny = flip (withFrozenCallStack catchAny)

-- | Flipped version of 'catchDeep'
--
-- @since 0.1.1.0
handleDeep :: HAS_CALL_STACK => (C.MonadCatch m, Exception e, MonadIO m, NFData a) => (e -> m a) -> m a -> m a
handleDeep = flip (withFrozenCallStack catchDeep)

-- | Flipped version of 'catchAnyDeep'
--
-- @since 0.1.1.0
handleAnyDeep :: HAS_CALL_STACK => (C.MonadCatch m, MonadIO m, NFData a) => (SomeException -> m a) -> m a -> m a
handleAnyDeep = flip (withFrozenCallStack catchAnyDeep)

-- | Flipped version of 'catchAsync'
--
-- Generally it's better to avoid using this function since we do not want to
-- recover from async exceptions, see
-- <https://github.com/fpco/safe-exceptions#quickstart>
--
-- @since 0.1.0.0
handleAsync :: HAS_CALL_STACK => (C.MonadCatch m, Exception e) => (e -> m a) -> m a -> m a
handleAsync = C.handle

-- | Flipped 'catchJust'.
--
-- @since 0.1.4.0
handleJust :: HAS_CALL_STACK => (C.MonadCatch m, Exception e) => (e -> Maybe b) -> (b -> m a) -> m a -> m a
handleJust f = flip (withFrozenCallStack catchJust f)

-- | Same as upstream 'C.try', but will not catch asynchronous
-- exceptions
--
-- @since 0.1.0.0
try :: HAS_CALL_STACK => (C.MonadCatch m, E.Exception e) => m a -> m (Either e a)
try f = withFrozenCallStack catch (liftM Right f) (return . Left)

-- | 'C.try' specialized to only catching 'E.IOException's
--
-- @since 0.1.3.0
tryIO :: HAS_CALL_STACK => C.MonadCatch m => m a -> m (Either E.IOException a)
tryIO = withFrozenCallStack C.try

-- | 'try' specialized to catch all synchronous exceptions
--
-- @since 0.1.0.0
tryAny :: HAS_CALL_STACK => C.MonadCatch m => m a -> m (Either SomeException a)
tryAny = withFrozenCallStack try

-- | Same as 'try', but fully force evaluation of the result value
-- to find all impure exceptions.
--
-- @since 0.1.1.0
tryDeep :: HAS_CALL_STACK => (C.MonadCatch m, MonadIO m, E.Exception e, NFData a) => m a -> m (Either e a)
tryDeep f = withFrozenCallStack catch (liftM Right (evaluateDeep f)) (return . Left)

-- | 'tryDeep' specialized to catch all synchronous exceptions
--
-- @since 0.1.1.0
tryAnyDeep :: HAS_CALL_STACK => (C.MonadCatch m, MonadIO m, NFData a) => m a -> m (Either SomeException a)
tryAnyDeep = withFrozenCallStack tryDeep

-- | 'try' without async exception safety
--
-- Generally it's better to avoid using this function since we do not want to
-- recover from async exceptions, see
-- <https://github.com/fpco/safe-exceptions#quickstart>
--
-- @since 0.1.0.0
tryAsync :: HAS_CALL_STACK => (C.MonadCatch m, E.Exception e) => m a -> m (Either e a)
tryAsync = C.try

-- | A variant of 'try' that takes an exception predicate to select
-- which exceptions are caught.
--
-- @since 0.1.4.0
tryJust :: HAS_CALL_STACK => (C.MonadCatch m, Exception e) => (e -> Maybe b) -> m a -> m (Either b a)
tryJust f a = withFrozenCallStack catch (Right `liftM` a) (\e -> maybe (throwM e) (return . Left) (f e))

-- | Async safe version of 'E.onException'
--
-- @since 0.1.0.0
onException :: HAS_CALL_STACK => C.MonadMask m => m a -> m b -> m a
onException thing after = withFrozenCallStack withException thing (\(_ :: SomeException) -> after)

-- Note: [withFrozenCallStack impredicativity]
--
-- We do not currently use 'withFrozenCallStack' in 'withException' or the similar
-- 'finally' due to impredicativity. That is, we would like to be consistent
-- with other functions and apply 'withFrozenCallStack' to the _handler only_ i.e.
--
--     withException thing after = withFrozenCallStack C.uninterruptibleMask $ \restore ...
--
-- Alas, that fails due to impredicativity:
--
--     • Couldn't match type: m a -> m a
--                      with: forall a1. m a1 -> m a1
--       Expected: (forall a1. m a1 -> m a1) -> m a
--         Actual: (m a -> m a) -> m a
--
-- Armed with -XImpredicativeTypes, we can define:
--
--     uninterruptibleMaskFrozen :: forall m b. C.MonadMask m => ((forall a. m a -> m a) -> m b) -> m b
--     uninterruptibleMaskFrozen = withFrozenCallStack C.uninterruptibleMask
--
-- and then
--
--     withException thing after = uninterruptibleMaskFrozen $ \restore -> do ...
--
-- But we cannot rely on -XImpredicativeTypes until GHC 9.2 is the oldest
-- supported release, and even then it is worth asking if the benefit
-- (consistency, omit handler from CallStack) is worth the cost (powerful,
-- relatively exotic extension).

-- | Like 'onException', but provides the handler the thrown
-- exception.
--
-- @since 0.1.0.0
withException :: HAS_CALL_STACK => (C.MonadMask m, E.Exception e) => m a -> (e -> m b) -> m a
withException thing after = C.uninterruptibleMask $ \restore -> do
    fmap fst $ C.generalBracket (pure ()) cAfter (const $ restore thing)
  where
    -- ignore the exception from after, see bracket for explanation
    cAfter () (C.ExitCaseException se) | Just ex <- fromException se =
        ignoreExceptions $ after ex
    cAfter () _ = pure ()

-- | Async safe version of 'E.bracket'
--
-- @since 0.1.0.0
bracket :: forall m a b c. HAS_CALL_STACK => C.MonadMask m
        => m a -> (a -> m b) -> (a -> m c) -> m c
bracket before after = withFrozenCallStack bracketWithError before (const after)

-- | Async safe version of 'E.bracket_'
--
-- @since 0.1.0.0
bracket_ :: HAS_CALL_STACK => C.MonadMask m => m a -> m b -> m c -> m c
bracket_ before after thing = withFrozenCallStack bracket before (const after) (const thing)

-- See Note [withFrozenCallStack impredicativity]

-- | Async safe version of 'E.finally'
--
-- @since 0.1.0.0
finally :: HAS_CALL_STACK => C.MonadMask m => m a -> m b -> m a
finally thing after = C.uninterruptibleMask $ \restore -> do
    fmap fst $ C.generalBracket (pure ()) cAfter (const $ restore thing)
  where
    -- ignore the exception from after, see bracket for explanation
    cAfter () (C.ExitCaseException se) =
        ignoreExceptions after
    cAfter () _ = void after

-- | Async safe version of 'E.bracketOnError'
--
-- @since 0.1.0.0
bracketOnError :: forall m a b c. HAS_CALL_STACK => C.MonadMask m
               => m a -> (a -> m b) -> (a -> m c) -> m c
bracketOnError before after thing = fmap fst $ withFrozenCallStack C.generalBracket before cAfter thing
  where
    -- ignore the exception from after, see bracket for explanation
    cAfter x (C.ExitCaseException se) =
        C.uninterruptibleMask_ $ ignoreExceptions $ after x
    cAfter x _ = pure ()


-- | A variant of 'bracketOnError' where the return value from the first
-- computation is not required.
--
-- @since 0.1.0.0
bracketOnError_ :: HAS_CALL_STACK => C.MonadMask m => m a -> m b -> m c -> m c
bracketOnError_ before after thing = withFrozenCallStack bracketOnError before (const after) (const thing)

-- | Async safe version of 'E.bracket' with access to the exception in the
-- cleanup action.
--
-- @since 0.1.7.0
bracketWithError :: forall m a b c. HAS_CALL_STACK => C.MonadMask m
        => m a -> (Maybe SomeException -> a -> m b) -> (a -> m c) -> m c
bracketWithError before after thing = fmap fst $ withFrozenCallStack C.generalBracket before cAfter thing
  where
    cAfter x (C.ExitCaseException se) =
        C.uninterruptibleMask_ $ ignoreExceptions $ after (Just se) x
    cAfter x _ =
        void $ C.uninterruptibleMask_ $ after Nothing x

-- | Internal function that swallows all exceptions, used in some bracket-like
-- combinators. When it's run inside of uninterruptibleMask, we know that
-- no async exceptions can be thrown from thing, so the other exception from
-- the combinator will not be overridden.
--
-- https://github.com/fpco/safe-exceptions/issues/2
ignoreExceptions :: C.MonadMask m => m a -> m ()
ignoreExceptions thing = void thing `C.catch` (\(_ :: SomeException) -> pure ())

-- | Wrap up an asynchronous exception to be treated as a synchronous
-- exception
--
-- This is intended to be created via 'toSyncException'
--
-- @since 0.1.0.0
data SyncExceptionWrapper = forall e. Exception e => SyncExceptionWrapper e
    deriving Typeable
instance Show SyncExceptionWrapper where
    show (SyncExceptionWrapper e) = show e
instance Exception SyncExceptionWrapper where
#if MIN_VERSION_base(4,8,0)
    displayException (SyncExceptionWrapper e) = displayException e
#endif

-- | Convert an exception into a synchronous exception
--
-- For synchronous exceptions, this is the same as 'toException'.
-- For asynchronous exceptions, this will wrap up the exception with
-- 'SyncExceptionWrapper'
--
-- @since 0.1.0.0
toSyncException :: Exception e => e -> SomeException
toSyncException e =
    case fromException se of
        Just (SomeAsyncException _) -> toException (SyncExceptionWrapper e)
        Nothing -> se
  where
    se = toException e

-- | Wrap up a synchronous exception to be treated as an asynchronous
-- exception
--
-- This is intended to be created via 'toAsyncException'
--
-- @since 0.1.0.0
data AsyncExceptionWrapper = forall e. Exception e => AsyncExceptionWrapper e
    deriving Typeable
instance Show AsyncExceptionWrapper where
    show (AsyncExceptionWrapper e) = show e
instance Exception AsyncExceptionWrapper where
    toException = toException . SomeAsyncException
    fromException se = do
        SomeAsyncException e <- fromException se
        cast e
#if MIN_VERSION_base(4,8,0)
    displayException (AsyncExceptionWrapper e) = displayException e
#endif

-- | Convert an exception into an asynchronous exception
--
-- For asynchronous exceptions, this is the same as 'toException'.
-- For synchronous exceptions, this will wrap up the exception with
-- 'AsyncExceptionWrapper'
--
-- @since 0.1.0.0
toAsyncException :: Exception e => e -> SomeException
toAsyncException e =
    case fromException se of
        Just (SomeAsyncException _) -> se
        Nothing -> toException (AsyncExceptionWrapper e)
  where
    se = toException e

-- | Check if the given exception is synchronous
--
-- @since 0.1.0.0
isSyncException :: Exception e => e -> Bool
isSyncException e =
    case fromException (toException e) of
        Just (SomeAsyncException _) -> False
        Nothing -> True

-- | Check if the given exception is asynchronous
--
-- @since 0.1.0.0
isAsyncException :: Exception e => e -> Bool
isAsyncException = not . isSyncException
{-# INLINE isAsyncException #-}

#if !MIN_VERSION_base(4,8,0)
-- | A synonym for 'show', specialized to 'Exception' instances.
--
-- Starting with base 4.8, the 'Exception' typeclass has a method @displayException@, used for user-friendly display of exceptions. This function provides backwards compatibility for users on base 4.7 and earlier, so that anyone importing this module can simply use @displayException@.
--
-- @since 0.1.1.0
displayException :: Exception e => e -> String
displayException = show
#endif

-- | Same as upstream 'C.catches', but will not catch asynchronous
-- exceptions
--
-- @since 0.1.2.0
catches :: HAS_CALL_STACK => (C.MonadCatch m, C.MonadThrow m) => m a -> [Handler m a] -> m a
catches io handlers = withFrozenCallStack catch io (catchesHandler handlers)

-- | Same as 'catches', but fully force evaluation of the result value
-- to find all impure exceptions.
--
-- @since 0.1.2.0
catchesDeep :: HAS_CALL_STACK => (C.MonadCatch m, C.MonadThrow m, MonadIO m, NFData a) => m a -> [Handler m a] -> m a
catchesDeep io handlers = withFrozenCallStack catch (evaluateDeep io) (catchesHandler handlers)

-- | 'catches' without async exception safety
--
-- Generally it's better to avoid using this function since we do not want to
-- recover from async exceptions, see
-- <https://github.com/fpco/safe-exceptions#quickstart>
--
-- @since 0.1.2.0
catchesAsync :: HAS_CALL_STACK => (C.MonadCatch m, C.MonadThrow m) => m a -> [Handler m a] -> m a
catchesAsync io handlers = io `catchAsync` catchesHandler handlers

catchesHandler :: HAS_CALL_STACK => (C.MonadThrow m) => [Handler m a] -> SomeException -> m a
catchesHandler handlers e = foldr tryHandler (C.throwM e) handlers
    where tryHandler (Handler handler) res
              = case fromException e of
                Just e' -> handler e'
                Nothing -> res
