{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE RankNTypes #-}

-- | The 'ZonkM' monad, a stripped down 'TcM', used when zonking within
-- the typechecker in "GHC.Tc.Zonk.TcType".
--
-- See Note [Module structure for zonking] in GHC.Tc.Zonk.Type.
module GHC.Tc.Zonk.Monad
  ( -- * The 'ZonkM' monad, a stripped down 'TcM' for zonking
    ZonkM(ZonkM,runZonkM)
  , ZonkGblEnv(..), getZonkGblEnv

   -- ** Logging within 'ZonkM'
  , traceZonk

  )
  where

import GHC.Prelude

import GHC.Driver.Flags ( DumpFlag(Opt_D_dump_tc_trace) )

import GHC.Types.SrcLoc ( SrcSpan )

import GHC.Tc.Types.BasicTypes ( TcBinderStack )
import GHC.Tc.Utils.TcType   ( TcLevel )

import GHC.Utils.Logger
import GHC.Utils.Outputable

import Control.Monad          ( when )
import Control.Monad.IO.Class ( MonadIO(..) )

import GHC.Exts               ( oneShot )

--------------------------------------------------------------------------------

-- | Information needed by the 'ZonkM' monad, which is a slimmed down version
-- of 'TcM' with just enough information for zonking.
data ZonkGblEnv
  = ZonkGblEnv
    { zge_logger       :: Logger     -- needed for traceZonk
    , zge_name_ppr_ctx :: NamePprCtx --          ''
    , zge_src_span     :: SrcSpan  -- needed for skolemiseUnboundMetaTyVar
    , zge_tc_level     :: TcLevel  --               ''
    , zge_binder_stack :: TcBinderStack -- needed for tcInitTidyEnv
    }

-- | A stripped down version of 'TcM' which is sufficient for zonking types.
newtype ZonkM a = ZonkM' { runZonkM :: ZonkGblEnv -> IO a }
{-
NB: we write the following instances by hand:

--  deriving (Functor, Applicative, Monad, MonadIO)
--    via ReaderT ZonkGblEnv IO

See Note [Instances for ZonkT] in GHC.Tc.Zonk.Env for the reasoning:

  - oneShot annotations,
  - strictness annotations to enable worker-wrapper.
-}

{-# COMPLETE ZonkM #-}
pattern ZonkM :: forall a. (ZonkGblEnv -> IO a) -> ZonkM a
pattern ZonkM m <- ZonkM' m
  where
    ZonkM m = ZonkM' (oneShot m)
-- See Note [The one-shot state monad trick] in GHC.Utils.Monad

instance Functor ZonkM where
  fmap f (ZonkM g) = ZonkM $ \ !env -> fmap f (g env)
  a <$ ZonkM g     = ZonkM $ \ !env -> a <$ g env
  {-# INLINE fmap #-}
  {-# INLINE (<$) #-}
instance Applicative ZonkM where
  pure a = ZonkM (\ !_ -> pure a)
  ZonkM f <*> ZonkM x = ZonkM (\ !env -> f env <*> x env )
  ZonkM m *> f = ZonkM (\ !env -> m env *> runZonkM f env)
  {-# INLINE pure #-}
  {-# INLINE (<*>) #-}
  {-# INLINE (*>) #-}

instance Monad ZonkM where
  ZonkM m >>= f =
    ZonkM (\ !env -> do { r <- m env
                        ; runZonkM (f r) env })
  (>>)   = (*>)
  {-# INLINE (>>=) #-}
  {-# INLINE (>>) #-}

instance MonadIO ZonkM where
  liftIO f = ZonkM (\ !_ -> f)
  {-# INLINE liftIO #-}

getZonkGblEnv :: ZonkM ZonkGblEnv
getZonkGblEnv = ZonkM return
{-# INLINE getZonkGblEnv #-}

-- | Same as 'traceTc', but for the 'ZonkM' monad.
traceZonk :: String -> SDoc -> ZonkM ()
traceZonk herald doc = ZonkM $
  \ ( ZonkGblEnv { zge_logger = !logger, zge_name_ppr_ctx = ppr_ctx }) ->
    do { let sty   = mkDumpStyle ppr_ctx
             flag  = Opt_D_dump_tc_trace
             title = ""
             msg   = hang (text herald) 2 doc
       ; when (logHasDumpFlag logger flag) $
         logDumpFile logger sty flag title FormatText msg
       }
{-# INLINE traceZonk #-}
  -- see Note [INLINE conditional tracing utilities] in GHC.Tc.Utils.Monad
