Skip to main content

HMock Compatibility (Part 4)

After resolving the MonadUnliftIO issue in HMock, I have been using it with GHC 8.6 through 9.2 in various projects. Mocking the higher-rank withFile function, as described in HMock and Impredicative Polymorphism (and Part 2), causes issues with GHC 8.6, however.

I am currently experimenting with mocking withFile in the Redact project. In this project, I would like to represent “normal errors” in the types. By “normal errors,” I mean errors that are expected in the normal usage of the program. For example, withFile may fail if the file cannot be read. This may happen due to one of many different causes, including the file not existing as well as all kinds of permissions issues. In this case, I use tryIOError to catch IOError exceptions and return them.

class Monad m => MonadHandle m where
  ...
  withFile
    :: Typeable r
    => FilePath
    -> IOMode
    -> (Handle -> m r)
    -> m (Either IOError r)

instance MonadHandle IO where
  ...
  withFile path mode = tryIOError . IO.withFile path mode

Using GHC 8.6.5, the code created by makeMockable causes the build to fail with the following errors.

/test/Redact/Terminal/Mock.hs:47:1: error:
    • Could not deduce: t0
                        ~ (GHC.IO.Handle.Types.Handle
                           -> Test.HMock.Internal.State.MockT m r)
      from the context: (name ~ "withFile", a ~ Either IOError r,
                         base-4.12.0.0:Data.Typeable.Internal.Typeable r)
        bound by a pattern with constructor:
                   WithFile :: forall r (b :: * -> *).
                               base-4.12.0.0:Data.Typeable.Internal.Typeable r =>
                               FilePath
                               -> IOMode
                               -> (GHC.IO.Handle.Types.Handle
                                   -> Test.HMock.Internal.State.MockT b r)
                               -> Test.HMock.Mockable.Action
                                    MonadHandle "withFile" b (Either IOError r),
                 in an equation for ‘showMatcher’
        at test/Redact/Terminal/Mock.hs:47:1-29
    • When checking that the pattern signature: t0
        fits the type of its context:
          GHC.IO.Handle.Types.Handle -> Test.HMock.Internal.State.MockT m r
      In the pattern: _ :: t_acTn
      In the pattern: WithFile _ _ (_ :: t_acTn)
    • Relevant bindings include
        showMatcher :: Maybe
                         (Test.HMock.Mockable.Action MonadHandle name m a)
                       -> Matcher MonadHandle name m b -> String
          (bound at test/Redact/Terminal/Mock.hs:47:1)
   |
47 | makeMockable [t|MonadHandle|]
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

/test/Redact/Terminal/Mock.hs:47:1: error:
    • Could not deduce: t0
                        ~ (GHC.IO.Handle.Types.Handle
                           -> Test.HMock.Internal.State.MockT m r0)
      from the context: (name ~ "withFile", a ~ Either IOError r,
                         base-4.12.0.0:Data.Typeable.Internal.Typeable r)
        bound by a pattern with constructor:
                   WithFile :: forall r (b :: * -> *).
                               base-4.12.0.0:Data.Typeable.Internal.Typeable r =>
                               FilePath
                               -> IOMode
                               -> (GHC.IO.Handle.Types.Handle
                                   -> Test.HMock.Internal.State.MockT b r)
                               -> Test.HMock.Mockable.Action
                                    MonadHandle "withFile" b (Either IOError r),
                 in an equation for ‘showMatcher’
        at test/Redact/Terminal/Mock.hs:47:1-29
      or from: (name ~ "withFile", b ~ Either IOError r1)
        bound by a pattern with constructor:
                   WithFile_ :: forall (b :: * -> *) r.
                                Test.Predicates.Predicate FilePath
                                -> Test.Predicates.Predicate IOMode
                                -> (forall r1.
                                    base-4.12.0.0:Data.Typeable.Internal.Typeable r1 =>
                                    Test.Predicates.Predicate
                                      (GHC.IO.Handle.Types.Handle
                                       -> Test.HMock.Internal.State.MockT b r1))
                                -> Matcher MonadHandle "withFile" b (Either IOError r),
                 in an equation for ‘showMatcher’
        at test/Redact/Terminal/Mock.hs:47:1-29
      Expected type: Test.Predicates.Predicate t0
        Actual type: Test.Predicates.Predicate
                       (GHC.IO.Handle.Types.Handle
                        -> Test.HMock.Internal.State.MockT m r0)
    • In the first argument of ‘show’, namely
        ‘(p_acTq :: Test.Predicates.Predicate t_acTn)’
      In the first argument of ‘(++)’, namely
        ‘show (p_acTq :: Test.Predicates.Predicate t_acTn)’
      In the second argument of ‘(++)’, namely
        ‘(show (p_acTq :: Test.Predicates.Predicate t_acTn) ++ "»")’
    • Relevant bindings include
        p_acTq :: forall r.
                  base-4.12.0.0:Data.Typeable.Internal.Typeable r =>
                  Test.Predicates.Predicate
                    (GHC.IO.Handle.Types.Handle -> Test.HMock.Internal.State.MockT m r)
          (bound at test/Redact/Terminal/Mock.hs:47:1)
        showMatcher :: Maybe
                         (Test.HMock.Mockable.Action MonadHandle name m a)
                       -> Matcher MonadHandle name m b -> String
          (bound at test/Redact/Terminal/Mock.hs:47:1)
   |
47 | makeMockable [t|MonadHandle|]
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

I do not know how to fix these errors, and I do not have time to dig into it at this time. For now, I am going to simply only enable the mock tests for GHC 8.8 and later, as described in HMock Compatibility (Part 3).