    {-# LANGUAGE ConstraintKinds       #-}
    {-# LANGUAGE CPP                   #-}
    {-# LANGUAGE FlexibleInstances     #-}
    {-# LANGUAGE DefaultSignatures     #-}
    {-# LANGUAGE MagicHash             #-}
    {-# LANGUAGE MultiParamTypeClasses #-}
    -- |
    -- Module      : Basement.Cast
    -- License     : BSD-style
    -- Maintainer  : Haskell Foundation
    --
    module Basement.Cast
        ( Cast(..)
        ) where

    #include "MachDeps.h"

    import qualified Basement.Block.Base as Block
    import           Basement.Compat.Base
    import           Basement.Compat.Natural
    import           Basement.Numerical.Number
    import           Basement.Numerical.Conversion
    import           Basement.PrimType

    import           Data.Proxy (Proxy(..))

    import           GHC.Int
    import           GHC.Prim
    import           GHC.Types
    import           GHC.ST
    import           GHC.Word

    -- | `Cast` an object of type a to b.
    --
    -- Do not add instance of this class if the source type is not of the same
    -- size of the destination type. Also keep in mind this is casting a value
    -- of a given type into a destination type. The value won't be changed to
    -- fit the destination represention.
    --
    -- If you wish to convert a value of a given type into another type, look at
    -- `From` and `TryFrom`.
    --
    -- @
    -- cast (-10 :: Int) :: Word === 18446744073709551606
    -- @
    --
    class Cast source destination where
        cast :: source -> destination

        default cast :: ( PrimType source
                        , PrimType destination
                        , PrimSize source ~ PrimSize destination
                        )
                     => source -> destination
        cast a = runST $ do
            mba <- Block.new 1
            Block.unsafeWrite mba 0 a
            Block.unsafeRead (Block.unsafeRecast mba) 0

    instance Cast Int8  Word8 where
        cast (I8# i) = W8# (narrow8Word# (int2Word# i))
    instance Cast Int16 Word16 where
        cast (I16# i) = W16# (narrow16Word# (int2Word# i))
    instance Cast Int32 Word32 where
        cast (I32# i) = W32# (narrow32Word# (int2Word# i))
    instance Cast Int64 Word64 where
        cast = int64ToWord64
    instance Cast Int   Word where
        cast (I# i) = W# (int2Word# i)

    instance Cast Word8  Int8 where
        cast (W8# i) = I8# (narrow8Int# (word2Int# i))
    instance Cast Word16 Int16 where
        cast (W16# i) = I16# (narrow16Int# (word2Int# i))
    instance Cast Word32 Int32 where
        cast (W32# i) = I32# (narrow32Int# (word2Int# i))
    instance Cast Word64 Int64 where
        cast = word64ToInt64
    instance Cast Word   Int where
        cast (W# w) = I# (word2Int# w)

    #if WORD_SIZE_IN_BITS == 64
    instance Cast Word   Word64 where
        cast (W# w) = W64# w
    instance Cast Word64 Word where
        cast (W64# w) = W# w

    instance Cast Word   Int64 where
        cast (W# w) = I64# (word2Int# w)
    instance Cast Int64  Word where
        cast (I64# i) = W# (int2Word# i)

    instance Cast Int    Int64 where
        cast (I# i) = I64# i
    instance Cast Int64  Int where
        cast (I64# i) = I# i

    instance Cast Int    Word64 where
        cast (I# i) = W64# (int2Word# i)
    instance Cast Word64 Int where
        cast (W64# w) = I# (word2Int# w)
    #else
    instance Cast Word   Word32 where
        cast (W# w) = W32# w
    instance Cast Word32 Word where
        cast (W32# w) = W# w

    instance Cast Word   Int32 where
        cast (W# w) = I32# (word2Int# w)
    instance Cast Int32  Word where
        cast (I32# i) = W# (int2Word# i)

    instance Cast Int    Int32 where
        cast (I# i) = I32# i
    instance Cast Int32  Int where
        cast (I32# i) = I# i

    instance Cast Int    Word32 where
        cast (I# i) = W32# (int2Word# i)
    instance Cast Word32 Int where
        cast (W32# w) = I# (word2Int# w)
    #endif

    instance Cast (Block.Block a) (Block.Block Word8) where
        cast (Block.Block ba) = Block.Block ba