diff --git a/Data/Primitive/Array.hs b/Data/Primitive/Array.hs index 8b5f42ba..428e30cb 100644 --- a/Data/Primitive/Array.hs +++ b/Data/Primitive/Array.hs @@ -63,6 +63,10 @@ import Data.Functor.Identity import Text.ParserCombinators.ReadP +#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,4,0) +import Data.Functor.Classes (Eq1(..),Ord1(..),Show1(..),Read1(..)) +#endif + -- | Boxed arrays data Array a = Array { array# :: Array# a } @@ -288,26 +292,50 @@ createArray n x f = runST $ do die :: String -> String -> a die fun problem = error $ "Data.Primitive.Array." ++ fun ++ ": " ++ problem +arrayLiftEq :: (a -> b -> Bool) -> Array a -> Array b -> Bool +arrayLiftEq p a1 a2 = sizeofArray a1 == sizeofArray a2 && loop (sizeofArray a1 - 1) + where loop i | i < 0 = True + | (# x1 #) <- indexArray## a1 i + , (# x2 #) <- indexArray## a2 i + , otherwise = p x1 x2 && loop (i-1) + instance Eq a => Eq (Array a) where - a1 == a2 = sizeofArray a1 == sizeofArray a2 && loop (sizeofArray a1 - 1) - where loop i | i < 0 = True - | (# x1 #) <- indexArray## a1 i - , (# x2 #) <- indexArray## a2 i - = x1 == x2 && loop (i-1) + a1 == a2 = arrayLiftEq (==) a1 a2 + +#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,4,0) +instance Eq1 Array where +#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,5,0) + liftEq = arrayLiftEq +#else + eq1 = arrayLiftEq (==) +#endif +#endif instance Eq (MutableArray s a) where ma1 == ma2 = isTrue# (sameMutableArray# (marray# ma1) (marray# ma2)) +arrayLiftCompare :: (a -> b -> Ordering) -> Array a -> Array b -> Ordering +arrayLiftCompare elemCompare a1 a2 = loop 0 + where + mn = sizeofArray a1 `min` sizeofArray a2 + loop i + | i < mn + , (# x1 #) <- indexArray## a1 i + , (# x2 #) <- indexArray## a2 i + = elemCompare x1 x2 `mappend` loop (i+1) + | otherwise = compare (sizeofArray a1) (sizeofArray a2) + instance Ord a => Ord (Array a) where - compare a1 a2 = loop 0 - where - mn = sizeofArray a1 `min` sizeofArray a2 - loop i - | i < mn - , (# x1 #) <- indexArray## a1 i - , (# x2 #) <- indexArray## a2 i - = compare x1 x2 `mappend` loop (i+1) - | otherwise = compare (sizeofArray a1) (sizeofArray a2) + compare a1 a2 = arrayLiftCompare compare a1 a2 + +#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,4,0) +instance Ord1 Array where +#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,5,0) + liftCompare = arrayLiftCompare +#else + compare1 = arrayLiftCompare compare +#endif +#endif instance Foldable Array where -- Note: we perform the array lookups eagerly so we won't @@ -347,7 +375,7 @@ instance Foldable Array where go i = case indexArray## ary i of (# x #) | i == 0 -> x - | otherwise -> f x (go (i - 1)) + | otherwise -> f (go (i - 1)) x in if sz < 0 then die "foldl1" "empty array" else go sz @@ -478,26 +506,35 @@ unsafeTraverseArray f = \ !ary -> go 0 mary {-# INLINE unsafeTraverseArray #-} +arrayFromListN :: Int -> [a] -> Array a +arrayFromListN n l = runST $ do + sma <- newArray n (die "fromListN" "uninitialized element") + let go !ix [] = if ix == n + then return () + else die "fromListN" "list length less than specified size" + go !ix (x : xs) = if ix < n + then do + writeArray sma ix x + go (ix+1) xs + else die "fromListN" "list length greater than specified size" + go 0 l + unsafeFreezeArray sma + +arrayFromList :: [a] -> Array a +arrayFromList l = arrayFromListN (length l) l + #if MIN_VERSION_base(4,7,0) instance Exts.IsList (Array a) where type Item (Array a) = a - fromListN n l = - createArray n (die "fromListN" "mismatched size and list") $ \mi -> - let go i (x:xs) = writeArray mi i x >> go (i+1) xs - go _ [ ] = return () - in go 0 l - fromList l = Exts.fromListN (length l) l + fromListN = arrayFromListN + fromList = arrayFromList toList = toList #else fromListN :: Int -> [a] -> Array a -fromListN n l = - createArray n (die "fromListN" "mismatched size and list") $ \mi -> - let go i (x:xs) = writeArray mi i x >> go (i+1) xs - go _ [ ] = return () - in go 0 l +fromListN = arrayFromListN fromList :: [a] -> Array a -fromList l = fromListN (length l) l +fromList = arrayFromList #endif instance Functor Array where @@ -526,6 +563,7 @@ instance Applicative Array where do x <- indexArrayM a j writeArray mb (off + j) (f x) + go2 off f (j + 1) go1 0 unsafeFreezeArray mb where szab = sizeofArray ab ; sza = sizeofArray a @@ -649,19 +687,48 @@ instance Monoid (Array a) where in go 0 l where sz = sum . fmap sizeofArray $ l +arrayLiftShowsPrec :: (Int -> a -> ShowS) -> ([a] -> ShowS) -> Int -> Array a -> ShowS +arrayLiftShowsPrec elemShowsPrec elemListShowsPrec p a = showParen (p > 10) $ + showString "fromListN " . shows (sizeofArray a) . showString " " + . listLiftShowsPrec elemShowsPrec elemListShowsPrec 11 (toList a) + +-- this need to be included for older ghcs +listLiftShowsPrec :: (Int -> a -> ShowS) -> ([a] -> ShowS) -> Int -> [a] -> ShowS +listLiftShowsPrec _ sl _ = sl + instance Show a => Show (Array a) where - showsPrec p a = showParen (p > 10) $ - showString "fromListN " . shows (sizeofArray a) . showString " " - . shows (toList a) + showsPrec p a = arrayLiftShowsPrec showsPrec showList p a + +#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,4,0) +instance Show1 Array where +#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,5,0) + liftShowsPrec = arrayLiftShowsPrec +#else + showsPrec1 = arrayLiftShowsPrec showsPrec showList +#endif +#endif + +arrayLiftReadsPrec :: (Int -> ReadS a) -> ReadS [a] -> Int -> ReadS (Array a) +arrayLiftReadsPrec _ listReadsPrec p = readParen (p > 10) . readP_to_S $ do + () <$ string "fromListN" + skipSpaces + n <- readS_to_P reads + skipSpaces + l <- readS_to_P listReadsPrec + return $ arrayFromListN n l instance Read a => Read (Array a) where - readsPrec p = readParen (p > 10) . readP_to_S $ do - () <$ string "fromListN" - skipSpaces - n <- readS_to_P reads - skipSpaces - l <- readS_to_P reads - return $ fromListN n l + readsPrec = arrayLiftReadsPrec readsPrec readList + +#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,4,0) +instance Read1 Array where +#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,5,0) + liftReadsPrec = arrayLiftReadsPrec +#else + readsPrec1 = arrayLiftReadsPrec readsPrec readList +#endif +#endif + arrayDataType :: DataType arrayDataType = mkDataType "Data.Primitive.Array.Array" [fromListConstr] diff --git a/Data/Primitive/ByteArray.hs b/Data/Primitive/ByteArray.hs index a9576bce..a1412d11 100644 --- a/Data/Primitive/ByteArray.hs +++ b/Data/Primitive/ByteArray.hs @@ -44,7 +44,6 @@ module Data.Primitive.ByteArray ( import Control.Monad.Primitive import Control.Monad.ST -import Control.Monad ( zipWithM_ ) import Data.Primitive.Types import Foreign.C.Types @@ -181,12 +180,20 @@ foldrByteArray f z arr = go 0 go i | sizeofByteArray arr > i * sz = f (indexByteArray arr i) (go (i+1)) | otherwise = z - sz = sizeofByteArray arr + sz = sizeOf (undefined :: a) fromListN :: Prim a => Int -> [a] -> ByteArray -fromListN n xs = runST $ do - marr <- newByteArray (n * sizeOf (head xs)) - zipWithM_ (writeByteArray marr) [0..n] xs +fromListN n ys = runST $ do + marr <- newByteArray (n * sizeOf (head ys)) + let go !ix [] = if ix == n + then return () + else die "fromListN" "list length less than specified size" + go !ix (x : xs) = if ix < n + then do + writeByteArray marr ix x + go (ix + 1) xs + else die "fromListN" "list length greater than specified size" + go 0 ys unsafeFreezeByteArray marr #if __GLASGOW_HASKELL__ >= 702 @@ -444,3 +451,7 @@ instance Exts.IsList ByteArray where fromList xs = fromListN (length xs) xs fromListN = fromListN #endif + +die :: String -> String -> a +die fun problem = error $ "Data.Primitive.ByteArray." ++ fun ++ ": " ++ problem + diff --git a/Data/Primitive/SmallArray.hs b/Data/Primitive/SmallArray.hs index a4ee966b..a085180e 100644 --- a/Data/Primitive/SmallArray.hs +++ b/Data/Primitive/SmallArray.hs @@ -54,6 +54,8 @@ module Data.Primitive.SmallArray , unsafeThawSmallArray , sizeofSmallArray , sizeofSmallMutableArray + , smallArrayFromList + , smallArrayFromListN , unsafeTraverseSmallArray ) where @@ -82,13 +84,16 @@ import Data.Monoid #if MIN_VERSION_base(4,9,0) import qualified Data.Semigroup as Sem #endif -import Text.ParserCombinators.ReadPrec -import Text.Read -import Text.Read.Lex +import Text.ParserCombinators.ReadP #if !(HAVE_SMALL_ARRAY) import Data.Primitive.Array import Data.Traversable +import qualified Data.Primitive.Array as Array +#endif + +#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,4,0) +import Data.Functor.Classes (Eq1(..),Ord1(..),Show1(..),Read1(..)) #endif #if HAVE_SMALL_ARRAY @@ -111,6 +116,12 @@ newtype SmallArray a = SmallArray (Array a) deriving , MonadFix , Monoid , Typeable +#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,4,0) + , Eq1 + , Ord1 + , Show1 + , Read1 +#endif ) #if MIN_VERSION_base(4,7,0) @@ -443,31 +454,54 @@ infixl 1 ? noOp :: a -> ST s () noOp = const $ pure () +smallArrayLiftEq :: (a -> b -> Bool) -> SmallArray a -> SmallArray b -> Bool +smallArrayLiftEq p sa1 sa2 = length sa1 == length sa2 && loop (length sa1 - 1) + where + loop i + | i < 0 + = True + | (# x #) <- indexSmallArray## sa1 i + , (# y #) <- indexSmallArray## sa2 i + = p x y && loop (i-1) + +#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,4,0) +instance Eq1 SmallArray where +#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,5,0) + liftEq = smallArrayLiftEq +#else + eq1 = smallArrayLiftEq (==) +#endif +#endif + instance Eq a => Eq (SmallArray a) where - sa1 == sa2 = length sa1 == length sa2 && loop (length sa1 - 1) - where - loop i - | i < 0 - = True - | (# x #) <- indexSmallArray## sa1 i - , (# y #) <- indexSmallArray## sa2 i - = x == y && loop (i-1) + sa1 == sa2 = smallArrayLiftEq (==) sa1 sa2 instance Eq (SmallMutableArray s a) where SmallMutableArray sma1# == SmallMutableArray sma2# = isTrue# (sameSmallMutableArray# sma1# sma2#) -instance Ord a => Ord (SmallArray a) where - compare a1 a2 = loop 0 - where - mn = length a1 `min` length a2 - loop i - | i < mn - , (# x1 #) <- indexSmallArray## a1 i - , (# x2 #) <- indexSmallArray## a2 i - = compare x1 x2 `mappend` loop (i+1) - | otherwise = compare (length a1) (length a2) +smallArrayLiftCompare :: (a -> b -> Ordering) -> SmallArray a -> SmallArray b -> Ordering +smallArrayLiftCompare elemCompare a1 a2 = loop 0 + where + mn = length a1 `min` length a2 + loop i + | i < mn + , (# x1 #) <- indexSmallArray## a1 i + , (# x2 #) <- indexSmallArray## a2 i + = elemCompare x1 x2 `mappend` loop (i+1) + | otherwise = compare (length a1) (length a2) + +#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,4,0) +instance Ord1 SmallArray where +#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,5,0) + liftCompare = smallArrayLiftCompare +#else + compare1 = smallArrayLiftCompare compare +#endif +#endif +instance Ord a => Ord (SmallArray a) where + compare sa1 sa2 = smallArrayLiftCompare compare sa1 sa2 instance Foldable SmallArray where -- Note: we perform the array lookups eagerly so we won't @@ -507,7 +541,7 @@ instance Foldable SmallArray where go i = case indexSmallArray## ary i of (# x #) | i == 0 -> x - | otherwise -> f x (go (i - 1)) + | otherwise -> f (go (i - 1)) x in if sz < 0 then die "foldl1" "Empty SmallArray" else go sz @@ -632,17 +666,21 @@ instance Applicative SmallArray where in go 0 where sza = sizeofSmallArray a ; szb = sizeofSmallArray b - sf <*> sx = createSmallArray (lf*lx) (die "<*>" "impossible") $ \smb -> - fix ? 0 $ \outer i -> when (i < lf) $ do - f <- indexSmallArrayM sf i - fix ? 0 $ \inner j -> - when (j < lx) $ do - x <- indexSmallArrayM sx j - writeSmallArray smb (lf*i + j) (f x) - *> inner (j+1) - outer $ i+1 - where - lf = length sf ; lx = length sx + ab <*> a = runST $ do + mb <- newSmallArray (szab*sza) $ die "<*>" "impossible" + let go1 i = when (i < szab) $ + do + f <- indexSmallArrayM ab i + go2 (i*sza) f 0 + go1 (i+1) + go2 off f j = when (j < sza) $ + do + x <- indexSmallArrayM a j + writeSmallArray mb (off + j) (f x) + go2 off f (j + 1) + go1 0 + unsafeFreezeSmallArray mb + where szab = sizeofSmallArray ab ; sza = sizeofSmallArray a instance Alternative SmallArray where empty = emptySmallArray @@ -757,25 +795,53 @@ instance Monoid (SmallArray a) where instance IsList (SmallArray a) where type Item (SmallArray a) = a - fromListN n l = - createSmallArray n (die "fromListN" "mismatched size and list") $ \sma -> - fix ? 0 ? l $ \go i li -> case li of - [] -> pure () - x:xs -> writeSmallArray sma i x *> go (i+1) xs - fromList l = fromListN (length l) l + fromListN = smallArrayFromListN + fromList = smallArrayFromList toList = Foldable.toList +smallArrayLiftShowsPrec :: (Int -> a -> ShowS) -> ([a] -> ShowS) -> Int -> SmallArray a -> ShowS +smallArrayLiftShowsPrec elemShowsPrec elemListShowsPrec p sa = showParen (p > 10) $ + showString "fromListN " . shows (length sa) . showString " " + . listLiftShowsPrec elemShowsPrec elemListShowsPrec 11 (toList sa) + +-- this need to be included for older ghcs +listLiftShowsPrec :: (Int -> a -> ShowS) -> ([a] -> ShowS) -> Int -> [a] -> ShowS +listLiftShowsPrec _ sl _ = sl + instance Show a => Show (SmallArray a) where - showsPrec p sa = showParen (p > 10) $ - showString "fromListN " . shows (length sa) . showString " " - . shows (toList sa) + showsPrec p sa = smallArrayLiftShowsPrec showsPrec showList p sa + +#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,4,0) +instance Show1 SmallArray where +#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,5,0) + liftShowsPrec = smallArrayLiftShowsPrec +#else + showsPrec1 = smallArrayLiftShowsPrec showsPrec showList +#endif +#endif + +smallArrayLiftReadsPrec :: (Int -> ReadS a) -> ReadS [a] -> Int -> ReadS (SmallArray a) +smallArrayLiftReadsPrec _ listReadsPrec p = readParen (p > 10) . readP_to_S $ do + () <$ string "fromListN" + skipSpaces + n <- readS_to_P reads + skipSpaces + l <- readS_to_P listReadsPrec + return $ smallArrayFromListN n l instance Read a => Read (SmallArray a) where - readPrec = parens . prec 10 $ do - Symbol "fromListN" <- lexP - Number nu <- lexP - n <- maybe empty pure $ numberToInteger nu - fromListN (fromIntegral n) <$> readPrec + readsPrec = smallArrayLiftReadsPrec readsPrec readList + +#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,4,0) +instance Read1 SmallArray where +#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,5,0) + liftReadsPrec = smallArrayLiftReadsPrec +#else + readsPrec1 = smallArrayLiftReadsPrec readsPrec readList +#endif +#endif + + smallArrayDataType :: DataType smallArrayDataType = @@ -797,3 +863,28 @@ instance (Typeable s, Typeable a) => Data (SmallMutableArray s a) where gunfold _ _ = die "gunfold" "SmallMutableArray" dataTypeOf _ = mkNoRepType "Data.Primitive.SmallArray.SmallMutableArray" #endif + +-- | Create a 'SmallArray' from a list of a known length. If the length +-- of the list does not match the given length, this throws an exception. +smallArrayFromListN :: Int -> [a] -> SmallArray a +#if HAVE_SMALL_ARRAY +smallArrayFromListN n l = runST $ do + sma <- newSmallArray n (die "smallArrayFromListN" "uninitialized element") + let go !ix [] = if ix == n + then return () + else die "smallArrayFromListN" "list length less than specified size" + go !ix (x : xs) = if ix < n + then do + writeSmallArray sma ix x + go (ix+1) xs + else die "smallArrayFromListN" "list length greater than specified size" + go 0 l + unsafeFreezeSmallArray sma +#else +smallArrayFromListN n l = SmallArray (Array.fromListN n l) +#endif + +-- | Create a 'SmallArray' from a list. +smallArrayFromList :: [a] -> SmallArray a +smallArrayFromList l = smallArrayFromListN (length l) l + diff --git a/cabal.project b/cabal.project index a7f0fed1..30058bb3 100644 --- a/cabal.project +++ b/cabal.project @@ -1,2 +1,5 @@ packages: . ./test +package quickcheck-classes + flags: -aeson -semigroupoids + diff --git a/changelog.md b/changelog.md index 7ecfaec6..43dd5db6 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,17 @@ +## Changes in version 0.6.4.0 + + * Add `Eq1`, `Ord1`, `Show1`, and `Read1` instances for `Array` and + `SmallArray`. + + * Improve the test suite. This includes having property tests for + typeclasses from `base` such as `Eq`, `Ord`, `Functor`, `Applicative`, + `Monad`, `IsList`, `Monoid`, `Foldable`, and `Traversable`. + + * Fix the broken `IsList` instance for `ByteArray`. + + * Fix the broken `Functor`, `Applicative`, and `Monad` instances for + `Array` and `SmallArray`. + ## Changes in version 0.6.3.0 * Add `PrimMonad` instances for `ContT`, `AccumT`, and `SelectT` from diff --git a/test/README.md b/test/README.md new file mode 100644 index 00000000..059cfca4 --- /dev/null +++ b/test/README.md @@ -0,0 +1,25 @@ +Test Suite +======================= + +The test suite for `primitive` cannot be included in the same package +as `primitive` itself. The test suite depends on `QuickCheck`, which +transitively depends on `primitive`. To break up this dependency cycle, +the test suite lives here in its own unpublished package. + +To accelerates builds of the test suite, it is recommended to use +`cabal new-build`, which will use the pass the flags specified in +the `cabal.project` file to build `quickcheck-classes`. From the +root directory of `primitive`, run the following command to build +the test suite: + + cabal new-build test --enable-tests + +This test suite is tested by travis. Although `primitive` supports +versions of transformers all the way back to `transformers-0.2.0.0`, +the test suite cannot be build with versions of transformers older than +`transformers-0.3.0.0`. As far as test coverage goes, this should not +be a problem since there is no CPP that treats these two versions of +transformers differently. Travis tests `transformers-0.3.0.0` with +its GHC 7.8.4 build, which provides high confidence that `primitive` +works with every version of transformers that it claims to. + diff --git a/test/main.hs b/test/main.hs index 433d2553..e453088c 100644 --- a/test/main.hs +++ b/test/main.hs @@ -1,6 +1,12 @@ -{-# LANGUAGE CPP, MagicHash, UnboxedTuples #-} +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE CPP #-} +{-# LANGUAGE KindSignatures #-} +{-# LANGUAGE MagicHash #-} +{-# LANGUAGE UnboxedTuples #-} +import Control.Applicative import Control.Monad +import Control.Monad.Fix (fix) import Control.Monad.Primitive import Control.Monad.ST import Data.Monoid @@ -8,7 +14,9 @@ import Data.Primitive import Data.Primitive.Array import Data.Primitive.ByteArray import Data.Primitive.Types +import Data.Primitive.SmallArray import Data.Word +import Data.Proxy (Proxy(..)) import GHC.Int import GHC.IO import GHC.Prim @@ -16,14 +24,65 @@ import GHC.Prim import Data.Semigroup (stimes) #endif --- Since we only have two test cases right now, I'm going to avoid the --- issue of choosing a test framework for the moment. This also keeps the --- package as a whole light on dependencies. +import Test.Tasty (defaultMain,testGroup,TestTree) +import Test.QuickCheck (Arbitrary,Arbitrary1,Gen) +import qualified Test.Tasty.QuickCheck as TQC +import qualified Test.QuickCheck as QC +import qualified Test.QuickCheck.Classes as QCC +import qualified Data.List as L main :: IO () main = do - testArray - testByteArray + testArray + testByteArray + defaultMain $ testGroup "properties" + [ testGroup "Array" + [ lawsToTest (QCC.eqLaws (Proxy :: Proxy (Array Int))) + , lawsToTest (QCC.ordLaws (Proxy :: Proxy (Array Int))) + , lawsToTest (QCC.monoidLaws (Proxy :: Proxy (Array Int))) + , lawsToTest (QCC.showReadLaws (Proxy :: Proxy (Array Int))) +#if MIN_VERSION_base(4,7,0) + , lawsToTest (QCC.isListLaws (Proxy :: Proxy (Array Int))) +#endif +#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,4,0) + , lawsToTest (QCC.functorLaws (Proxy1 :: Proxy1 Array)) + , lawsToTest (QCC.applicativeLaws (Proxy1 :: Proxy1 Array)) + , lawsToTest (QCC.monadLaws (Proxy1 :: Proxy1 Array)) + , lawsToTest (QCC.foldableLaws (Proxy1 :: Proxy1 Array)) + , lawsToTest (QCC.traversableLaws (Proxy1 :: Proxy1 Array)) +#endif + ] + , testGroup "SmallArray" + [ lawsToTest (QCC.eqLaws (Proxy :: Proxy (SmallArray Int))) + , lawsToTest (QCC.ordLaws (Proxy :: Proxy (SmallArray Int))) + , lawsToTest (QCC.monoidLaws (Proxy :: Proxy (SmallArray Int))) + , lawsToTest (QCC.showReadLaws (Proxy :: Proxy (Array Int))) +#if MIN_VERSION_base(4,7,0) + , lawsToTest (QCC.isListLaws (Proxy :: Proxy (SmallArray Int))) +#endif +#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,4,0) + , lawsToTest (QCC.functorLaws (Proxy1 :: Proxy1 SmallArray)) + , lawsToTest (QCC.applicativeLaws (Proxy1 :: Proxy1 SmallArray)) + , lawsToTest (QCC.monadLaws (Proxy1 :: Proxy1 SmallArray)) + , lawsToTest (QCC.foldableLaws (Proxy1 :: Proxy1 SmallArray)) + , lawsToTest (QCC.traversableLaws (Proxy1 :: Proxy1 SmallArray)) +#endif + ] + , testGroup "ByteArray" + [ lawsToTest (QCC.eqLaws (Proxy :: Proxy ByteArray)) + , lawsToTest (QCC.ordLaws (Proxy :: Proxy ByteArray)) + , lawsToTest (QCC.showReadLaws (Proxy :: Proxy (Array Int))) +#if MIN_VERSION_base(4,7,0) + , lawsToTest (QCC.isListLaws (Proxy :: Proxy ByteArray)) +#endif + ] + ] + +-- on GHC 7.4, Proxy is not polykinded, so we need this instead. +data Proxy1 (f :: * -> *) = Proxy1 + +lawsToTest :: QCC.Laws -> TestTree +lawsToTest (QCC.Laws name pairs) = testGroup name (map (uncurry TQC.testProperty) pairs) testArray :: IO () testArray = do @@ -69,3 +128,31 @@ mkByteArray xs = runST $ do marr <- newByteArray (length xs * sizeOf (head xs)) sequence $ zipWith (writeByteArray marr) [0..] xs unsafeFreezeByteArray marr + +instance Arbitrary1 Array where + liftArbitrary elemGen = fmap fromList (QC.liftArbitrary elemGen) + +instance Arbitrary a => Arbitrary (Array a) where + arbitrary = fmap fromList QC.arbitrary + +instance Arbitrary1 SmallArray where + liftArbitrary elemGen = fmap smallArrayFromList (QC.liftArbitrary elemGen) + +instance Arbitrary a => Arbitrary (SmallArray a) where + arbitrary = fmap smallArrayFromList QC.arbitrary + +instance Arbitrary ByteArray where + arbitrary = do + xs <- QC.arbitrary :: Gen [Word8] + return $ runST $ do + a <- newByteArray (L.length xs) + iforM_ xs $ \ix x -> do + writeByteArray a ix x + unsafeFreezeByteArray a + + +iforM_ :: Monad m => [a] -> (Int -> a -> m b) -> m () +iforM_ xs0 f = go 0 xs0 where + go !_ [] = return () + go !ix (x : xs) = f ix x >> go (ix + 1) xs + diff --git a/test/primitive-tests.cabal b/test/primitive-tests.cabal index d2d00ca4..91876ad1 100644 --- a/test/primitive-tests.cabal +++ b/test/primitive-tests.cabal @@ -31,6 +31,12 @@ test-suite test build-depends: base >= 4.5 && < 4.12 , ghc-prim , primitive + , QuickCheck + , tasty + , tasty-quickcheck + , tagged + , transformers >= 0.3 + , quickcheck-classes >= 0.4.4 ghc-options: -O2 source-repository head