module SubHask.TemplateHaskell.Test where import Prelude import Control.Monad import qualified Data.Map as Map import Debug.Trace import Language.Haskell.TH import GHC.Exts import SubHask.Internal.Prelude import SubHask.TemplateHaskell.Deriving -- import SubHask.Category -- import SubHask.Algebra -- | Ideally, this map would be generated automatically via template haskell. -- Due to bug , however, we must enter these manually. testMap :: Map.Map String [String] testMap = Map.fromList [ ( "Eq",[] ) , ( "MinBound",[]) , ( "Lattice",[]) , ( "Ord",[]) , ( "POrd",[]) , ( "IsMutable", []) -- comparison , ( "Eq_", [ "law_Eq_reflexive" , "law_Eq_symmetric" , "law_Eq_transitive" ] ) , ( "POrd_", [ "law_POrd_commutative" , "law_POrd_associative" , "theorem_POrd_idempotent" ]) , ("MinBound_", [ "law_MinBound_inf" ] ) , ( "Lattice_", [ "law_Lattice_infabsorption" , "law_Lattice_supabsorption" ] ) , ( "Ord_", [ "law_Ord_totality" , "law_Ord_min" , "law_Ord_max" ] ) , ("Bounded", [ "law_Bounded_sup" ] ) , ("Complemented", [ "law_Complemented_not" ] ) , ("Heyting", [ "law_Heyting_maxbound" , "law_Heyting_infleft" , "law_Heyting_infright" , "law_Heyting_distributive" ] ) , ("Boolean", [ "law_Boolean_infcomplement" , "law_Boolean_supcomplement" , "law_Boolean_infdistributivity" , "law_Boolean_supdistributivity" ]) , ( "Graded", [ "law_Graded_pred" , "law_Graded_fromEnum" ] ) , ( "Enum", [ "law_Enum_succ" , "law_Enum_toEnum" ] ) -- algebra , ( "Semigroup" , [ "law_Semigroup_associativity" , "defn_Semigroup_plusequal" ] ) , ( "Action" , [ "law_Action_compatibility" , "defn_Action_dotplusequal" ] ) , ( "Cancellative", [ "law_Cancellative_rightminus1" , "law_Cancellative_rightminus2" , "defn_Cancellative_plusequal" ]) , ( "Monoid", [ "law_Monoid_leftid" , "law_Monoid_rightid" , "defn_Monoid_isZero" ] ) , ( "Abelian", [ "law_Abelian_commutative" ] ) , ( "Group", [ "defn_Group_negateminus" , "law_Group_leftinverse" , "law_Group_rightinverse" ] ) , ("Rg", [ "law_Rg_multiplicativeAssociativity" , "law_Rg_multiplicativeCommutivity" , "law_Rg_annihilation" , "law_Rg_distributivityLeft" , "theorem_Rg_distributivityRight" , "defn_Rg_timesequal" ]) , ("Rig", [ "law_Rig_multiplicativeId" ] ) , ("Rng", []) , ("Ring", [ "defn_Ring_fromInteger" ] ) , ("Integral", [ "law_Integral_divMod" , "law_Integral_quotRem" , "law_Integral_toFromInverse" ]) , ("Module", [ "law_Module_multiplication" , "law_Module_addition" , "law_Module_action" , "law_Module_unital" , "defn_Module_dotstarequal" ] ) , ("FreeModule", [ "law_FreeModule_commutative" , "law_FreeModule_associative" , "law_FreeModule_id" , "defn_FreeModule_dotstardotequal" ] ) , ("VectorSpace", [] ) -- sizes , ( "HasScalar", [] ) , ( "Normed", [ ] ) , ( "Metric", [ "law_Metric_nonnegativity" , "law_Metric_indiscernables" , "law_Metric_symmetry" , "law_Metric_triangle" ] ) -- containers , ( "Container", [ "law_Container_preservation" ] ) , ( "Constructible", [ "law_Constructible_singleton" , "defn_Constructible_cons" , "defn_Constructible_snoc" , "defn_Constructible_fromList" , "defn_Constructible_fromListN" , "theorem_Constructible_cons" ] ) , ( "Foldable", -- [ "law_Foldable_sum" [ "theorem_Foldable_tofrom" , "defn_Foldable_foldr" , "defn_Foldable_foldr'" , "defn_Foldable_foldl" , "defn_Foldable_foldl'" -- , "defn_Foldable_foldr1" -- , "defn_Foldable_foldr1'" -- , "defn_Foldable_foldl1" -- , "defn_Foldable_foldl1'" ] ) , ( "Partitionable", [ "law_Partitionable_length" , "law_Partitionable_monoid" ] ) -- indexed containers , ( "IxConstructible", [ "law_IxConstructible_lookup" , "defn_IxConstructible_consAt" , "defn_IxConstructible_snocAt" , "defn_IxConstructible_fromIxList" ] ) , ( "IxContainer", [ "law_IxContainer_preservation" , "defn_IxContainer_bang" , "defn_IxContainer_findWithDefault" , "defn_IxContainer_hasIndex" ] ) ] -- | makes tests for all instances of a class that take no type variables mkClassTests :: Name -> Q Exp mkClassTests className = do info <- reify className typeTests <- case info of ClassI _ xs -> go xs otherwise -> error "mkClassTests called on something not a class" return $ AppE ( AppE ( VarE $ mkName "testGroup" ) ( LitE $ StringL $ nameBase className ) ) ( typeTests ) where go [] = return $ ConE $ mkName "[]" go ((InstanceD ctx (AppT _ t) _):xs) = case t of (ConT a) -> do tests <- mkSpecializedClassTest (ConT a) className next <- go xs return $ AppE ( AppE ( ConE $ mkName ":" ) ( tests ) ) ( next ) -- (AppT _ _) -> do -- let specializedType = specializeType t (ConT ''Int) -- tests <- mkSpecializedClassTest specializedType className -- next <- go xs -- return $ AppE -- ( AppE -- ( ConE $ mkName ":" ) -- ( tests ) -- ) -- ( next ) -- otherwise -> trace ("mkClassTests: skipping "++show ctx++" => "++show t) $ go xs otherwise -> go xs -- | Given a type and a class, searches "testMap" for all tests for the class; -- then specializes those tests to test on the given type mkSpecializedClassTest :: Type -- ^ type to create tests for -> Name -- ^ class to create tests for -> Q Exp mkSpecializedClassTest typeName className = case Map.lookup (nameBase className) testMap of Nothing -> error $ "mkSpecializedClassTest: no tests defined for type " ++ nameBase className Just xs -> do tests <- mkTests typeName $ map mkName xs return $ AppE ( AppE ( VarE $ mkName "testGroup" ) -- ( LitE $ StringL $ show $ ppr typeName ) ( LitE $ StringL $ nameBase className ) ) ( tests ) -- | Like "mkSpecializedClassTests", but takes a list of classes mkSpecializedClassTests :: Q Type -> [Name] -> Q Exp mkSpecializedClassTests typeNameQ xs = do typeName <- typeNameQ testnames <- liftM concat $ mapM listSuperClasses xs tests <- liftM listExp2Exp $ mapM (mkSpecializedClassTest typeName) testnames return $ AppE ( AppE ( VarE $ mkName "testGroup" ) ( LitE $ StringL $ show $ ppr typeName ) ) ( tests ) -- | replace all variables with a concrete type specializeType :: Type -- ^ type with variables -> Type -- ^ instantiate variables to this type -> Type specializeType t n = case t of VarT _ -> n AppT t1 t2 -> AppT (specializeType t1 n) (specializeType t2 n) ForallT xs ctx t -> {-ForallT xs ctx $-} specializeType t n -- ForallT xs ctx t -> ForallT xs (specializeType ctx n) $ specializeType t n x -> x specializeLaw :: Type -- ^ type to specialize the law to -> Name -- ^ law (i.e. function) that we're testing -> Q Exp specializeLaw typeName lawName = do lawInfo <- reify lawName let newType = case lawInfo of VarI _ t _ _ -> specializeType t typeName otherwise -> error "mkTest lawName not a function" return $ SigE (VarE lawName) newType -- | creates an expression of the form: -- -- > testProperty "testname" (law_Classname_testname :: typeName -> ... -> Bool) -- mkTest :: Type -- ^ type to specialize the law to -> Name -- ^ law (i.e. function) that we're testing -> Q Exp mkTest typeName lawName = do spec <- specializeLaw typeName lawName return $ AppE ( AppE ( VarE $ mkName "testProperty" ) ( LitE $ StringL $ extractTestStr lawName ) ) ( spec ) -- | Like "mkTest", but takes a list of laws and returns a list of tests mkTests :: Type -> [Name] -> Q Exp mkTests typeName xs = liftM listExp2Exp $ mapM (mkTest typeName) xs listExp2Exp :: [Exp] -> Exp listExp2Exp [] = ConE $ mkName "[]" listExp2Exp (x:xs) = AppE ( AppE ( ConE $ mkName ":" ) ( x ) ) ( listExp2Exp xs ) -- | takes a "Name" of the form -- -- > law_Class_test -- -- and returns the string -- -- > test extractTestStr :: Name -> String extractTestStr name = nameBase name -- extractTestStr name = last $ words $ map (\x -> if x=='_' then ' ' else x) $ nameBase name