apply-unordered-1.0: src/Control/Apply/Unordered/Plugin.hs
{-# LANGUAGE LambdaCase #-}
-- TODO: Cleanup this module, right now it's just the state at the end
-- of a hacking session to bring it to proof-of-concept.
module Control.Apply.Unordered.Plugin where
import Control.Monad (guard)
import Data.Generics (everything, mkQ)
import Data.Traversable (for)
import GHC.TcPluginM.Extra (lookupModule, lookupName, evByFiat)
import Data.Maybe
import GhcPlugins
import TcEvidence (EvTerm (..))
import TcPluginM (TcPluginM, tcLookupTyCon, newGiven)
import TcRnTypes
import TcType (isTyFamFree)
import Unify (tcMatchTy)
import TyCoRep
plugin :: Plugin
plugin = defaultPlugin
{ tcPlugin = const $ Just $ TcPlugin
{ tcPluginInit = initContext
, tcPluginSolve = \context _ _ ->
fmap (TcPluginOk []) . solveWanteds (implementTyFam context) (familyTyCon context)
, tcPluginStop = const $ pure ()
}
, pluginRecompile = const $ pure NoForceRecompile
}
data Context = Context
{ familyTyCon :: TyCon
, typeErrorTyCon :: TyCon
{-
, textTyCon :: TyCon
, showTypeTyCon :: TyCon
-}
}
initContext :: TcPluginM Context
initContext = Context
<$> loadTyCon
"apply-unordered"
"Control.Apply.Unordered"
"BestParamIxImpl"
<*> loadTyCon
"base"
"GHC.TypeLits"
"TypeError"
{-
<*> loadTyCon
"base"
"GHC.TypeLits"
"Text"
<*> loadTyCon
"base"
"GHC.TypeLits"
"ShowType"
-}
implementTyFam :: Context -> [Type] -> Maybe Type
implementTyFam context [a, b] = do
-- TODO: Are these guards really necessary? Useful to be able to
-- ignore stuckness?
guard $ isTyFamFree a
guard $ isTyFamFree b
pure $ promoteBestMatchResult context $ findBestMatch a b
promoteBestMatchResult :: Context -> BestMatchResult -> Type
promoteBestMatchResult context = \case
BestMatch n -> LitTy (NumTyLit n)
-- FIXME: Emit TypeError instead
AmbiguousMatch _ -> error "Ambiguous"
NoMatch -> error "No match"
data BestMatchResult
= BestMatch Integer
| AmbiguousMatch [(Integer, Type)]
| NoMatch
findBestMatch :: Type -> Type -> BestMatchResult
findBestMatch arg fun =
-- case foldr addMatch [] (pprTraceIt "candidates" (candidateMatches (pprTraceIt "arg" arg) 0 (pprTraceIt "fun" fun))) of
case foldr addMatch [] (candidateMatches arg 0 fun) of
[] -> NoMatch
[(ix, _)] -> BestMatch ix
matches -> AmbiguousMatch matches
-- This logic was directly inspired by the function insert_overlapping
-- in ghc/compiler/types/InstEnv.hs
addMatch :: (Integer, Type) -> [(Integer, Type)] -> [(Integer, Type)]
addMatch new_item [] = [new_item]
addMatch new_item (old_item : old_items)
-- New strictly overrides old
| new_beats_old
, not old_beats_new
= addMatch new_item old_items
-- Old strictly overrides new
| old_beats_new
, not new_beats_old
= old_item : old_items
| otherwise
= old_item : addMatch new_item old_items
where
new_beats_old = snd new_item `more_specific_than` snd old_item
old_beats_new = snd old_item `more_specific_than` snd new_item
-- | l can be instantiated to match r, or they are equal
more_specific_than :: Type -> Type -> Bool
more_specific_than l r = isJust (tcMatchTy r l)
candidateMatches :: Type -> Integer -> Type -> [(Integer, Type)]
candidateMatches arg ix = \case
FunTy paramTy resultTy
| isJust (tcMatchTy paramTy arg) ->
(ix, paramTy) : candidateMatches arg (ix + 1) resultTy
| otherwise ->
candidateMatches arg (ix + 1) resultTy
-- TODO: Does GHC ever represent function types using TyConApp?
TyConApp{} -> []
-- TODO: Handle forall.
ForAllTy{} -> []
CastTy ty _ -> candidateMatches arg ix ty
TyVarTy{} -> []
AppTy{} -> []
CoercionTy{} -> []
-- | Get your hands on the type constructor for your type family.
loadTyCon
:: String -- ^ package
-> String -- ^ module
-> String -- ^ identifier
-> TcPluginM TyCon
loadTyCon p m i = do
md <- lookupModule (mkModuleName m) $ fsLit p
nm <- lookupName md $ mkTcOcc i
tcLookupTyCon nm
-- | @solveWanteds f tyfam cts@ finds all instaces of @tyfam@ inside the wanted
-- constraints @cts@, and evaluates them via @f@. The result is a set of
-- 'CNonCanonical' constraints, which should be emitted as the second parameter
-- of 'TcPluginOk'.
solveWanteds
:: ([Type] -> Maybe Type)
-> TyCon
-> [Ct]
-> TcPluginM [Ct]
solveWanteds _ _ [] = pure []
solveWanteds f familyTyCon wanteds = do
let rel = map (\wanted -> findRelevant f familyTyCon (ctLoc wanted) (ctev_pred (cc_ev wanted))) wanteds
gs <- for (concat rel) $ \(MagicTyFamResult loc t res) -> do
let EvExpr ev = evByFiat "magic-tyfams" t res
newGiven loc (mkPrimEqPred t res) ev
pure $ fmap CNonCanonical gs
-- | Locate and expand the use of any type families.
findRelevant :: ([Type] -> Maybe Type) -> TyCon -> CtLoc -> Type -> [MagicTyFamResult]
findRelevant f familyTyCon loc = everything (++) $ mkQ [] findCmpType
where
findCmpType t =
case splitTyConApp_maybe t of
Just (tc, ts) | tc == familyTyCon ->
maybe [] (pure . MagicTyFamResult loc t) $ f ts
_ -> []
data MagicTyFamResult = MagicTyFamResult
{ _mtfrLoc :: CtLoc
, _mtfrOriginal :: Type
, _mtfrSolved :: Type
}