easytensor-2.1.1.0: test/Spec/Util.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
module Spec.Util (propWithTypes, testWithTypes) where
import Control.Applicative
import Control.Monad
import Data.Monoid
import Language.Haskell.TH
import Test.QuickCheck
-- import Language.Haskell.TH.Syntax
-- | Test a property with several types.
--
-- The first argument is the name of a @Testable@ function
--
-- The second argument is expected to be a type synonym of kind [k] --
-- the variants of type arguments to apply to a fun
--
-- Kind @k@ here should match the kind of type variable of the testable function,
-- (or a promoted n-tuple or a promoted list of such variables.)
testWithTypes :: Name -- ^ Function with a few type variables
-> Name -- ^ Name of a type with kind [k]
-> ExpQ
testWithTypes fName tyNames = do
typeLists <- getTypes tyNames
let props = (\ts -> mkProp ts $ foldl AppTypeE (VarE fName) ts) <$> typeLists
[e| conjoin $(listE props) |]
where
mkProp :: [Type] -> Exp -> ExpQ
mkProp ts ex
= let qe :: ExpQ
qe = pure ex
qs :: ExpQ
qs = litE . stringL $ unlines ["Tested with types:", pprint ts]
in [e| property ( counterexample $qs $qe ) |]
-- | Assuming the argument is a type synonym for a list of types, unpack it.
getTypes :: Name -> Q [[Type]]
getTypes tyNames =
reify tyNames >>= unpackTyConI >>= unpackFamList >>= traverse unpackAppType
where
unpackTyConI :: Info -> Q Type
unpackTyConI (TyConI (TySynD _ [] t)) = pure t
unpackTyConI i = error $
"getTypes/unpackTyConI:"
++ "Expected a type synonym with no arguments, but got something else "
++ show i
unpackFamList :: Type -> Q [Type]
unpackFamList t
| Just ts <- unpackPList t = pure ts
| otherwise = error $
"getTypes/unpackFamList:"
++ "Expected a promoted (type) list inside the type synonym, but got "
++ show t
unpackAppType :: Type -> Q [Type]
unpackAppType t
| Just ts <- unpackPList t = pure ts
| Just ts <- unpackPTuple t = pure ts
| otherwise = pure [t]
unpackPTuple :: Type -> Maybe [Type]
unpackPTuple (PromotedTupleT _) = pure []
unpackPTuple (AppT t r) = (++ [r]) <$> unpackPTuple t
unpackPTuple (ParensT t) = unpackPTuple t
unpackPTuple (SigT t _) = unpackPTuple t
unpackPTuple _ = Nothing
unpackPList :: Type -> Maybe [Type]
unpackPList PromotedNilT = pure []
unpackPList (AppT (AppT PromotedConsT r) t) = (r:) <$> unpackPList t
unpackPList (ParensT t) = unpackPList t
unpackPList (SigT t _) = unpackPList t
unpackPList _ = Nothing
-- | Make several properties by applying types
propWithTypes :: Name -- ^ Function with a few type variables
-> [[Name]] -- ^ All specific types to test it on.
-> DecsQ
propWithTypes fName tyNames
= fmap join . sequence
$ zipWith3 mkDecl funNames funTypes appliedExpressions
where
appliedExpressions :: [ExpQ]
appliedExpressions
= foldl (\e -> appTypeE e . conT) (varE fName) <$> tyNames
funNames :: [Q Name]
funNames
= newName . foldl (\n -> (n ++) . nameBase) ("prop_" ++ nameBase fName) <$> tyNames
funTypes :: [TypeQ]
funTypes
= (\ns -> fmap (subtTyVars (map ConT ns)) ) <$> tyNames <*> [infoType <$> reify fName]
mkDecl :: Q Name -> Q Type -> Q Exp -> DecsQ
mkDecl qn qt qe = do
n <- qn
sequence
[ sigD n qt
, funD n [(\e -> Clause [] (NormalB e) []) <$> qe]
]
-- | Get a type of something at the term level (e.g. function, var, constructor).
infoType :: Info -> Type
infoType (ClassOpI _ t _) = t
infoType (DataConI _ t _) = t
infoType (VarI _ t _) = t
infoType _ = undefined
-- | Substitute at most as many type variables as given types in the list.
--
-- For every type in a list @ts@, the function looks for the first occurrence
-- of any type variable and substitutes all its occurrences in the target type.
--
-- Returns the original type if it has no type variables.
subtTyVars :: [Type] -> Type -> Type
subtTyVars = flip (foldl f)
where
f t x
| Just v <- findFirstTyVar t
= substTyVar v x t
| otherwise = t
-- | Substitute all occurrences of a type variable
substTyVar :: Name -- ^ TyVar name
-> Type -- ^ Substitution
-> Type -- ^ Original type
-> Type
substTyVar x y (ForallT vs ctx t)
= ForallT (remVar vs) (ctx >>= substOrRemove) (substTyVar x y t)
where
#if __GLASGOW_HASKELL__ >= 900
thisVar :: TyVarBndr a -> Bool
thisVar (PlainTV n _) = n == x
thisVar (KindedTV n _ _) = n == x
remVar :: [TyVarBndr a] -> [TyVarBndr a]
#else
thisVar :: TyVarBndr -> Bool
thisVar (PlainTV n) = n == x
thisVar (KindedTV n _) = n == x
remVar :: [TyVarBndr] -> [TyVarBndr]
#endif
remVar = filter (not . thisVar)
substOrRemove p
= let p' = substTyVar x y p
in if findFirstTyVar p' == Nothing then [] else [p]
substTyVar x y (AppT t1 t2) = AppT (substTyVar x y t1) (substTyVar x y t2)
substTyVar x y (SigT t1 t2) = SigT (substTyVar x y t1) (substTyVar x y t2)
substTyVar x y v@(VarT n) = if n == x then y else v
substTyVar x y (InfixT t1 n t2) = InfixT (substTyVar x y t1) n (substTyVar x y t2)
substTyVar x y (UInfixT t1 n t2) = UInfixT (substTyVar x y t1) n (substTyVar x y t2)
substTyVar x y (ParensT t) = ParensT (substTyVar x y t)
substTyVar _ _ t = t
findFirstTyVar :: Type -> Maybe Name
#if __GLASGOW_HASKELL__ >= 900
findFirstTyVar (ForallT (PlainTV n _:_) _ _) = Just n
findFirstTyVar (ForallT (KindedTV n _ _:_) _ _) = Just n
#else
findFirstTyVar (ForallT (PlainTV n:_) _ _) = Just n
findFirstTyVar (ForallT (KindedTV n _:_) _ _) = Just n
#endif
findFirstTyVar (ForallT [] ctx t)
= getFirst (foldMap (First . findFirstTyVar) ctx) <|> findFirstTyVar t
findFirstTyVar (AppT t1 t2) = findFirstTyVar t1 <|> findFirstTyVar t2
findFirstTyVar (SigT t1 k1) = findFirstTyVar t1 <|> findFirstTyVar k1
findFirstTyVar (VarT n) = Just n
findFirstTyVar (InfixT t1 _ t2) = findFirstTyVar t1 <|> findFirstTyVar t2
findFirstTyVar (UInfixT t1 _ t2) = findFirstTyVar t1 <|> findFirstTyVar t2
findFirstTyVar (ParensT t) = findFirstTyVar t
findFirstTyVar _ = Nothing