{-# 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 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 = [ ConT ''MonadReader `AppT` ConT ''Int `AppT` VarT m ] , 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` [ConT ''Ord `AppT` VarT x] 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` [ConT ''Ord `AppT` ConT ''Char] quoteShouldBe :: (Eq a, Show a) => Q a -> Q [a] -> Expectation quoteShouldBe qa qb = do actual <- runQ qa [expected] <- runQ qb actual `shouldBe` expected