-- |
-- Fixed versions of 'reifyInstances' and 'isInstance' as per
-- the following ghc issue:
-- <https://ghc.haskell.org/trac/ghc/ticket/7066>.
module THInstanceReification
(
reifyProperInstances,
isProperInstance,
typesSatisfyDecConstraints,
)
where
import THInstanceReification.Prelude.Basic
import THInstanceReification.Prelude.TH
import qualified Data.Map as Map
-- |
-- Same as 'reifyInstances', but also checks the constraints.
reifyProperInstances :: Name -> [Type] -> Q [InstanceDec]
reifyProperInstances n tl =
reifyInstances n tl >>= filterM (typesSatisfyDecConstraints tl)
-- |
-- Same as 'isInstance', but also checks the constraints.
isProperInstance :: Name -> [Type] -> Q Bool
isProperInstance n tl =
not . null <$> reifyProperInstances n tl
-- |
-- Analyze the constraints of the provided instance dec to be satisfied
-- by the provided types.
--
-- Note that this function does not analyze the equality constraints (@F a ~ Bool@).
-- It simply considers them to be true.
typesSatisfyDecConstraints :: [Type] -> InstanceDec -> Q Bool
typesSatisfyDecConstraints tl = \case
InstanceD _ context instanceType _ -> do
let ([ConT n], htl) = splitAt 1 $ reverse $ unapplyType instanceType
-- Expand type synonyms in type signatures,
-- using 'expandSyns' from the "th-expand-syns" library:
expandedTypes <- mapM expandSyns tl
expandedInstanceTypes <- mapM expandSyns htl
maybe
(fail $ "Unmatching amounts of types: " <> show expandedTypes <> ", " <>
show expandedInstanceTypes)
(analyze context)
-- 'pair' is a safe version of 'zip' from the "list-extras" library,
-- which returns 'Nothing', when lists differ in size.
(pair expandedTypes expandedInstanceTypes)
d -> fail $ "Not an instance dec: " <> show d
where
-- |
-- Test, whether a list of associations from tested types to types in instance head
-- satisfies the given context.
analyze :: Cxt -> [(Type, Type)] -> Q Bool
analyze context typeAssocs = and <$> mapM analyzePredicate context
where
-- |
-- A partial function,
-- which returns a tested type by a variable name.
actualTypeByVarName :: Name -> Maybe Type
actualTypeByVarName = \n -> Map.lookup n m
where
-- A memoization cache.
m = Map.fromList $ concat $ map accRecords $ typeAssocs
where
-- Parallelly expand all associations down to type variables
-- of instance head,
-- producing a list of associations of names of those variables
-- to tested types.
accRecords = \case
(AppT al ar, AppT hl hr) -> accRecords (al, hl) ++ accRecords (ar, hr)
(a, VarT n) -> [(n, a)]
_ -> []
-- |
-- Test a predicate by substituting all type vars with associated
-- tested types.
analyzePredicate :: Pred -> Q Bool
analyzePredicate = \case
AppT (AppT EqualityT _) _ -> return True
AppT (ConT n) t ->
case replaceTypeVars actualTypeByVarName t of
Just t' -> isProperInstance n [t']
_ -> return False
_ -> return True
unapplyType :: Type -> [Type]
unapplyType = \case
AppT l r -> r : unapplyType l
t -> [t]
-- | Deeply traverse the type signature and replace all vars in it.
replaceTypeVars :: (Name -> Maybe Type) -> Type -> Maybe Type
replaceTypeVars f = \case
AppT l r -> AppT <$> replaceTypeVars f l <*> replaceTypeVars f r
VarT n -> f n
t -> Just t