packages feed

if-instance-0.6.0.0: src/IfSat/Plugin/Compat.hs

{-# LANGUAGE CPP #-}

{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TypeApplications #-}

{-# OPTIONS_GHC -Wno-unused-top-binds #-}

module IfSat.Plugin.Compat
  ( -- * Dealing with TcS state
    wrapTcS, getRestoreTcS
    -- * Dealing with mutable references
  , UnfilledRef(..), unfilledRefsOfType, unfillMutableRef
  )
  where

-- base
import Control.Monad
  ( when )
import Unsafe.Coerce
  ( unsafeCoerce )

-- ghc
import GHC.Core.Coercion
  ( mkCoVarCo, mkHoleCo, coVarName, coHoleCoVar )
import GHC.Core.Type
  ( TyCoMapper(..), mapTyCo )
#if MIN_VERSION_ghc(9,4,0)
import GHC.Tc.Solver.InertSet
  ( WorkList )
#endif
#if MIN_VERSION_ghc(9,1,0)
import GHC.Tc.Solver.Monad
  ( wrapTcS )
#endif
#if !MIN_VERSION_ghc(9,4,0)
import GHC.Tc.Solver.Monad
  ( WorkList )
#endif
import GHC.Tc.Types
  ( TcM, TcRef )

import GHC.Tc.Utils.TcMType
  ( isUnfilledMetaTyVar )
import GHC.Tc.Utils.TcType
  ( MetaDetails(..), metaTyVarRef )
import GHC.Tc.Types.Evidence
  ( EvBindsVar(..) )

-- transformers
import Control.Monad.Trans.Class ( lift )
import Control.Monad.Trans.Writer.CPS ( WriterT )
import qualified Control.Monad.Trans.Writer.CPS as Writer

-- ghc-tcplugin-api
import GHC.TcPlugin.API
import GHC.Types.Name.Env
import GHC.Types.Var (tyVarName)

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

-- | Capture the current 'TcS' state, returning an action which restores
-- the fields of 'TcSEnv' as appropriate after running a test-run
-- of 'solveSimpleWanteds' and deciding to backtrack.
getRestoreTcS :: TcS (TcS ())
getRestoreTcS = do
  shim_tcs_env <- getShimTcSEnv
  let ev_binds_var   = shim_tcs_ev_binds shim_tcs_env
      unif_var       = shim_tcs_unified  shim_tcs_env
#if MIN_VERSION_ghc(9,1,0)
      unif_lvl_var   = shim_tcs_unif_lvl shim_tcs_env
#endif
      unit_count_var = shim_tcs_count    shim_tcs_env
  wrapTcS $ do
    restore_evBinds <- case ev_binds_var of
      EvBindsVar { ebv_binds = ev_binds_ref
                 , ebv_tcvs  = ev_cvs_ref } ->
        do ev_binds <- readTcRef ev_binds_ref
           ev_cvs   <- readTcRef ev_cvs_ref
           return do
             writeTcRef ev_binds_ref ev_binds
             writeTcRef ev_cvs_ref   ev_cvs
      CoEvBindsVar { ebv_tcvs = ev_cvs_ref } ->
        do ev_cvs   <- readTcRef ev_cvs_ref
           return do
             writeTcRef ev_cvs_ref   ev_cvs

    unif         <- readTcRef unif_var
#if MIN_VERSION_ghc(9,1,0)
    unif_lvl     <- readTcRef unif_lvl_var
#endif
    count        <- readTcRef unit_count_var
    return $ wrapTcS $ do
      restore_evBinds
      writeTcRef unif_var       unif
#if MIN_VERSION_ghc(9,1,0)
      writeTcRef unif_lvl_var   unif_lvl
#endif
      writeTcRef unit_count_var count

  -- NB: no need to reset 'tcs_inerts' or 'tcs_worklist', because
  -- 'solveSimpleWanteds' calls 'nestTcS', which appropriately resets
  -- both of those fields.

#if !MIN_VERSION_ghc(9,1,0)
wrapTcS :: TcM a -> TcS a
wrapTcS = unsafeCoerce const
#endif

-- Obtain the 'TcSEnv' underlying the 'TcS' monad (in the form of a 'ShimTcSEnv').
getShimTcSEnv :: TcS ShimTcSEnv
getShimTcSEnv = unsafeCoerce ( return :: ShimTcSEnv -> TcM ShimTcSEnv )

-- | A shim copy of "GHC.Tc.Solver.Monad.TcSEnv", to work around the
-- fact that it isn't exported.
--
-- Needs to be manually kept in sync with 'TcSEnv' to avoid segfaults due
-- to the use of 'unsafeCoerce' in 'getShimTcSEnv'.
data ShimTcSEnv
  = ShimTcSEnv
  { shim_tcs_ev_binds           :: EvBindsVar
  , shim_tcs_unified            :: TcRef Int
#if MIN_VERSION_ghc(9,1,0)
  , shim_tcs_unif_lvl           :: TcRef (Maybe TcLevel)
#endif
  , shim_tcs_count              :: TcRef Int
  , shim_tcs_inerts             :: TcRef InertSet
#if MIN_VERSION_ghc(9,3,0)
  , shim_tcs_abort_on_insoluble :: Bool
#endif
  , shim_tcs_worklist           :: TcRef WorkList
  }

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

-- | A mutable reference that was originally unfilled
data UnfilledRef
  -- | A metavariable that was originally unfilled
  = UnfilledMeta   !( TcRef MetaDetails )
  -- | A coercion hole that was originally unfilled
  | UnfilledCoHole !( TcRef ( Maybe Coercion ) )

-- | Gather all the unfilled mutable references of a type: unfilled
-- metavariables and unfilled coercion holes.
unfilledRefsOfType :: TcType -> TcM [ UnfilledRef ]
unfilledRefsOfType ty0
  = fmap
#if MIN_VERSION_ghc(9,3,0)
      nonDetNameEnvElts
#else
      nameEnvElts
#endif
  $ Writer.execWriterT
  $ go_ty ty0
  where
    (go_ty, _go_tys, _go_co, _go_cos) =
      mapTyCo @( WriterT ( NameEnv UnfilledRef ) TcM ) $
        -- Use a NameEnv to avoid collecting the same reference twice
        -- (not that it would be particularly harmful to do so).
        TyCoMapper
          { tcm_tyvar = \ _ tv -> do
              unfilled_meta <- lift $ isUnfilledMetaTyVar tv
              when unfilled_meta do
                let nm = tyVarName tv
                    ref = UnfilledMeta $ metaTyVarRef tv
                Writer.tell $ unitNameEnv nm ref
              return $ mkTyVarTy tv
          , tcm_tycobinder =
#if MIN_VERSION_ghc(9,7,0)
              \ () tv _ftf k -> k () tv
#else
              \ () tv _af -> return ( (), tv )
#endif
          , tcm_tycon = return
          , tcm_covar = \ _ cv -> return $ mkCoVarCo cv
          , tcm_hole = \ _ hole@(CoercionHole { ch_ref = hole_ref }) -> do
              hole_contents <- lift $ readTcRef hole_ref
              case hole_contents of
                Nothing -> do
                  let nm = coVarName $ coHoleCoVar hole
                      ref = UnfilledCoHole hole_ref
                  Writer.tell $ unitNameEnv nm ref
                Just {} ->
                  return ()
              return $ mkHoleCo hole
          }

-- | Restore a mutable reference to the unfilled state.
unfillMutableRef :: UnfilledRef -> TcM ()
unfillMutableRef = \case
  UnfilledMeta   ref  -> writeTcRef ref  Flexi
  UnfilledCoHole hole -> writeTcRef hole Nothing