{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE CPP #-} {-# LANGUAGE TemplateHaskell #-} module Data.Acid.TemplateHaskellSpec where import Test.Hspec hiding (context) import Data.SafeCopy (SafeCopy) import Data.Typeable (Typeable) import Control.DeepSeq (force) import Control.Exception (evaluate) import Language.Haskell.TH import Language.Haskell.TH.Quote import Control.Monad.Reader import Control.Monad.State import Data.Acid import Data.Acid.TemplateHaskell spec :: Spec spec = do let name = mkName "foo" nameT = ConT name upperName = mkName "Foo" upperNameT = ConT upperName describe "makeEventInstance" $ do it "works with monomorphic types" $ do eventType <- runQ [t| Int -> Query Char () |] makeEventInstance name eventType `quoteShouldBe` [d| instance QueryEvent $(return upperNameT) |] it "requires instances on polymorphic types" $ do let a = VarT (mkName "a") a' = return a eventType <- runQ [t| (Ord $(a')) => $(a') -> Update Char $(a') |] makeEventInstance name eventType `quoteShouldBe` [d| instance (Ord $(a')) => UpdateEvent $(return upperNameT) |] describe "analyseType" $ do it "can work with the Query type" $ do typ <- runQ [t| Int -> Query String Char |] analyseType name typ `shouldBe` TypeAnalysis { tyvars = [] , context = [] , argumentTypes = [ConT ''Int] , stateType = ConT ''String , resultType = ConT ''Char , isUpdate = False } it "can work with the Update type" $ do typ <- runQ [t| Int -> Update String Char |] analyseType name typ `shouldBe` TypeAnalysis { tyvars = [] , context = [] , argumentTypes = [ConT ''Int] , stateType = ConT ''String , resultType = ConT ''Char , isUpdate = True } it "can work with MonadReader" $ do typ <- runQ [t| forall m. (MonadReader Int m) => Int -> m () |] analyseType name typ `shouldBe` TypeAnalysis { tyvars = [] , context = [] , argumentTypes = [ConT ''Int] , stateType = ConT ''Int , resultType = TupleT 0 , isUpdate = False } it "can work with MonadState" $ do typ <- runQ [t| forall m. (MonadState Int m) => Int -> m () |] analyseType name typ `shouldBe` TypeAnalysis { tyvars = [] , context = [] , argumentTypes = [ConT ''Int] , stateType = ConT ''Int , resultType = TupleT 0 , isUpdate = True } it "can work with many type variables (note that eventCxts later rejects this)" $ do let m = mkName "m" typ <- runQ [t| (MonadReader Int $(varT m)) => Int -> Query Int ($(varT m) ()) |] analyseType name typ `shouldBe` TypeAnalysis { tyvars = [] , context = #if MIN_VERSION_template_haskell(2,10,0) [ ConT ''MonadReader `AppT` ConT ''Int `AppT` VarT m ] #else [ ClassP ''MonadReader [ConT ''Int, VarT m] ] #endif , argumentTypes = [ConT ''Int] , stateType = ConT ''Int , resultType = VarT m `AppT` TupleT 0 , isUpdate = False } describe "eventCxts" $ do let binders = [] stateType = ConT ''Char it "rejects types with constrainted type variables unknown to state" $ do let predicate eventType = evaluate . force . map show $ eventCxts stateType binders name eventType eventType <- runQ [t| forall a. (Ord a) => Int -> Query Char a |] predicate eventType `shouldThrow` anyErrorCall it "accepts types with unconstrained type variables" $ do eventType <- runQ [t| forall a. Int -> Query Char a |] eventCxts stateType binders name eventType `shouldBe` [] let x = mkName "x" it "accepts constrained type variables in the state" $ do let binders :: [TyVarBndrUnit] #if MIN_VERSION_template_haskell(2,17,0) binders = [PlainTV (mkName "x") ()] #else binders = [PlainTV (mkName "x")] #endif stateType = ConT ''Maybe `AppT` VarT x eventType <- runQ [t| forall a. (Ord a) => Int -> Query (Maybe a) Int|] eventCxts stateType binders name eventType `shouldBe` #if MIN_VERSION_template_haskell(2,10,0) [ConT ''Ord `AppT` VarT x] #else [ClassP ''Ord [VarT x]] #endif it "can rename a polymorphic state" $ do eventType <- runQ [t| forall r m. (MonadReader r m, Ord r) => Int -> m Char |] eventCxts stateType binders name eventType `shouldBe` #if MIN_VERSION_template_haskell(2,10,0) [ConT ''Ord `AppT` ConT ''Char] #else [ClassP ''Ord [ConT ''Char]] #endif quoteShouldBe :: (Eq a, Show a) => Q a -> Q [a] -> Expectation quoteShouldBe qa qb = do actual <- runQ qa [expected] <- runQ qb actual `shouldBe` expected