constraints-emerge-0.1.2: Data/Constraint/Emerge/Plugin.hs
{-# OPTIONS_GHC -Wall #-}
module Data.Constraint.Emerge.Plugin (plugin) where
import Control.Exception (throw)
import Control.Monad
import Data.Bifunctor (second)
import Data.Map (Map)
import qualified Data.Map as M
import Data.Maybe
import Data.Traversable (for)
import Data.Tuple (swap)
import Prelude hiding (pred)
import TcType
import Class hiding (className)
import GHC (GhcException (..), idType)
import InstEnv (ClsInst (..), lookupUniqueInstEnv)
import Module (mkModuleName)
import OccName (mkTcOcc)
import Outputable
import Plugins (Plugin (..), defaultPlugin)
import TcEvidence (EvTerm (EvDFunApp))
import TcPluginM
import TcRnTypes
import TyCon (TyCon, tyConName)
import Type (Type, TyVar, splitTyConApp_maybe, isTyVarTy, splitAppTy_maybe, getTyVar_maybe, splitAppTys)
------------------------------------------------------------------------------
-- | Match an instance head against a concrete type; returning a substitution
-- from one to the other. If this returns 'mempty', there were no type
-- variables to match.
match
:: PredType -- class inst
-> PredType -- concrete type
-> Map TyVar Type
match instClass concClass =
let Just (_, instHead) = splitAppTy_maybe instClass
Just (_, concHead) = splitAppTy_maybe concClass
(_, instTys) = splitAppTys instHead
(_, concTys) = splitAppTys concHead
in M.fromList . mapMaybe (fmap swap . sequence . second getTyVar_maybe)
$ zip concTys instTys
------------------------------------------------------------------------------
-- | Determine if a 'PredType' is fully monomorphic (ie. has no type
-- variables.)
isMonomorphicCtx
:: PredType
-> Bool
isMonomorphicCtx t = isJust $ do
(_, instHead) <- splitAppTy_maybe t
let (_, instTys) = splitAppTys instHead
case null $ mapMaybe getTyVar_maybe instTys of
True -> Just ()
False -> Nothing
------------------------------------------------------------------------------
-- | Substitute all type variables in a 'Type'.
-- TODO(sandy): this should probably operate over a 'PredType' and explicily
-- split and fold, rather than assuming a shape of 'C a b c'.
instantiateHead
:: Map TyVar Type
-> Type
-> Type
instantiateHead mmap t =
let (tc, tys) = splitAppTys t
tys' = fmap (\ty -> maybe ty (mmap M.!) $ getTyVar_maybe ty) tys
in mkAppTys tc tys'
------------------------------------------------------------------------------
-- | Construct an 'EvTerm' witnessing an instance of 'wantedDict' by
-- recursively finding instances, and building dicts for their contexts.
buildDict
:: CtLoc
-> PredType
-> TcPluginM (Maybe EvTerm)
buildDict loc wantedDict = do
(className, classParams) <-
case splitTyConApp_maybe wantedDict of
Just a -> pure a
Nothing -> errMissingSig loc
myclass <- tcLookupClass $ tyConName className
envs <- getInstEnvs
case lookupUniqueInstEnv envs myclass classParams of
-- success!
Right (clsInst, _) -> do
let dfun = is_dfun clsInst
(vars, subclasses, inst) = tcSplitSigmaTy $ idType dfun
mmap = match inst wantedDict
if null subclasses
then pure . Just $ EvDFunApp dfun [] []
else do
mayDicts <- traverse (buildDict loc . instantiateHead mmap) subclasses
for (sequence mayDicts) $ \dicts ->
pure $ EvDFunApp dfun (fmap (mmap M.!) vars) dicts
Left _ -> do
-- check givens?
pure Nothing
------------------------------------------------------------------------------
-- | The exported 'Emerge' plugin.
plugin :: Plugin
plugin = defaultPlugin { tcPlugin = const $ Just emergePlugin }
------------------------------------------------------------------------------
-- | Actual implementation of the plugin.
emergePlugin :: TcPlugin
emergePlugin = TcPlugin
{ tcPluginInit = lookupEmergeTyCons
, tcPluginSolve = solveEmerge
, tcPluginStop = const (return ())
}
------------------------------------------------------------------------------
-- | Class instances from 'Data.Constraint.Emerge' necessary for us to know
-- about.
data EmergeData = EmergeData
{ emergeEmerge :: Class
, emergeFail :: Class
, emergeSucceed :: Class
}
------------------------------------------------------------------------------
-- | Lookup the classes from 'Data.Constraint.Emerge' and build an
-- 'EmergeData'.
lookupEmergeTyCons :: TcPluginM EmergeData
lookupEmergeTyCons = do
Found _ md <- findImportedModule emergeModule Nothing
emergeTcNm <- lookupOrig md $ mkTcOcc "Emerge"
failTcNm <- lookupOrig md $ mkTcOcc "AlwaysFail"
succeedTcNm <- lookupOrig md $ mkTcOcc "Succeed"
EmergeData
<$> tcLookupClass emergeTcNm
<*> tcLookupClass failTcNm
<*> tcLookupClass succeedTcNm
where
emergeModule = mkModuleName "Data.Constraint.Emerge"
------------------------------------------------------------------------------
-- | Determines if 'ct' is an instance of the 'c' class, and if so, splits out
-- its applied types.
findEmergePred :: Class -> Ct -> Maybe (Ct, [PredType])
findEmergePred c ct = do
let pred = ctev_pred $ cc_ev ct
case splitTyConApp_maybe pred of
Just (x, preds) ->
case x == classTyCon c of
True -> Just (ct, preds)
False -> Nothing
_ -> Nothing
------------------------------------------------------------------------------
-- | Discharge all wanted 'Emerge' constraints.
solveEmerge
:: EmergeData
-> [Ct] -- ^ [G]iven constraints
-> [Ct] -- ^ [D]erived constraints
-> [Ct] -- ^ [W]anted constraints
-> TcPluginM TcPluginResult
solveEmerge emerge _ _ allWs = do
let ws = mapMaybe (findEmergePred $ emergeEmerge emerge) allWs
case length ws of
0 -> pure $ TcPluginOk [] []
_ -> do
evs <- for ws $ discharge emerge
pure $ TcPluginOk evs []
------------------------------------------------------------------------------
-- | Discharge an 'Emerge' constraint.
discharge
:: EmergeData
-> (Ct, [PredType])
-> TcPluginM (EvTerm, Ct)
discharge emerge (ct, ts) = do
let [wantedDict] = ts
loc = ctev_loc $ cc_ev ct
when (not $ isMonomorphicCtx wantedDict) $
errPolyUsage loc
(className, classParams) <-
case splitTyConApp_maybe wantedDict of
Just a -> pure a
Nothing -> errMissingSig loc
envs <- getInstEnvs
mayMyDict <- buildDict loc wantedDict
case mayMyDict of
-- we successfully built a dict
Just myDict ->
case lookupUniqueInstEnv envs (emergeSucceed emerge) ts of
Right (successInst, _) ->
pure (EvDFunApp (is_dfun successInst) ts [myDict], ct)
Left err ->
pprPanic "couldn't get a unique instance for Success" err
-- couldn't find the instance
Nothing -> do
when (any isTyVarTy classParams) $
errPolyClassTys className classParams loc
case lookupUniqueInstEnv envs (emergeFail emerge) [] of
Right (clsInst, _) ->
pure (EvDFunApp (is_dfun clsInst) [] [], ct)
Left err ->
pprPanic "couldn't get a unique instance for AlwaysFail" err
------------------------------------------------------------------------------
-- | Error out complaining about polymorphic type variables in the implicit
-- context.
errPolyClassTys :: TyCon -> [Type] -> CtLoc -> a
errPolyClassTys c ts loc = errHelper loc
[ text "Polymorphic type variables bound in the implicit constraint of 'Emerge'"
$$ hang empty 2 (ppr (ctl_origin loc))
, text "Probable fix: add an explicit 'Emerge ("
<> ppr c
<+> foldl (<+>) empty (fmap ppr $ ts )
<> text ")' constraint to the type signature"
]
------------------------------------------------------------------------------
-- | Erorr out complaining about a polymorphic constraint.
errMissingSig :: CtLoc -> a
errMissingSig loc = errHelper loc
[ text "Polymorphic constraint bound in the implicit constraint of 'Emerge'"
$$ hang empty 2 (ppr (ctl_origin loc))
, text "Probable fix: add an explicit 'Emerge c'"
<+> text "constraint to the type signature"
]
------------------------------------------------------------------------------
-- | Error out complaining about a polymorphic usage.
errPolyUsage :: CtLoc -> a
errPolyUsage loc = errHelper loc
[ text "Polymorphic usage of function bound with an implicit 'Emerge' constraint"
$$ hang empty 2 (ppr (ctl_origin loc))
, text "'Emerge' can only be discharged when it is fully monomorphic"
, text "Fix: make the call-site monomorphic, or add an explicit 'Emerge c'"
$$ hang empty 2 (text "constraint to the type-signature.")
]
------------------------------------------------------------------------------
-- | Helper function to generate pretty error messages.
errHelper :: CtLoc -> [SDoc] -> a
errHelper loc ss
= throw
. PprProgramError ""
. hang (ppr . tcl_loc $ ctl_env loc) 2
. foldl ($$) empty
$ fmap (\z -> hang empty 0 $ char '•' <+> z) ss