{-# LANGUAGE DeriveDataTypeable, ExistentialQuantification #-}
-- |
-- It is often useful to distinguish between synchronous and asynchronous
-- exceptions. The common idiom is to run a user-supplied computation
-- catching any synchronous exceptions but allowing asynchronous exceptions
-- (such as user interrupt) pass through.
--
-- There's no way to know how — synchronously or asynchronously — an
-- exception was thrown, so we have to work around it by relying on the
-- exception type itself.
--
-- This module provides an extensible type for asynchronous exceptions
-- — 'SomeAsyncException' — as well as functions for catching synchronous
-- exceptions.

module Control.Exception.Async
  ( SomeAsyncException
  , asyncToException
  , asyncFromException
  , isAsynchronous
  , catchSync
  , handleSync
  , trySync
  )
  where

import Control.Exception as E
import Control.Monad
import Data.Typeable
import Data.Maybe

-- | Exception class for asynchronous exceptions.
--
-- To mark an exception as asynchronous:
--
-- >instance Exception MyException where
-- >  fromException = asyncFromException
-- >  toException = asyncToException
--
-- Note that as of base 4.6, 'AsyncException' is not yet a subclass of
-- 'SomeAsyncException'. Use 'isAsynchronous' to recognize both
-- 'AsyncException' and 'SomeAsyncException'.
data SomeAsyncException
  = forall e . Exception e => SomeAsyncException e
  deriving Typeable

instance Exception SomeAsyncException

instance Show SomeAsyncException where
  showsPrec p (SomeAsyncException e) = showsPrec p e

-- | 'toException' implementation for asynchronous exceptions
asyncToException :: Exception e => e -> SomeException
asyncToException = toException . SomeAsyncException

-- | 'fromException' implementation for asynchronous exceptions
asyncFromException :: Exception e => SomeException -> Maybe e
asyncFromException x = do
  SomeException e <- fromException x
  cast e

-- | Check whether an exception is asynchronous
isAsynchronous :: SomeException -> Bool
isAsynchronous e =
  isJust (fromException e :: Maybe AsyncException) ||
  isJust (fromException e :: Maybe SomeAsyncException)


-- | Like 'catch', but catch any synchronous exceptions; let asynchronous ones pass through
catchSync :: IO a -> (SomeException -> IO a) -> IO a
catchSync a h =
  E.catch a $ \e ->
    if isAsynchronous e
      then throwIO e
      else h e

-- | Like 'handle', but catch any synchronous exceptions; let asynchronous ones pass through
handleSync :: (SomeException -> IO a) -> IO a -> IO a
handleSync = flip catchSync

-- | Like 'try', but catch any synchronous exceptions; let asynchronous ones pass through
trySync :: IO b -> IO (Either SomeException b)
trySync a =
  catchSync
    (liftM Right a)
    (return . Left)