{-|
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 DataKinds              #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE KindSignatures         #-}
{-# LANGUAGE MultiParamTypeClasses  #-}
{-# LANGUAGE Rank2Types             #-}
{-# LANGUAGE ScopedTypeVariables    #-}
{-# LANGUAGE TypeApplications       #-}

{-# 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 k = unsafeCoerce (Secret @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 = GHC.Classes.ip @x
{-# INLINE fromLabel #-}