{-# LANGUAGE TypeFamilies  #-}
{-# LANGUAGE TypeInType    #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UnicodeSyntax #-}

{- |
Copyright:  (c) 2019-2020 Dmitrii Kovanikov
SPDX-License-Identifier: MPL-2.0
Maintainer: Dmitrii Kovanikov <kovanikov@gmail.com>

This module provides type-level functions and operators to make the job of
writing text of custom type errors easier. The motivation behind using custom
type errors is described in detail in the following blog post:

* [A story told by Type Errors](https://kodimensional.dev/type-errors)

If you want to write the text of a custom error message, you need to use
constructors of the 'ErrorMessage' data type. But this gets messy and
inconvenient pretty quickly. Consider the following examples:

@
__type__ MessageText (e1 :: k) (e2 :: k) (es :: [k]) =
       ''Text' "You require the following two effects from your computation:"
 '':$$:' ''Text' ""
 '':$$:' ''Text' "    '" '':<>:' ''ShowType' e1 '':<>:' ''Text' "' and '" '':<>:' ''ShowType' e2 '':<>:' ''Text' "'"
 '':$$:' ''Text' ""
 '':$$:' ''Text' "However, your monad is capable of performing only the following effects:"
 '':$$:' ''Text' ""
 '':$$:' ''Text' "    " '':<>:' ''ShowType' es
@

Using combinators from this library, you can define error messages in a simpler
way:

@
__type__ MessageText (e1 :: k) (e2 :: k) (es :: [k])
    = "You require the following two effects from your computation:"
    '%' ""
    '%' "    '" 'Type.Errors.Pretty.<>' e1 'Type.Errors.Pretty.<>' "' and '" 'Type.Errors.Pretty.<>' e2 'Type.Errors.Pretty.<>' "'"
    '%' ""
    '%' "However, your monad is capable of performing only the following effects:"
    '%' ""
    '%' "    " 'Type.Errors.Pretty.<>' es
@

If you prefer, you can use unicode operators to contstruct messages:

@
__type__ MessageText (e1 :: k) (e2 :: k) (es :: [k])
    = "You require the following two effects from your computation:"
    '•' ""
    '•' "    '" '⊕' e1 '⊕' "' and '" '⊕' e2 '⊕' "'"
    '•' ""
    '•' "However, your monad is capable of performing only the following effects:"
    '•' ""
    '•' "    " '⊕' es
@
-}

module Type.Errors.Pretty
       ( -- * Combinators
         type (<>)
       , type ()
       , type (%)
       , type ()

         -- * Reexports from "GHC.TypeLits"
       , TypeError

         -- * Helper internal type families
       , ToErrorMessage
       ) where

import GHC.TypeLits (ErrorMessage (..), Symbol, TypeError)


{- | Append two types automatically converting them to corresponding
'ErrorMessage' constructors.

>>> :kind! "Integer values have type: " <> Int
"Integer values have type: " <> Int :: ErrorMessage
= 'Text "Integer values have type: " ':<>: 'ShowType Int
-}
infixl 5 <>
type family (<>) (l :: k1) (r :: k2) :: ErrorMessage where
    l <> r = ToErrorMessage l ':<>: ToErrorMessage r

{- | Unicode version of the 'Type.Errors.Pretty.<>' type-level operator.
-}
infixl 5 
type () (l :: k1) (r :: k2) = l <> r

{- | Append two types on top of each other automatically converting them to
corresponding 'ErrorMessage' constructors.

>>> :kind! "Expecting value of type: " % "   " <> Integer
"Expecting value of type: " % "   " <> Integer :: ErrorMessage
= 'Text "Expecting value of type: "
  ':$$: ('Text "   " ':<>: 'ShowType Integer)
-}
infixr 4 %
type family (%) (t :: k1) (b :: k2) :: ErrorMessage where
    t % b = ToErrorMessage t ':$$: ToErrorMessage b

{- | Unicode version of the '%' type-level operator.
-}
infixr 4 
type () (t :: k1) (b :: k2) = t % b

-- | Type family to convert any type to 'ErrorMessage'.
type family ToErrorMessage (t :: k) :: ErrorMessage where
    ToErrorMessage (t :: Symbol) = 'Text t
    ToErrorMessage (t :: ErrorMessage) = t
    ToErrorMessage t = 'ShowType t