if-instance-0.1.0.0: src/IfCt/Plugin.hs
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE PatternSynonyms #-}
module IfCt.Plugin
( plugin )
where
-- base
import Data.Maybe
( catMaybes )
#if !MIN_VERSION_ghc(9,2,0)
import Unsafe.Coerce
( unsafeCoerce )
#endif
-- ghc
import GHC.Plugins
( Plugin(..)
, defaultPlugin, purePlugin
)
import GHC.Data.Bag
( unitBag )
import GHC.Tc.Solver.Interact
( solveSimpleGivens, solveSimpleWanteds )
import GHC.Tc.Solver.Monad
( getTcEvBindsMap, readTcRef, runTcSWithEvBinds, traceTcS
#if MIN_VERSION_ghc(9,2,0)
, wrapTcS
#else
, TcS
#endif
)
import GHC.Tc.Types
( TcM )
import GHC.Utils.Outputable
( (<+>), ($$), empty, text, vcat )
-- ghc-tcplugin-api
import GHC.TcPlugin.API
import GHC.TcPlugin.API.Internal
( unsafeLiftTcM )
--------------------------------------------------------------------------------
-- Plugin definition.
-- | A type-checking plugin that solves @MyCt ct@ constraints.
-- Theis allows users to branch on whether @ct@ is satisfied.
--
-- To use this plugin, add @{-# OPTIONS_GHC -fplugin=IfCt.Plugin #-}@
-- to your module header.
--
-- A @MyCt ct@ instance is solved by trying to solve @ct@:
--
-- - if solving succeeds, the 'Data.Constraint.If.ifCt' function will
-- pick the first branch,
-- - otherwise, 'Data.Constraint.If.ifCt' will pick the second branch.
--
-- This means that the branch selection occurs precisely at the moment
-- at which we solve the @IfCt ct@ constraint.
-- See the documentation of 'Data.Constraint.If.IfCt' for more information.
plugin :: Plugin
plugin =
defaultPlugin
{ tcPlugin = \ _args -> Just $ mkTcPlugin ifCtTcPlugin
, pluginRecompile = purePlugin
}
ifCtTcPlugin :: TcPlugin
ifCtTcPlugin =
TcPlugin
{ tcPluginInit = initPlugin
, tcPluginSolve = solver
, tcPluginRewrite = \ _ -> emptyUFM
, tcPluginStop = \ _ -> pure ()
}
--------------------------------------------------------------------------------
-- Plugin initialisation.
data PluginDefs
= PluginDefs
{ ifCtClass :: !Class }
findModule :: MonadTcPlugin m => Maybe String -> String -> m Module
findModule mb_pkg modName = do
findResult <- findImportedModule ( mkModuleName modName ) ( fmap fsLit mb_pkg )
case findResult of
Found _ res -> pure res
FoundMultiple _ -> error $ "IfCt plugin: found multiple modules named " <> modName <> "."
_ -> error $ "IfCt plugin: could not find any module named " <> modName <> "."
initPlugin :: TcPluginM Init PluginDefs
initPlugin = do
ifCtModule <- findModule Nothing "Data.Constraint.If"
ifCtClass <- tcLookupClass =<< lookupOrig ifCtModule ( mkClsOcc "IfCt" )
pure $ PluginDefs { ifCtClass }
--------------------------------------------------------------------------------
-- Constraint solving.
solver :: PluginDefs -> [ Ct ] -> [ Ct ] -> TcPluginM Solve TcPluginSolveResult
solver defs givens wanteds
| null wanteds
= pure $ TcPluginOk [] []
| otherwise
= do
tcPluginTrace "IfCt plugin {" (ppr givens $$ ppr wanteds)
solveds <- catMaybes <$> traverse ( solveWanted defs givens ) wanteds
tcPluginTrace "IfCt plugin }" empty
pure $ TcPluginOk solveds []
solveWanted :: PluginDefs -> [ Ct ] -> Ct -> TcPluginM Solve ( Maybe ( EvTerm, Ct ) )
solveWanted defs@( PluginDefs { ifCtClass } ) givens wanted
| ClassPred cls [ct_ty] <- classifyPredType ( ctPred wanted )
, cls == ifCtClass
= do
tcPluginTrace "IfCt plugin: found IfCt constraint" ( ppr wanted )
ct_ev <- newWanted ( ctLoc wanted ) ct_ty
let
ct :: Ct
ct = mkNonCanonical ct_ev
ct_ev_dest :: TcEvDest
ct_ev_dest = ctev_dest ct_ev
evBindsVar <- askEvBinds
-- Start a new Solver run.
unsafeLiftTcM $ runTcSWithEvBinds evBindsVar $ do
-- Add back all the Givens.
traceTcS "IfCt plugin: adding Givens to the inert set" (ppr givens)
solveSimpleGivens givens
-- Try to solve 'ct', using both Givens and top-level instances.
_ <- solveSimpleWanteds ( unitBag ct )
-- Now look up whether GHC has managed to produce evidence for 'ct'.
mb_ct_evTerm <-
case ct_ev_dest of
HoleDest ( CoercionHole { ch_ref = ref } ) -> do
mb_co <- readTcRef ref
traceTcS "IfCt plugin: coercion hole" (ppr mb_co)
case mb_co of
Nothing -> pure Nothing
Just co -> pure . Just $ evCoercion co
EvVarDest ev_var -> do
evBindsMap <- getTcEvBindsMap evBindsVar
let
mb_evBind :: Maybe EvBind
mb_evBind = lookupEvBind evBindsMap ev_var
traceTcS "IfCt plugin: evidence binding" (ppr mb_evBind)
case mb_evBind of
Nothing -> pure Nothing
Just ev_bind -> pure . Just $ eb_rhs ev_bind
wanted_evTerm <- case mb_ct_evTerm of
Just ( EvExpr ct_evExpr ) -> do
-- We've managed to solve 'ct': use the evidence and take the 'True' branch.
traceTcS "IfCt plugin: constraint could be solved"
( vcat
[ text "ct =" <+> ppr ct_ty
, text "ev =" <+> ppr ct_evExpr
]
)
wrapTcS $ ifCtTrueEvTerm defs ct_ty ct_evExpr
_ -> do
-- We couldn't solve 'ct': take the 'False' branch.
traceTcS "IfCt plugin: constraint could not be solved"
( text "ct =" <+> ppr ct_ty )
wrapTcS $ ifCtFalseEvTerm defs ct_ty
pure $ Just ( wanted_evTerm, wanted )
| otherwise
= pure Nothing
-- Evidence term for @IfCt ct@ when @ct@ isn't satisfied.
-- ifCt = \ @r (a :: ct => r) (_ :: r) -> a ct_evTerm
ifCtTrueEvTerm :: PluginDefs -> Type -> EvExpr -> TcM EvTerm
ifCtTrueEvTerm ( PluginDefs { ifCtClass } ) ct_ty ct_evTerm = do
r_name <- newName ( mkTyVarOcc "r" )
a_name <- newName ( mkVarOcc "a" )
let
r, a, b :: CoreBndr
r = mkTyVar r_name liftedTypeKind
a = mkLocalId a_name Many ( mkInvisFunTyMany ct_ty r_ty )
b = mkWildValBinder Many r_ty
r_ty :: Type
r_ty = mkTyVarTy r
pure . EvExpr $
mkCoreConApps ( classDataCon ifCtClass )
[ Type ct_ty
, mkCoreLams [ r, a, b ]
( mkCoreApps ( Var a ) [ ct_evTerm ] )
]
-- Evidence term for @IfCt ct@ when @ct@ isn't satisfied.
-- ifCt = \ @r (_ :: ct => r) (b :: r) -> b
ifCtFalseEvTerm :: PluginDefs -> Type -> TcM EvTerm
ifCtFalseEvTerm ( PluginDefs { ifCtClass } ) ct_ty = do
r_name <- newName ( mkTyVarOcc "r" )
b_name <- newName ( mkVarOcc "b" )
let
r, a, b :: CoreBndr
r = mkTyVar r_name liftedTypeKind
a = mkWildValBinder Many ( mkInvisFunTyMany ct_ty r_ty )
b = mkLocalId b_name Many r_ty
r_ty :: Type
r_ty = mkTyVarTy r
pure . EvExpr $
mkCoreConApps ( classDataCon ifCtClass )
[ Type ct_ty
, mkCoreLams [ r, a, b ] ( Var b )
]
--------------------------------------------------------------------------------
#if !MIN_VERSION_ghc(9,2,0)
wrapTcS :: TcM a -> TcS a
wrapTcS = unsafeCoerce const
#endif