ekmett / exceptions

mtl friendly exceptions
Other
49 stars 32 forks source link

`exceptions-0.10.6` breaks `GeneralizedNewtypeDeriving` on pre-8.0 GHCs unless the right extensions are enabled #91

Closed RyanGlScott closed 1 year ago

RyanGlScott commented 1 year ago

I recently noticed that building criterion-1.6.0.0 on GHC 7.10.3 or earlier using exceptions-0.10.6 will fail (example):

Failed to build criterion-1.6.0.0.
Build log ( /github/home/.cabal/logs/ghc-7.10.3/criterion-1.6.0.0-9cd2fd521a95b83c0fe29c1cf9fa357e9061f7e4f25468fdd4e13f93b10aa05f.log ):
Configuring library for criterion-1.6.0.0..
Preprocessing library for criterion-1.6.0.0..
Building library for criterion-1.6.0.0..
cabal-3.6.2.0: Failed to build criterion-1.6.0.0. See the build log above for details.

[ 1 of 13] Compiling Criterion.Main.Options.Internal ( Criterion/Main/Options/Internal.hs, dist/build/Criterion/Main/Options/Internal.o )
[ 2 of 13] Compiling Paths_criterion  ( dist/build/autogen/Paths_criterion.hs, dist/build/Paths_criterion.o )
[ 3 of 13] Compiling Criterion.Types  ( Criterion/Types.hs, dist/build/Criterion/Types.o )
[ 4 of 13] Compiling Criterion.Monad.Internal ( Criterion/Monad/Internal.hs, dist/build/Criterion/Monad/Internal.o )

Criterion/Monad/Internal.hs:40:18:
    Non type-variable argument
      in the constraint: ?callStack::GHC.Stack.CallStack
    (Use FlexibleContexts to permit this)
    In an expression type signature:
      forall (e :: *)
             (a :: *). (call-stack-0.4.0:Data.CallStack.HasCallStack,
                        GHC.Exception.Exception e) =>
      e -> Criterion a
    In the expression:
        ghc-prim-0.4.0.0:GHC.Prim.coerce
          (Control.Monad.Catch.throwM :: e -> ReaderT Crit IO a) ::
          forall (e :: *)
                 (a :: *). (call-stack-0.4.0:Data.CallStack.HasCallStack,
                            GHC.Exception.Exception e) =>
          e -> Criterion a
    In an equation for ‘throwM’:
        throwM
          = ghc-prim-0.4.0.0:GHC.Prim.coerce
              (Control.Monad.Catch.throwM :: e -> ReaderT Crit IO a) ::
              forall (e :: *)
                     (a :: *). (call-stack-0.4.0:Data.CallStack.HasCallStack,
                                GHC.Exception.Exception e) =>
              e -> Criterion a
    When typechecking the code for  ‘Control.Monad.Catch.throwM’
      in a derived instance for ‘MonadThrow Criterion’:
      To see the code I am typechecking, use -ddump-deriv

Criterion/Monad/Internal.hs:40:30:
    Non type-variable argument
      in the constraint: ?callStack::GHC.Stack.CallStack
    (Use FlexibleContexts to permit this)
    In an expression type signature:
      forall (a :: *)
             (e :: *). (call-stack-0.4.0:Data.CallStack.HasCallStack,
                        GHC.Exception.Exception e) =>
      Criterion a -> (e -> Criterion a) -> Criterion a
    In the expression:
        ghc-prim-0.4.0.0:GHC.Prim.coerce
          (Control.Monad.Catch.catch ::
             ReaderT Crit IO a
             -> (e -> ReaderT Crit IO a) -> ReaderT Crit IO a) ::
          forall (a :: *)
                 (e :: *). (call-stack-0.4.0:Data.CallStack.HasCallStack,
                            GHC.Exception.Exception e) =>
          Criterion a -> (e -> Criterion a) -> Criterion a
    In an equation for ‘catch’:
        catch
          = ghc-prim-0.4.0.0:GHC.Prim.coerce
              (Control.Monad.Catch.catch ::
                 ReaderT Crit IO a
                 -> (e -> ReaderT Crit IO a) -> ReaderT Crit IO a) ::
              forall (a :: *)
                     (e :: *). (call-stack-0.4.0:Data.CallStack.HasCallStack,
                                GHC.Exception.Exception e) =>
              Criterion a -> (e -> Criterion a) -> Criterion a
    When typechecking the code for  ‘Control.Monad.Catch.catch’
      in a derived instance for ‘MonadCatch Criterion’:
      To see the code I am typechecking, use -ddump-deriv

Criterion/Monad/Internal.hs:40:42:
    Non type-variable argument
      in the constraint: ?callStack::GHC.Stack.CallStack
    (Use FlexibleContexts to permit this)
    In an expression type signature:
      forall (b :: *). call-stack-0.4.0:Data.CallStack.HasCallStack =>
      ((forall (a :: *). Criterion a -> Criterion a) -> Criterion b)
      -> Criterion b
    In the expression:
        ghc-prim-0.4.0.0:GHC.Prim.coerce
          (Control.Monad.Catch.mask ::
             ((forall (a :: *). ReaderT Crit IO a -> ReaderT Crit IO a)
              -> ReaderT Crit IO b)
             -> ReaderT Crit IO b) ::
          forall (b :: *). call-stack-0.4.0:Data.CallStack.HasCallStack =>
          ((forall (a :: *). Criterion a -> Criterion a) -> Criterion b)
          -> Criterion b
    In an equation for ‘mask’:
        mask
          = ghc-prim-0.4.0.0:GHC.Prim.coerce
              (Control.Monad.Catch.mask ::
                 ((forall (a :: *). ReaderT Crit IO a -> ReaderT Crit IO a)
                  -> ReaderT Crit IO b)
                 -> ReaderT Crit IO b) ::
              forall (b :: *). call-stack-0.4.0:Data.CallStack.HasCallStack =>
              ((forall (a :: *). Criterion a -> Criterion a) -> Criterion b)
              -> Criterion b
    When typechecking the code for  ‘Control.Monad.Catch.mask’
      in a derived instance for ‘MonadMask Criterion’:
      To see the code I am typechecking, use -ddump-deriv

This is most likely an unforeseen consequence of using the call-stack library on pre-8.0 GHCs, which encodes the HasCallStack constraint differently than GHC 8.0+ does. While we could just insist that pre-8.0 GHCs should enable FlexibleContexts, this does (unfortunately) mean that the use of call-stack would be a breaking API change, and it feels silly to induce a breaking API change that only affects old GHC versions. Moreover, I'm unclear if call-stack's encoding of HasCallStack is even that functional on pre-8.0 GHCs.

In light of this, I think the most prudent course of action is to simply not use HasCallStack constraints at all on pre-8.0 GHCs. That is, do something like:

#if __GLASGOW_HASKELL__ >= 800
import qualified GHC.Stack
# define HasCallStack GHC.Stack.HasCallStack
#else
# define HasCallStack ()
#endif

We would then use HasCallStack => ... constraints on all of the functions in exceptions. I'll work on making a new exceptions Hackage release with this change soon, and then we can deprecate exceptions-0.10.6.