    {-|
    Copyright  :  (C) 2018     , QBayLogic B.V.
    License    :  BSD2 (see the file LICENSE)
    Maintainer :  Christiaan Baaij <christiaan.baaij@gmail.com>

    Hidden arguments
    -}

    {-# LANGUAGE AllowAmbiguousTypes #-}
    {-# LANGUAGE ConstraintKinds #-}
    {-# LANGUAGE FunctionalDependencies #-}
    {-# LANGUAGE MultiParamTypeClasses #-}
    {-# LANGUAGE RankNTypes #-}

    {-# LANGUAGE Trustworthy #-}

    module Clash.Hidden
      ( Hidden
      , expose
      -- * OverloadedLabels
      , fromLabel
      )
    where

    import qualified GHC.Classes
    import GHC.TypeLits
    import Unsafe.Coerce

    -- | A value reflected to, or /hiding/ at, the /Constraint/ level
    --
    -- e.g. a function:
    --
    -- @
    -- f :: Hidden "foo" Int
    --   => Bool
    --   -> Int
    -- f = ...
    -- @
    --
    -- has a /normal/ argument of type @Bool@, and a /hidden/ argument called \"foo\"
    -- of type @Int@. In order to apply the @Int@ argument we have to use the
    -- 'expose' function, so that the /hidden/ argument becomes a normal argument
    -- again.
    --
    -- === __Original implementation__
    --
    -- 'Hidden' used to be implemented by:
    --
    -- @
    -- class Hidden (x :: Symbol) a | x -> a where
    --   hidden :: a
    -- @
    --
    -- which is equivalent to /IP/, except that /IP/ has magic inference rules
    -- bestowed by GHC so that there's never any ambiguity. We need these magic
    -- inference rules so we don't end up in type inference absurdity where asking
    -- for the type of an type-annotated value results in a /no-instance-in-scope/
    -- error.
    type Hidden (x :: Symbol) a = GHC.Classes.IP x a

    newtype Secret x a r = Secret (Hidden x a => r)

    -- | Expose a 'Hidden' argument so that it can be applied normally, e.g.
    --
    -- @
    -- f :: Hidden "foo" Int
    --   => Bool
    --   -> Int
    -- f = ...
    --
    -- g :: Int -> Bool -> Int
    -- g = 'expose' \@\"foo" f
    -- @
    expose
      :: forall x a r
       . (Hidden x a => r)
      -- ^ Function with a 'Hidden' argument
      -> (a -> r)
      -- ^ Function with the 'Hidden' argument exposed
    expose :: forall (x :: Symbol) a r. (Hidden x a => r) -> a -> r
    expose Hidden x a => r
    k = Secret x a r -> a -> r
    forall a b. a -> b
    unsafeCoerce (forall (x :: Symbol) a r. (Hidden x a => r) -> Secret x a r
    Secret @x @a @r r
    Hidden x a => r
    k)
    {-# INLINE expose #-}

    -- | Using /-XOverloadedLabels/ and /-XRebindableSyntax/, we can turn any
    -- value into a /hidden/ argument using the @#foo@ notation, e.g.:
    --
    -- @
    -- f :: Int -> Bool -> Int
    -- f = ...
    --
    -- g :: Hidden "foo" Bool
    --   => Int -> Int
    -- g i = f i #foo
    -- @
    fromLabel :: forall x a . Hidden x a => a
    fromLabel :: forall (x :: Symbol) a. Hidden x a => a
    fromLabel = forall (x :: Symbol) a. Hidden x a => a
    GHC.Classes.ip @x
    {-# INLINE fromLabel #-}