GHC 8.8.x Migration Guide

This guide summarises the changes you may need to make to your code to migrate from GHC 8.6 to GHC 8.8. This guide complements the GHC 8.8.x release notes which should be consulted as well.

Compiler changes

Pattern-match coverage checking changes for strict fields

Consider the following function and data type:

data Foo = MkFoo1 Int | MkFoo2 !Void

f :: Foo -> Int
f (MkFoo1 i) = i

f is actually an exhaustive function. Because MkFoo2 has a strict field of type Void (which has no terminating values which inhabit it), it's impossible to construct anything with MkFoo2 without triggering an infinite loop or throwing an exception. Therefore, one cannot reach the MkFoo2 case of f.

However, previous versions of GHC did not recognize this fact and flagged f as being non-exhaustive. As a workaround, one had to explicitly match on MkFoo2 in f:

f :: Foo -> Int
f (MkFoo1 i) = i
f (MkFoo2 v) = absurd v

However, now that GHC is smart enough to recognize that MkFoo2 is unreachable, it will actually throw a warning (with -Woverlapping-patterns enabled) on the above code:

    Pattern match is redundant
    In an equation for ‘f’: f (MkFoo2 v) = ...

As a result, some code which compiled without warnings on previous versions of GHC may emit warnings on GHC 8.8.

Kind generalization changes for local definitions

Starting in GHC 8.8, we now generalize the kinds in the types of local definitions (e.g., let- or where-bound functions). As a result, there are a handful of programs which will no longer compile. Here is one such example:

type family LetGo :: k

foo :: Proxy (LetGo :: Type)
foo = undefined

sSconcat :: forall (x :: Type). x
sSconcat = undefined
   where sGo :: x -> Proxy LetGo
         sGo _ = foo

This kind-checks on previous versions of GHC, since the return kind of LetGo (in sGo) is not generalized, so we have sGo :: x -> Proxy (LetGo :: Type) (which is necessary for the body of sGo to typecheck). However, this will not kind-check on GHC 8.8, since the return kind of LetGo is generalized, giving us sGo :: x -> Proxy (LetGo :: k) (which is too polymorphic for foo). Therefore, this code fails on GHC 8.8 with a Couldn't match type ‘k’ with ‘*’ error.

To avoid this, one can use an explicit kind signature on LetGo, like so:

  sGo :: x -> Proxy (LetGo :: Type)
  sGo _ = foo

Template Haskell reification change for classes

If you have a type class like this:

class C a where
  method :: a

Then in previous versions of GHC, reifying C would give you something like this:

class C a where
  method :: forall a. C a => a

Notice that that forall a. C a => in the type signature for method is completely redundant, since that's implied by the fact that it's a class method of C. This oversight has been fixed, so reifying C will now give you simply a as the type signature for method.

There may be code in the wild that previously depended on the assumption that reifying a class would put these redundant tyvars/class contexts at the front of each method's type signature, so it's possible that there may be breakage because of this.

Changes to unused import warnings

GHC 8.8 tightened up the implementation of its unused import warning algorithm to more closely match the specification in wiki:Commentary/Compiler/UnusedImports, as GHC 8.0 inadvertently introduced a regression that caused it to stray from this specification. As a result, some programs which compile without warnings on GHC 8.0 through 8.6 will now produce warnings on GHC 8.8. A good example that illustrates this is this one:

{-# OPTIONS_GHC -Wunused-imports #-}
module T13064 where

import Control.Applicative
import Prelude (IO, pure)   -- Import of 'pure' is redundant

foo :: IO ()
foo = () <$ pure ()

This produces no warnings on 8.0–8.6, but does warn on GHC 8.8:

T13064.hs:5:1: warning: [-Wunused-imports (in -Wextra)]
    The import of ‘pure’ from module ‘Prelude’ is redundant

To summarize the reasoning from wiki:Commentary/Compiler/UnusedImports, if an identifier f is imported from two different locations, once from import A and once from import B (f), then GHC will mark the explicit import from B as redundant, since catch-all imports like import A "dominate" explicit imports like import B (f).

If one wishes to keep this sort of compiling without any warnings on both GHC 8.8 and previous versions, there are two options. One is to make both imports catch-all:

{-# OPTIONS_GHC -Wunused-imports #-}
module T13064 where

import Control.Applicative
import Prelude

foo :: IO ()
foo = () <$ pure ()

Alternatively, one can use pure qualified to avoid GHC warning about it:

{-# OPTIONS_GHC -Wunused-imports #-}
module T13064 where

import Control.Applicative
import Prelude (IO, pure)

foo :: IO ()
foo = () <$ Prelude.pure ()

Additions to the -Wcompat warning group

As part of this GHC proposal, the uses of * that rely on the StarIsType extension now produce a warning:

Prelude> type M = (Maybe :: * -> *)

<interactive>:1:20: warning: [-Wstar-is-type (in -Wcompat)]
    Using ‘*’ (or its Unicode variant) to mean ‘Data.Kind.Type’
    relies on the StarIsType extension, which will become
    deprecated in the future.
    Suggested fix: use ‘Type’ from ‘Data.Kind’ instead.

The recommended migration strategy is to follow the suggestion in the warning: import Type from the Data.Kind module and use it in place of *:

Prelude Data.Kind> type M = (Maybe :: Type -> Type)

More literal out-of-range warnings

GHC can now detect more uses of literal values that do not lie within the expected range dictated by their type. As a result, some code that GHC did not warn about in previous versions will now produce warnings with 8.8. Some examples of code that will now produce warnings are:

  • Out-of-range unboxed literals, such as 100000000000000000000000000# :: Int#.
  • Out-of-range literal patterns, such as (\x -> case (x :: Int) of 100000000000000000000000000000000 -> 0) 8 :: Int.
  • Negative Natural literals (from Numeric.Natural), such as (-123) :: Natural.
  • Empty Natural enumerations, such as [10..3] :: [Natural].

Stricter type synonym validity-checking

Previous versions of GHC would permit code that should require certain language extensions to be enabled even when those extensions weren't actually on. Here are two notable examples:

  1. Previously, GHC would accept some uses of type synonyms which did not have all of their arguments supplied without requiring the LiberalTypeSynonyms extension. Starting with version 8.8, GHC is stricter about requiring the extension in these cases.

For example, this code requires LiberalTypeSynonyms in GHC 8.8:

type Generic i o = forall x. i x -> o x
type Id x = x

foo :: Generic Id Id
foo = undefined

If GHC is complaining about type synonyms not being passed enough arguments or an 'illegal polymorphic type', try enabling LiberalTypeSynonyms in that module.

  1. Previously, it was possible to sneak in impredicative types through clever use of type synonyms. For instance, this code used to compile even without the ImpredicativeTypes extension enabled!
{-# LANGUAGE RankNTypes #-}

type Foo x = forall a. a -> a
type Bar x = Int -> Foo x

f :: [Bar ()] -- Impredicative!
f = []

GHC will now properly reject this:

    • Illegal polymorphic type: forall a. a -> a
      GHC doesn't yet support impredicative polymorphism
    • In the expansion of type synonym ‘Foo’
      In the expansion of type synonym ‘Bar’
      In the type signature: f :: [Bar ()]

Kind variable visibility in poly-kinded class methods

Previous versions of GHC were somewhat ambiguous about whether poly-kinded type class methods would make their kind variables be available for visible type application or not. This is best explained by examples. Consider the following two classes:

class C1 (a :: k) b where
  p1 :: Proxy a -> Proxy b

class C2 (a :: k) (b :: Type) where
  p2 :: Proxy a -> Proxy b

On previous versions of GHC, the kind of a is not available for visible type application in p1, but it is available for visible type application in p2:

λ> :type +v p1
  :: forall {k1} {k2} (a :: k1) (b :: k2).
     C1 a b =>
     Proxy a -> Proxy b
λ> :type +v p2
p2 :: forall k (a :: k) b. C2 a b => Proxy a -> Proxy b

GHC 8.8 has cleaned up this infelicity, so now both p1 and p2 treat the kind of a as being available for visible type application:

λ> :type +v p1
  :: forall {k1} k2 (a :: k2) (b :: k1). C1 a b => Proxy a -> Proxy b
λ> :type +v p2
p2 :: forall k (a :: k) b. C2 a b => Proxy a -> Proxy b

There is a possibility that existing code will break due to this change. For example, the following code typechecks on old GHCs due to the aforementioned quirk of how GHC infers the kind of a:

class C (a :: k) b where
  p :: Proxy a -> Proxy b

p' :: forall k (a :: k) b. C a b => Proxy a -> Proxy b
p' = p @a

This will no longer typecheck on GHC 8.8. Two possible ways of fixing this are:

  1. Change class C (a :: k) b to class C a b (this is backwards-compatible with old GHCs).
  2. Change p' = p @a to p' = p @k @a (this is not backwards-compatible with old GHCs).

Library changes


The Template Haskell AST has changed to accommodate the Visible kind application and More explicit foralls proposals, both of which debut in GHC 8.8. At a glance, the breaking API changes are as follows:

 data Dec
  = ...
- | DataInstD Cxt                     Name [Type] (Maybe Kind) [Con] [DerivClause]
+ | DataInstD Cxt (Maybe [TyVarBndr]) Type        (Maybe Kind) [Con] [DerivClause]
- | NewtypeInstD Cxt                     Name [Type] (Maybe Kind) Con [DerivClause]
+ | NewtypeInstD Cxt (Maybe [TyVarBndr]) Type        (Maybe Kind) Con [DerivClause]
- | TySynInstD Name TySynEqn
+ | TySynInstD TySynEqn

 data TySynEqn =
-   TySynEqn                     [Type] Type
+   TySynEqn (Maybe [TyVarBndr]) Type   Type

 data Pragma
  = ...
- | RuleP                     String [RuleBndr] Exp Exp Phases
+ | RuleP (Maybe [TyVarBndr]) String [RuleBndr] Exp Exp Phases

 tySynInstD ::
-    Name -> TySynEqnQ -> DecQ
+            TySynEqnQ -> DecQ

 tySynEqn ::
-                           [TypeQ] -> TypeQ -> TySynEqnQ
+   (Maybe [TyVarBndrQ]) -> TypeQ   -> TypeQ -> TySynEqnQ

Here are the reasons why each of these had to change, and how one can accommodate these changes:

  • DataInstD, NewtypeInstD, and TySynEqn each gained a field of type type Maybe [TyVarBndr] to support the More explicit foralls proposal, which allows writing type family instances and RULES with explicit foralls (binding type variables) at the front. (If you don't care about this feature, it's perfectly fine to use Nothing here, since that omits the forall entirely.)
  • Because of the Visible kind application proposal, type family instances can feature richer arguments than before. For example, this is an example of a type family one can write using visible kind applications:
type family Foo (a :: k) :: k where
  Foo @Bool a = a
  Foo @Char a = a

As a result, the AST forms for type family instances needed to be updated to support these new forms of arguments. It was decided to migrate away from the old style using [Type] to store each individual type argument, since this doesn't encode enough information to tell whether an argument is applied using visible kind application or not. Instead, a single Type is used. For instance, this is how the Foo @Bool a = a equation is encoded:

TySynEqn Nothing (ConT ''Foo `AppKindT` ConT ''Bool `AppT` VarT a) (VarT a)

Note that the name of the type family, Foo, is now present on the left-hand side! This was not the case before, as previously only the arguments were included in a TySynEqn. This means that the Name field in TySynInstD is now wholly redundant (as that same Name is present in the TynSynEqn), so the Name field was removed from TySynInstD.

Similar changes were made to DataInstD and NewtypeInstD. Their Name and [Type] fields (representing the data family name and left-hand-side arguments, respectively) were removed in favor of a single Type representing the data family name applied to its arguments.

One consequence of this change is that retrieving the Name from a type or data family instance is no longer as straightforward as it used to be, as the Name is now inside of the left-hand side Type field. Similarly, the type arguments of the instance are also sprinkled throughout the LHS Type field. One may find the following utility function useful to retrieve the Name and arguments:


-- | An argument to a type, either a normal type ('TANormal') or a visible
-- kind application ('TyArg').
-- 'TypeArg' is useful when decomposing an application of a 'Type' to its
-- arguments (e.g., in 'unfoldType').
data TypeArg
  = TANormal Type -- Normal arguments
  | TyArg    Kind -- Visible kind applications

-- | Decompose an applied type into its individual components. For example, this:
-- @
-- Proxy \@Type Char
-- @
-- would be unfolded to this:
-- @
-- ('ConT' ''Proxy, ['TyArg' ('ConT' ''Type), 'TANormal' ('ConT' ''Char)])
-- @
unfoldType :: Type -> (Type, [TypeArg])
unfoldType = go []
    go :: [TypeArg] -> Type -> (Type, [TypeArg])
    go acc (ForallT _ _ ty) = go acc ty
    go acc (AppT ty1 ty2)   = go (TANormal ty2:acc) ty1
    go acc (SigT ty _)      = go acc ty
#if MIN_VERSION_template_haskell(2,11,0)
    go acc (ParensT ty)     = go acc ty
#if MIN_VERSION_template_haskell(2,15,0)
    go acc (AppKindT ty ki) = go (TyArg ki:acc) ty
    go acc ty               = (ty, acc)

famInstLHSName :: Type -> Maybe Name
famInstLHSName t =
  case unfoldType t of
    (ConT n, _) -> Just n
    (_,      _) -> Nothing
  • Because of the bullet point above, it was no longer possible to keep the old type signature of tySynEqn since it lacks a Type containing the Name of the type family. For this reason, it was decided to just make a breaking change to the type of tySynEqn (and similarly for tySynInstD, which is almost always used in conjunction with tySynEqn).

Note that neither of the type signatures for dataInstD nor newtypeInstD had to change since the data family Name was already an argument, so these functions happen to have enough information to stay backwards compatible even in the face of these AST changes.

Last modified 7 months ago Last modified on Mar 8, 2019 2:05:31 PM