The hint to add missing context to the type signature is not produced by GHC when using non-trivial constraint
Intro
When a user didn't specify typeclass constraints for all variables in a function signature the GHC sometimes suggests to add relevant constraint to the signature, like this:
• No instance for (Foo a) arising from a use of ‘foo’
Possible fix:
add (Foo a) to the context of
the type signature for:
bar :: forall a. a -> Sum a
However if the constraint is a "non-trivial" one, in particular if it uses multiparameter typeclass and some of the parameters not variables then the suggestion is not produced. My expectation is that suggestion should be produced.
A bit of context, the Haskell Language Server uses GHC's error messages to suggest fixes. When error message contains the Possible fix
similar to the one shows before then in can add the constraint to the type signature automatically as a code action. But if the suggestion is missing then it's just not possible. It appears that only GHC has all the necessary info regarding whether to produce Possible fix
or not so the issue cannot be worked around in HLS (nor should it be since GHC's error messages are useful even for users that don't use HLS).
Specific cases
For the following program the suggestion is not produced:
{-# LANGUAGE MultiParamTypeClasses #-}
import Data.Kind
import Data.Monoid
class Foo (a :: Type) (b :: Type) where
foo :: Monoid m => (a -> m) -> b -> m
bar :: a -> Sum Int
bar x = foo Sum x
/tmp/test2.hs:10:9: error:
• No instance for (Foo Int a) arising from a use of ‘foo’
• In the expression: foo Sum x
In an equation for ‘bar’: bar x = foo Sum x
|
10 | bar x = foo Sum x
| ^^^
But for a slightly simpler program the suggestion is present
{-# LANGUAGE MultiParamTypeClasses #-}
import Data.Kind
import Data.Monoid
class Foo (a :: Type) where
foo :: Monoid m => (a -> m) -> a -> m
bar :: a -> Sum a
bar x = foo Sum x
/tmp/test2.hs:10:9: error:
• No instance for (Foo a) arising from a use of ‘foo’
Possible fix:
add (Foo a) to the context of
the type signature for:
bar :: forall a. a -> Sum a
• In the expression: foo Sum x
In an equation for ‘bar’: bar x = foo Sum x
|
10 | bar x = foo Sum x
| ^^^
A bit of investigation
I found out that call to isTyVarClassPred
at https://gitlab.haskell.org/ghc/ghc/-/blob/master/compiler/GHC/Tc/Errors/Ppr.hs#L2844 is responsible for GHC's decision to omit the suggestion. The call appears to check that typeclass is only applied to variables and thus fails for a constraint like Foo Int a
which has constants mixed in.
Perhaps the check should be relaxed to require that constraint to be suggested has some variables in its parameters? Or maybe the check shouldn't be performed at all (not really sure about this one though)?
One thing to probably watch out for is that the variable can occur as part of some other type in the constraint as illustrated by following modification to the program
{-# LANGUAGE MultiParamTypeClasses #-}
import Data.Kind
import Data.Monoid
class Foo (a :: Type) (b :: Type) where
foo :: Monoid m => (a -> m) -> b -> m
newtype Quux a = Quux { unQuux :: a }
bar :: a -> Sum Int
bar x = foo Sum (Quux x)
/tmp/test2.hs:12:9: error:
• No instance for (Foo Int (Quux a)) arising from a use of ‘foo’
• In the expression: foo Sum (Quux x)
In an equation for ‘bar’: bar x = foo Sum (Quux x)
|
12 | bar x = foo Sum (Quux x)
| ^^^
NB if I just omit the call to isTyVarClassPred
the patched ghc produces the error message I was expecting all along:
• No instance for (Foo Int (Quux a)) arising from a use of ‘foo’
Possible fix:
add (Foo Int (Quux a)) to the context of
the type signature for:
bar :: forall a. a -> Sum Int
• In the expression: foo Sum (Quux x)
In an equation for ‘bar’: bar x = foo Sum (Quux x)
|
12 | bar x = foo Sum (Quux x)
| ^^^
Environment
- GHC version used: 9.2.2, HEAD
Optional:
- Operating System: Linux
- System Architecture: x86_64