packages feed

grapesy-1.2.0: src/Network/GRPC/Util/Exception/Shims.hs

{-# LANGUAGE CPP            #-}
{-# LANGUAGE ImplicitParams #-}

module Network.GRPC.Util.Exception.Shims (
    -- * Throwing
    throwIO
  , throwM
    -- * Showable wrapper for 'Backtraces' (or 'CallStack' for GHC < 9.10)
  , Backtraces
  , collectBacktraces
  , displayBacktraces
    -- * Make `annotateIO` a no-op for GHC < 9.10
  , annotateIO
  , addExceptionContext
    -- * Bug-free version of 'ExceptionWithContext'
  , WithAnnotations
  , pattern WithAnnotations
    -- * STM
#ifndef PATCHED_GHC_FOR_EXCEPTION_DEBUGGING
  , AtomicallyBacktrace(..)
  , atomically
#else
  , STM.atomically
#endif
  ) where

import Control.Concurrent.STM qualified as STM
import Control.Exception (Exception(..))
import Control.Exception qualified as Base
import Control.Monad.Catch qualified as Exceptions
import GHC.Stack

#if MIN_VERSION_base(4,20,0)
import Control.Exception.Annotation
import Control.Exception.Backtrace qualified as Backtrace
import Control.Exception.Context
#endif

#ifndef PATCHED_GHC_FOR_EXCEPTION_DEBUGGING
import Control.Concurrent.STM (STM)
import GHC.Generics
#endif

{-------------------------------------------------------------------------------
  Throwing

  This just adds the @HasCallStack@ constraint, even when it is technically
  speaking redundant (in older GHC). Avoids redundant constraints warnings
  upstream.
-------------------------------------------------------------------------------}

throwIO :: (Exception e, HasCallStack) => e -> IO a
throwIO = Base.throwIO
#if !MIN_VERSION_base(4,20,0)
  where
    _suppressRedundantConstraintWarning = callStack
#endif

throwM :: (Exception e, Exceptions.MonadThrow m, HasCallStack) => e -> m a
throwM = Exceptions.throwM
#if !MIN_VERSION_exceptions(0,10,6)
  where
    _suppressRedundantConstraintWarning = callStack
#endif

{-------------------------------------------------------------------------------
  Showable wrapper for 'Backtraces' (or 'CallStack' for GHC < 9.10)
-------------------------------------------------------------------------------}

#if !MIN_VERSION_base(4,20,0)
newtype Backtraces = WrapBacktraces {
      unwrapBacktraces :: CallStack
    }
  deriving stock (Show)

collectBacktraces :: HasCallStack => IO Backtraces
collectBacktraces = return $ WrapBacktraces GHC.Stack.callStack

displayBacktraces :: Backtraces -> String
displayBacktraces = prettyCallStack . unwrapBacktraces

#else

newtype Backtraces = WrapBacktraces {
      unwrapBacktraces :: Backtrace.Backtraces
    }

-- Frustratingly, 'Backtraces' does not have a law-abiding 'Show' instance
instance Show Backtraces where
  show = displayBacktraces

collectBacktraces :: HasCallStack => IO Backtraces
collectBacktraces = WrapBacktraces <$> Backtrace.collectBacktraces

displayBacktraces :: Backtraces -> String
displayBacktraces = Backtrace.displayBacktraces . unwrapBacktraces

#endif

{-------------------------------------------------------------------------------
  Make `annotateIO` a no-op for GHC < 9.10
-------------------------------------------------------------------------------}

#if !MIN_VERSION_base(4,20,0)

annotateIO :: ann -> IO a -> IO a
annotateIO _ = id

addExceptionContext :: ann -> Base.SomeException -> Base.SomeException
addExceptionContext _ = id

#else

annotateIO ::
     ExceptionAnnotation ann
  => ann -> IO a -> IO a
annotateIO = Base.annotateIO

addExceptionContext ::
     ExceptionAnnotation ann
  => ann -> Base.SomeException -> Base.SomeException
addExceptionContext = Base.addExceptionContext

#endif

{-------------------------------------------------------------------------------
  Bug-free version of 'ExceptionWithContext'

  In GHC 9.10 'ExceptionWithContext' is broken (throwing something of type
  @ExceptionWithContext SomeException@ will result in nested @SomeException@,
  breaking exception handlers). Prior to GHC 9.10 it is not available at all.
  The implementation here stays as close as possible to the one in GHC 9.14.
-------------------------------------------------------------------------------}

#if !MIN_VERSION_base(4,20,0)

data ExceptionContext = EmptyExceptionContext
  deriving stock (Show)

data WithAnnotations a = WithAnnotations ExceptionContext a
  deriving stock (Show)

instance Exception e => Exception (WithAnnotations e) where
  toException (WithAnnotations EmptyExceptionContext e) =
      toException e

  fromException se = do
      e <- fromException se
      return (WithAnnotations EmptyExceptionContext e)

  displayException = displayException . toException

#elif !MIN_VERSION_base(4,21,0)

-- | Bug-free replacement for 'ExceptionWithContext' in GHC 9.10
data WithAnnotations e = WithAnnotations ExceptionContext e
  deriving stock Generic

instance Exception a => Show (WithAnnotations a) where
  show (WithAnnotations _ctxt e) = show e

instance Exception a => Exception (WithAnnotations a) where
    toException (WithAnnotations ctxt e) =
        case toException e of
          Base.SomeException c ->
            let ?exceptionContext = ctxt
            in Base.SomeException c
    fromException se = do
        e <- fromException se
        return (WithAnnotations (Base.someExceptionContext se) e)
    backtraceDesired (WithAnnotations _ e) = backtraceDesired e
    displayException = displayException . toException

#else

type WithAnnotations = Base.ExceptionWithContext

pattern WithAnnotations :: ExceptionContext -> a -> WithAnnotations a
pattern WithAnnotations ctxt e = Base.ExceptionWithContext ctxt e

#endif

{-------------------------------------------------------------------------------
  STM
-------------------------------------------------------------------------------}

#ifdef PATCHED_GHC_FOR_EXCEPTION_DEBUGGING
-- Nothing to do, part of the patch
#else
-- | Backtrace to a call to 'atomically'
--
-- When an STM transaction throws an exception, this will tell us where that
-- tranaction was invoked (though not where /within/ the transaction it
-- threw an exception).
newtype AtomicallyBacktrace = AtomicallyBacktrace Backtraces
  deriving stock (Generic, Show)
#if MIN_VERSION_base(4,20,0)
  deriving anyclass (ExceptionAnnotation)
#endif

atomically :: HasCallStack => STM a -> IO a
atomically stm = do
    backtraces <- collectBacktraces
    annotateIO (AtomicallyBacktrace backtraces) $
      STM.atomically stm
#endif