Skip to content

Inconsistent treatment of ambiguous instance signatures

This was originally spun out of #14266 (comment 142782), but this issue can be understood in isolation. Below are four programs:

  1. {-# LANGUAGE InstanceSigs #-}
    {-# LANGUAGE ScopedTypeVariables #-}
    
    class C a where
      m :: forall x. String
    instance C Bool where
      m :: forall x. String
      m = "Bool"
  2. {-# LANGUAGE AllowAmbiguousTypes #-}
    {-# LANGUAGE InstanceSigs #-}
    {-# LANGUAGE ScopedTypeVariables #-}
    
    class C a where
      m :: forall x. String
    instance C Bool where
      m :: forall x. String
      m = "Bool"
  3. {-# LANGUAGE InstanceSigs #-}
    {-# LANGUAGE ScopedTypeVariables #-}
    
    class C a where
      m :: forall x. Show x => String
    instance C Bool where
      m :: forall x. Show x => String
      m = "Bool"
  4. {-# LANGUAGE AllowAmbiguousTypes #-}
    {-# LANGUAGE InstanceSigs #-}
    {-# LANGUAGE ScopedTypeVariables #-}
    
    class C a where
      m :: forall x. Show x => String
    instance C Bool where
      m :: forall x. Show x => String
      m = "Bool"

Can you guess what GHC 8.8.3 will do when compiling ech of these programs?

  1. This will be rejected:

    $ /opt/ghc/8.8.3/bin/ghci Bug.hs
    GHCi, version 8.8.3: https://www.haskell.org/ghc/  :? for help
    Loaded GHCi configuration from /home/rgscott/.ghci
    [1 of 1] Compiling Main             ( Bug.hs, interpreted )
    
    Bug.hs:5:3: error:
        • Could not deduce (C a0)
          from the context: C a
            bound by the type signature for:
                       m :: forall a x. C a => String
            at Bug.hs:5:3-23
          The type variable ‘a0’ is ambiguous
        • In the ambiguity check for ‘m’
          To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
          When checking the class method: m :: forall a x. C a => String
          In the class declaration for ‘C’
      |
    5 |   m :: forall x. String
      |   ^^^^^^^^^^^^^^^^^^^^^

    This makes sense, as the type of m is genuinely ambiguous. The error message helpfully suggests enabling AllowAmbiguousTypes, and if we do so...

  2. ...then we end up with program (2), which typechecks. Hooray!

  3. As was the case with program (1), this program will be rejected:

    $ /opt/ghc/8.8.3/bin/ghci Bug.hs
    GHCi, version 8.8.3: https://www.haskell.org/ghc/  :? for help
    Loaded GHCi configuration from /home/rgscott/.ghci
    [1 of 1] Compiling Main             ( Bug.hs, interpreted )
    
    Bug.hs:5:3: error:
        • Could not deduce (C a0)
          from the context: (C a, Show x)
            bound by the type signature for:
                       m :: forall a x. (C a, Show x) => String
            at Bug.hs:5:3-33
          The type variable ‘a0’ is ambiguous
        • In the ambiguity check for ‘m’
          To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
          When checking the class method:
            m :: forall a x. (C a, Show x) => String
          In the class declaration for ‘C’
      |
    5 |   m :: forall x. Show x => String
      |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    Again, this is sensible, since m's type is also ambiguous. Experience has taught us that the solution is to enable AllowAmbiguousTypes, and if we do so...

  4. ...then we end up with program (4). However, this still doesn't typecheck!

    $ /opt/ghc/8.8.3/bin/ghci Bug.hs
    GHCi, version 8.8.3: https://www.haskell.org/ghc/  :? for help
    Loaded GHCi configuration from /home/rgscott/.ghci
    [1 of 1] Compiling Main             ( Bug.hs, interpreted )
    
    Bug.hs:8:8: error:
        • Could not deduce (Show x0)
          from the context: Show x
            bound by the type signature for:
                       m :: forall x. Show x => String
            at Bug.hs:8:8-33
          The type variable ‘x0’ is ambiguous
        • When checking that instance signature for ‘m’
            is more general than its signature in the class
            Instance sig: forall x. Show x => String
               Class sig: forall x. Show x => String
          In the instance declaration for ‘C Bool’
      |
    8 |   m :: forall x. Show x => String
      |        ^^^^^^^^^^^^^^^^^^^^^^^^^^

    How bizarre. Why did the trick of enabling AllowAmbiguousTypes work for program (2), but not for program (4)?

At first, I thought that perhaps there was something special about constraint arguments like Show x =>. But I don't think that this is the case, since if you remove the C Bool instance from program (4):

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE ScopedTypeVariables #-}

class C a where
  m :: forall x. Show x => String

Then it typechecks without issue. This leads me to believe that there is some inconsistency in the way that instance signatures are checked for ambiguity, especially when compared to the way other type signatures are checked for ambiguity. Some digging into the source code appears to support my hypothesis. Here is the code that checks instance signatures for ambiguity (in TcInstDecls.tcMethodBodyHelp):

                   ; hs_wrap <- addErrCtxtM (methSigCtxt sel_name sig_ty local_meth_ty) $
                                tcSubType_NC ctxt sig_ty local_meth_ty

And here is the code that checks other signatures for ambiguity (in TcValidity.checkAmbiguity):

       ; allow_ambiguous <- xoptM LangExt.AllowAmbiguousTypes
       ; (_wrap, wanted) <- addErrCtxt (mk_msg allow_ambiguous) $
                            captureConstraints $
                            tcSubType_NC ctxt ty ty
       ; simplifyAmbiguityCheck ty wanted

Notice how the latter code uses simplifyAmbiguityCheck, but the former does not. I believe that this is a crucial distinction, because simplifyAmbiguityCheck has special logic for discarding errors depending on whether or not AllowAmbiguousTypes is enabled:

simplifyAmbiguityCheck :: Type -> WantedConstraints -> TcM ()
simplifyAmbiguityCheck ty wanteds
  = do { ...

       -- Normally report all errors; but with -XAllowAmbiguousTypes
       -- report only insoluble ones, since they represent genuinely
       -- inaccessible code
       ; allow_ambiguous <- xoptM LangExt.AllowAmbiguousTypes
       ; traceTc "reportUnsolved(ambig) {" empty
       ; unless (allow_ambiguous && not (insolubleWC final_wc))
                (discardResult (reportUnsolved final_wc))
       ; ... }

This might explain why forall x. Show x => String is ambiguous only as an instance signature.

To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information