morley-1.13.0: src/Util/TypeLits.hs
-- SPDX-FileCopyrightText: 2020 Tocqueville Group
--
-- SPDX-License-Identifier: LicenseRef-MIT-TQ
{-# OPTIONS_GHC -Wno-redundant-constraints #-}
-- | Re-exports 'GHC.TypeLits', modifying it considering our practices.
module Util.TypeLits
( Symbol
, KnownSymbol
, AppendSymbol
, symbolVal
, symbolValT
, symbolValT'
, TypeError
, ErrorMessage (..)
, TypeErrorUnless
) where
import GHC.TypeLits
symbolValT :: forall s. KnownSymbol s => Proxy s -> Text
symbolValT = toText . symbolVal
symbolValT' :: forall s. KnownSymbol s => Text
symbolValT' = symbolValT (Proxy @s)
-- | Conditional type error.
--
-- There is a very subtle difference between 'TypeErrorUnless' and the following type family:
--
-- > type family TypeErrorUnlessAlternative (cond :: Bool) (err :: ErrorMessage) :: Constraint where
-- > TypeErrorUnlessAlternative cond err =
-- > ( If cond
-- > (() :: Constraint)
-- > (TypeError err)
-- > , cond ~ 'True
-- > )
--
-- If @cond@ cannot be fully reduced (e.g. it's a stuck type family), then:
--
-- * @TypeErrorUnless@ will state that the constraint cannot be deduced.
-- * @TypeErrorUnlessAlternative@ will fail with the given error message @err@.
--
-- For example:
--
-- > -- Partial function
-- > type family IsZero (n :: Peano) :: Bool where
-- > IsZero ('S _) = 'False
-- >
-- > f1 :: TypeErrorUnless (IsZero n) ('Text "Expected zero") => ()
-- > f1 = ()
-- >
-- > f2 :: TypeErrorUnlessAlternative (IsZero n) ('Text "Expected zero") => ()
-- > f2 = ()
-- >
-- >
-- > f1res = f1 @'Z
-- > -- • Couldn't match type ‘IsZero 'Z’ with ‘'True’
-- >
-- > f2res = f2 @'Z
-- > -- • Expected zero
--
-- As you can see, the error message in @f2res@ is misleading (because the type argument
-- actually _is_ zero), so it's preferable to fail with the standard GHC error message.
type TypeErrorUnless (cond :: Bool) (err :: ErrorMessage) =
( TypeErrorUnlessHelper cond err
-- Note: the '~' constraint below might seem redundant, but, without it,
-- GHC would warn that the following pattern match is not exhaustive (even though it is):
--
-- > f :: TypeErrorUnless (n >= ToPeano 2) "some err msg" => Sing n -> ()
-- > f = \case
-- > SS (SS SZ) -> ()
-- > SS (SS _) -> ()
--
-- GHC needs to "see" the type equality '~' in order to actually "learn" something from a
-- type family's result.
, cond ~ 'True
)
type family TypeErrorUnlessHelper (cond :: Bool) (err :: ErrorMessage) :: Constraint where
TypeErrorUnlessHelper 'True _ = ()
TypeErrorUnlessHelper 'False err = TypeError err