{-# LANGUAGE DataKinds, FlexibleInstances, FlexibleContexts, UndecidableInstances, KindSignatures, TypeFamilies, CPP #-} #if !defined(TESTING) # if __GLASGOW_HASKELL__ >= 710 {-# LANGUAGE Safe #-} # else {-# LANGUAGE Trustworthy #-} #endif #endif -- | Unsatisfiable constraints for functions being removed. module Utils.Containers.Internal.TypeError where import GHC.TypeLits -- | The constraint @Whoops s@ is unsatisfiable for every 'Symbol' @s@. -- Under GHC 8.0 and above, trying to use a function with a @Whoops s@ -- constraint will lead to a pretty type error explaining how to fix -- the problem. Under earlier GHC versions, it will produce an extremely -- ugly type error within which the desired message is buried. -- -- ==== Example -- -- @ -- oldFunction :: Whoops "oldFunction is gone now. Use newFunction." -- => Int -> IntMap a -> IntMap a -- @ class Whoops (a :: Symbol) #if __GLASGOW_HASKELL__ >= 800 instance TypeError ('Text a) => Whoops a #endif -- Why don't we just use -- -- type Whoops a = TypeError ('Text a) ? -- -- When GHC sees the type signature of oldFunction, it will see that it -- has an unsatisfiable constraint and reject it out of hand. -- -- It seems possible to hack around that with a type family: -- -- type family Whoops a where -- Whoops a = TypeError ('Text a) -- -- but I don't really trust that to work reliably. What we actually -- do is pretty much guaranteed to work. Despite the fact that there -- is a totally polymorphic instance in scope, GHC will refrain from -- reducing the constraint because it knows someone could (theoretically) -- define an overlapping instance of Whoops. It doesn't commit to -- the polymorphic one until it has to, at the call site.