summaryrefslogtreecommitdiff
path: root/libraries
diff options
context:
space:
mode:
Diffstat (limited to 'libraries')
-rw-r--r--libraries/base/Data/Type/Ord.hs17
-rw-r--r--libraries/base/GHC/TypeError.hs141
-rw-r--r--libraries/base/GHC/TypeLits.hs50
-rw-r--r--libraries/base/base.cabal1
-rw-r--r--libraries/base/changelog.md7
5 files changed, 160 insertions, 56 deletions
diff --git a/libraries/base/Data/Type/Ord.hs b/libraries/base/Data/Type/Ord.hs
index 87c17c00cd..fa84754115 100644
--- a/libraries/base/Data/Type/Ord.hs
+++ b/libraries/base/Data/Type/Ord.hs
@@ -3,7 +3,7 @@
{-# LANGUAGE GADTs #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE PolyKinds #-}
-{-# LANGUAGE Safe #-}
+{-# LANGUAGE Trustworthy #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE StandaloneKindSignatures #-}
{-# LANGUAGE TypeFamilies #-}
@@ -35,6 +35,7 @@ module Data.Type.Ord (
) where
import GHC.Show(Show(..))
+import GHC.TypeError
import GHC.TypeLits.Internal
import GHC.TypeNats.Internal
import Data.Bool
@@ -64,29 +65,31 @@ data OrderingI a b where
deriving instance Show (OrderingI a b)
deriving instance Eq (OrderingI a b)
-
infix 4 <=?, <=, >=?, >=, <?, <, >?, >
-- | Comparison (<=) of comparable types, as a constraint.
--
-- @since 4.16.0.0
-type x <= y = (x <=? y) ~ 'True
+type x <= y = Assert (x <=? y) (LeErrMsg x y)
+type LeErrMsg x y = TypeError ('Text "Cannot satisfy: " ':<>: 'ShowType x ':<>: 'Text " <= " ':<>: 'ShowType y)
-- | Comparison (>=) of comparable types, as a constraint.
--
-- @since 4.16.0.0
-type x >= y = (x >=? y) ~ 'True
+type x >= y = Assert (x >=? y) (GeErrMsg x y)
+type GeErrMsg x y = TypeError ('Text "Cannot satisfy: " ':<>: 'ShowType x ':<>: 'Text " >= " ':<>: 'ShowType y)
-- | Comparison (<) of comparable types, as a constraint.
--
-- @since 4.16.0.0
-type x < y = (x >? y) ~ 'True
+type x < y = Assert (x <? y) (LtErrMsg x y)
+type LtErrMsg x y = TypeError ('Text "Cannot satisfy: " ':<>: 'ShowType x ':<>: 'Text " < " ':<>: 'ShowType y)
-- | Comparison (>) of comparable types, as a constraint.
--
-- @since 4.16.0.0
-type x > y = (x >? y) ~ 'True
-
+type x > y = Assert (x >? y) (GtErrMsg x y)
+type GtErrMsg x y = TypeError ('Text "Cannot satisfy: " ':<>: 'ShowType x ':<>: 'Text " > " ':<>: 'ShowType y)
-- | Comparison (<=) of comparable types, as a function.
--
diff --git a/libraries/base/GHC/TypeError.hs b/libraries/base/GHC/TypeError.hs
new file mode 100644
index 0000000000..a74f573ae2
--- /dev/null
+++ b/libraries/base/GHC/TypeError.hs
@@ -0,0 +1,141 @@
+{-# LANGUAGE Trustworthy #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE ExistentialQuantification #-}
+{-# LANGUAGE PolyKinds #-}
+{-# LANGUAGE StandaloneKindSignatures #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# OPTIONS_HADDOCK not-home #-}
+
+{-|
+This module exports the TypeError family, which is used to provide custom type
+errors, and the ErrorMessage kind used to define these custom error messages.
+This is a type-level analogue to the term level error function.
+
+@since 4.16.0.0
+-}
+
+module GHC.TypeError
+ ( ErrorMessage (..)
+ , TypeError
+ , Assert
+ ) where
+
+import Data.Bool
+import GHC.Num.Integer () -- See Note [Depend on GHC.Num.Integer] in GHC.Base
+import GHC.Types (Constraint, Symbol)
+
+{-
+Note [Custom type errors]
+~~~~~~~~~~~~~~~~~~~~~~~~~
+TypeError is used to provide custom type errors, similar to the term-level
+error function. TypeError is somewhat magical: when the constraint solver
+encounters a constraint where the RHS is TypeError, it reports the error to
+GHC. Later, GHC renders this error for display to the user (see the function
+GHC.Tc.Errors.mkUserTypeErrorReporter).
+
+See also the wiki page on custom type errors:
+https://gitlab.haskell.org/ghc/ghc/-/wikis/proposal/custom-type-errors
+-}
+
+-- | A description of a custom type error.
+data {-kind-} ErrorMessage = Text Symbol
+ -- ^ Show the text as is.
+
+ | forall t. ShowType t
+ -- ^ Pretty print the type.
+ -- @ShowType :: k -> ErrorMessage@
+
+ | ErrorMessage :<>: ErrorMessage
+ -- ^ Put two pieces of error message next
+ -- to each other.
+
+ | ErrorMessage :$$: ErrorMessage
+ -- ^ Stack two pieces of error message on top
+ -- of each other.
+
+infixl 5 :$$:
+infixl 6 :<>:
+
+-- | The type-level equivalent of 'Prelude.error'.
+--
+-- The polymorphic kind of this type allows it to be used in several settings.
+-- For instance, it can be used as a constraint, e.g. to provide a better error
+-- message for a non-existent instance,
+--
+-- @
+-- -- in a context
+-- instance TypeError (Text "Cannot 'Show' functions." :$$:
+-- Text "Perhaps there is a missing argument?")
+-- => Show (a -> b) where
+-- showsPrec = error "unreachable"
+-- @
+--
+-- It can also be placed on the right-hand side of a type-level function
+-- to provide an error for an invalid case,
+--
+-- @
+-- type family ByteSize x where
+-- ByteSize Word16 = 2
+-- ByteSize Word8 = 1
+-- ByteSize a = TypeError (Text "The type " :<>: ShowType a :<>:
+-- Text " is not exportable.")
+-- @
+--
+-- @since 4.9.0.0
+type family TypeError (a :: ErrorMessage) :: b where
+
+{-
+Note [Getting good error messages from boolean comparisons]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+We want to write types like
+
+ f :: forall (x :: Int) (y :: Int). (x <= y) => T x -> T y
+
+so we need (<=) :: Int -> Int -> Constraint. We already have
+(<=?) :: Int -> Int -> Bool, defined in Data.Type.Ord. One obvious way to get
+(<=) is to say
+
+ type (<=) x y = (x <=? y) ~ True
+
+But suppose we call (f @4 @2); then we get the constraint (4 <=? 2) ~ True
+which simplifies to False ~ True, which gives a very poor error message.
+
+So we adopt a different idiom:
+
+ type (<=) x y = Assert (x <=? y) (LeErrMsg x y)
+
+The Assert function is defined so that
+
+ Assert True msg ===> ()
+
+so as soon as (x <=? y) evaluates to True, the Assert disappears. But as soon
+as (x <=? y) is apart from True (i.e. cannot evaluate to True) the second
+equation of Assert kicks in, and
+
+ Assert non-true msg ==> msg
+-}
+
+-- | A type-level assert function.
+--
+-- If the first argument evaluates to true, then the empty constraint is
+-- returned, otherwise the second argument (which is intended to be something
+-- which reduces to 'TypeError' is used).
+--
+-- For example, given some type level predicate @P' :: Type -> Bool@, it is
+-- possible to write the type synonym
+--
+-- @
+-- type P a = Assert (P' a) (NotPError a)
+-- @
+--
+-- where @NotPError@ reduces to a @TypeError@ which is reported if the
+-- assertion fails.
+--
+-- @since 4.16.0.0
+--
+type Assert :: Bool -> Constraint -> Constraint
+type family Assert check errMsg where
+ Assert 'True _ = ()
+ Assert _ errMsg = errMsg
+ -- See Note [Getting good error messages from boolean comparisons]
diff --git a/libraries/base/GHC/TypeLits.hs b/libraries/base/GHC/TypeLits.hs
index 79759f2b87..97b922c79e 100644
--- a/libraries/base/GHC/TypeLits.hs
+++ b/libraries/base/GHC/TypeLits.hs
@@ -10,7 +10,6 @@
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE MagicHash #-}
-{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE TypeApplications #-}
{-|
@@ -61,6 +60,7 @@ module GHC.TypeLits
import GHC.Base(Eq(..), Ord(..), Ordering(..), String, otherwise, withDict)
import GHC.Types(Symbol, Char)
+import GHC.TypeError(ErrorMessage(..), TypeError)
import GHC.Num(Integer, fromInteger)
import GHC.Show(Show(..))
import GHC.Read(Read(..))
@@ -181,54 +181,6 @@ instance Read SomeChar where
-- @since 4.10.0.0
type family AppendSymbol (m ::Symbol) (n :: Symbol) :: Symbol
--- | A description of a custom type error.
-data {-kind-} ErrorMessage = Text Symbol
- -- ^ Show the text as is.
-
- | forall t. ShowType t
- -- ^ Pretty print the type.
- -- @ShowType :: k -> ErrorMessage@
-
- | ErrorMessage :<>: ErrorMessage
- -- ^ Put two pieces of error message next
- -- to each other.
-
- | ErrorMessage :$$: ErrorMessage
- -- ^ Stack two pieces of error message on top
- -- of each other.
-
-infixl 5 :$$:
-infixl 6 :<>:
-
--- | The type-level equivalent of 'Prelude.error'.
---
--- The polymorphic kind of this type allows it to be used in several settings.
--- For instance, it can be used as a constraint, e.g. to provide a better error
--- message for a non-existent instance,
---
--- @
--- -- in a context
--- instance TypeError (Text "Cannot 'Show' functions." :$$:
--- Text "Perhaps there is a missing argument?")
--- => Show (a -> b) where
--- showsPrec = error "unreachable"
--- @
---
--- It can also be placed on the right-hand side of a type-level function
--- to provide an error for an invalid case,
---
--- @
--- type family ByteSize x where
--- ByteSize Word16 = 2
--- ByteSize Word8 = 1
--- ByteSize a = TypeError (Text "The type " :<>: ShowType a :<>:
--- Text " is not exportable.")
--- @
---
--- @since 4.9.0.0
-type family TypeError (a :: ErrorMessage) :: b where
-
-
-- Char-related type families
-- | Extending a type-level symbol with a type-level character
diff --git a/libraries/base/base.cabal b/libraries/base/base.cabal
index 7e09412839..9b9d5ba2d2 100644
--- a/libraries/base/base.cabal
+++ b/libraries/base/base.cabal
@@ -275,6 +275,7 @@ Library
GHC.Stats
GHC.Storable
GHC.TopHandler
+ GHC.TypeError
GHC.TypeLits
GHC.TypeLits.Internal
GHC.TypeNats
diff --git a/libraries/base/changelog.md b/libraries/base/changelog.md
index 525cdbab72..a38c768f64 100644
--- a/libraries/base/changelog.md
+++ b/libraries/base/changelog.md
@@ -71,6 +71,13 @@
* Functions in `Data.List` are specialized to list.
`Data.OldList` and `GHC.List` modules are removed.
+ * Add `GHC.TypeError` module to contain functionality related to custom type
+ errors. `TypeError` is re-exported from `GHC.TypeLits` for backwards
+ compatibility.
+
+ * Comparison constraints in `Data.Type.Ord` (e.g. `<=`) now use the new
+ `GHC.TypeError.Assert` type family instead of type equality with `~`.
+
## 4.15.0.0 *TBA*
* `openFile` now calls the `open` system call with an `interruptible` FFI