generic-arbitrary-1.0.0: src/Test/QuickCheck/Arbitrary/Generic.hs
{-|
This module is a generic implementation of the 'arbitrary' method. Example
usage:
@
data Foo = Foo
{ _fooX :: X
, _fooY :: Y
} deriving (Generic)
instance Arbitrary Foo where
arbitrary = genericArbitrary
shrink = genericShrink
@
This instance can also be derived using DerivingVia language extension
@
data Foo = Foo
{ _fooX :: X
, _fooY :: Y
} deriving (Generic)
deriving (Arbitrary) via GenericArbitrary Foo
@
The generated 'arbitrary' method is equivalent to
@
Foo '<$>' arbitrary '<*>' arbitrary
@.
It can also handle a recursive types problem. Assuming a type
@
data R = R R
deriving Generic
@
there is no instance
@
instance Arbitrary R where
arbitrary = genericArbitrary
shrink = genericShrink
@
If you try to compile this you will get a type level error
> • R refers to itself in all constructors
Which means that there is no finite term for @R@ because it is recursive. But,
if you correct the definition of @R@ like this.
@
data R = R R | F
deriving Generic
@
Then it will compile. And the @arbitrary@ generated will not hang forever, because
it respects the @size@ parameter.
There is a limitation of recursion detection:
@
data R1 = R1 R2
deriving (Eq, Ord, Show, Generic)
deriving anyclass NFData
deriving Arbitrary via (GenericArbitrary R1)
data R2 = R2 R1
deriving (Eq, Ord, Show, Generic)
deriving anyclass NFData
deriving Arbitrary via (GenericArbitrary R2)
@
This code will compile and the @arbitrary@ generated will always hang. Yes,
there is a problem with mutually recursive types.
Now lets see an example of datatype with parameters
@
data A a = A a
deriving (Eq, Ord, Show)
deriving anyclass NFData
deriving (Generic)
instance (Arbitrary a) => Arbitrary (A a) where
arbitrary = genericArbitrary
shrink = genericShrink
@
It should work from first glance, but when compile it will throw an error:
@
• Could not deduce (Test.QuickCheck.Arbitrary.Generic.GArbitrary
(A a)
(GHC.Generics.D1
('GHC.Generics.MetaData "A" "ParametersTest" "main" 'False)
(GHC.Generics.C1
('GHC.Generics.MetaCons "A" 'GHC.Generics.PrefixI 'False)
(GHC.Generics.S1
('GHC.Generics.MetaSel
'Nothing
'GHC.Generics.NoSourceUnpackedness
'GHC.Generics.NoSourceStrictness
'GHC.Generics.DecidedLazy)
(GHC.Generics.Rec0 a))))
(TypesDiffer (A a) a))
arising from a use of ‘genericArbitrary’
@
Here the @TypesDiffer@ is a type familty dealing with recursive types and
helping us to eliminate inproper instances. To convince the compiller, that the
@a@ parameter is not an @A a@ we must fix the instance with additional constraint
@
instance (Arg (A a) a, Arbitrary a) => Arbitrary (A a) where
arbitrary = genericArbitrary
shrink = genericShrink
@
Now everything compiles and works as expected.
-}
module Test.QuickCheck.Arbitrary.Generic
( -- * Main
genericArbitrary
#if MIN_VERSION_QuickCheck(2, 14, 0)
, GenericArbitrary(..)
#endif
, Arg
-- * Internal
, GArbitrary
, FiniteSum
, FiniteSumElem
, Finite
, AllFieldsFinal
, TypesDiffer
, ArgumentsCount
, SumLen
-- * Reexports
, Arbitrary(..)
, genericShrink
) where
import Control.Applicative
import Data.Coerce (coerce)
import Data.Proxy
import Data.Type.Bool
import GHC.Generics as G
import GHC.TypeLits
import Test.QuickCheck as QC
#if MIN_VERSION_QuickCheck(2, 14, 0)
import Test.QuickCheck.Arbitrary (GSubterms, RecursivelyShrink)
-- | Newtype for @DerivingVia@
--
-- Usage:
--
-- @
-- data Foo = Foo
-- { _fooX :: X
-- , _fooY :: Y
-- } deriving (Generic)
-- deriving (Arbitrary) via GenericArbitrary Foo
-- @
--
-- @since 1.0.0
newtype GenericArbitrary a = GenericArbitrary { unGenericArbitrary :: a }
deriving (Show, Eq)
instance
( Generic a,
GArbitrary a (Rep a) some,
RecursivelyShrink (Rep a),
GSubterms (Rep a) a
) => Arbitrary (GenericArbitrary a) where
arbitrary = coerce (genericArbitrary :: Gen a)
shrink = coerce (genericShrink :: a -> [a])
#endif
-- | Constraint helper for types with parameters
--
-- Usage:
--
-- @
-- data A a = A a
-- deriving (Generic)
-- instance (Arg (A a) a, Arbitrary a) => Arbitrary (A a) where
-- arbitrary = genericArbitrary
-- shrink = genericShrink
-- @
--
-- @since 1.0.0
type Arg self field = (TypesDiffer self field ~ 'True)
type family TypesDiffer a b where
TypesDiffer a a = 'False
TypesDiffer a b = 'True
type family AllFieldsFinal self (a :: * -> *) :: Bool where
AllFieldsFinal self U1 = 'True
AllFieldsFinal self (a :*: b) = AllFieldsFinal self a && AllFieldsFinal self b
AllFieldsFinal self (M1 S t (K1 R field)) = TypesDiffer self field
type family Finite self (a :: * -> *) :: Bool where
Finite self U1 = 'True
Finite self (K1 R field) = TypesDiffer self field
Finite self (a :*: b) = Finite self a && Finite self b
Finite self (M1 D t f) = Finite self f
Finite self (a :+: b) = Finite self a || Finite self b
Finite self (M1 C c f) = AllFieldsFinal self f
Finite self (M1 S s f) = Finite self f
type family ArgumentsCount (a :: * -> *) :: Nat where
ArgumentsCount U1 = 1
ArgumentsCount (M1 S s f) = 1
ArgumentsCount (a :*: b) = (ArgumentsCount a) + (ArgumentsCount b)
-- | Calculates count of constructors encoded by particular ':+:'.
-- Internal use only.
type family SumLen a :: Nat where
SumLen (a G.:+: b) = (SumLen a) + (SumLen b)
SumLen a = 1
-- | Generic arbitrary.
--
-- Parameters are:
-- * self: the ADT we generating instance for
-- * a: some part of the `Rep self`
-- * finite: Is `a` finite? Infinite type has no finite values (like Stream)
class (Finite self a ~ finite) => GArbitrary self a (finite :: Bool) where
gArbitrary :: Proxy self -> QC.Gen (a x)
instance
( GArbitrary self (M1 C c f) 'True
) => GArbitrary self (M1 D t (M1 C c f)) 'True where
gArbitrary _ = M1 <$> gArbitrary (Proxy :: Proxy self)
-- | The constructor meta information
instance
( GArbitrary self f some
, KnownNat (ArgumentsCount f)
, AllFieldsFinal self f ~ some
) => GArbitrary self (M1 C c f) some where
gArbitrary _ = M1 <$> scale predNat (gArbitrary (Proxy :: Proxy self))
where
argumentsCount = fromIntegral $ natVal (Proxy :: Proxy (ArgumentsCount f))
predNat n = max 0 $ if argumentsCount > 1
then n `div` argumentsCount
else pred n
-- | Unit type instance
instance GArbitrary self U1 'True where
gArbitrary _ = pure U1
-- | Constructor field meta information
instance GArbitrary self f some => GArbitrary self (M1 S t f) some where
gArbitrary _ = M1 <$> gArbitrary (Proxy :: Proxy self)
-- | Data of the constructor field
instance
( Arbitrary t
, Finite self (K1 R t) ~ some
) => GArbitrary self (K1 R t) some where
gArbitrary _ = K1 <$> arbitrary
-- | Product
instance
( GArbitrary self a af
, GArbitrary self b bf
, (af && bf) ~ some
) => GArbitrary self (a :*: b) some where
gArbitrary _ = liftA2 (:*:)
(gArbitrary (Proxy :: Proxy self)) (gArbitrary (Proxy :: Proxy self))
#if __GLASGOW_HASKELL__ >= 800
instance
( TypeError (ShowType self :<>: Text " refers to itself in all constructors")
, AllFieldsFinal self f ~ 'False
) => GArbitrary self (M1 D t (M1 C c f)) 'False where
gArbitrary _ = error "Unreachable"
#endif
-- | ADT declaration with multiple constructors
instance
( FiniteSum self a b af bf
, GArbitrary self (a :+: b) 'True
) => GArbitrary self (M1 D t (a :+: b)) 'True where
gArbitrary _ = sized $ \s -> M1 <$>
if s > 1
then gArbitrary (Proxy :: Proxy self)
else oneof (finiteSum (Proxy :: Proxy self))
-- | Any sum inside of declaration
instance
( GArbitrary self a af, GArbitrary self b bf
, KnownNat (SumLen a), KnownNat (SumLen b)
, (af || bf) ~ some
) => GArbitrary self (a :+: b) some where
gArbitrary _ = frequency
[ (lfreq, G.L1 <$> gArbitrary (Proxy :: Proxy self))
, (rfreq, G.R1 <$> gArbitrary (Proxy :: Proxy self)) ]
where
lfreq = fromIntegral $ natVal (Proxy :: Proxy (SumLen a))
rfreq = fromIntegral $ natVal (Proxy :: Proxy (SumLen b))
class
( Finite self a ~ af, Finite self b ~ bf
) => FiniteSum self (a :: * -> *) (b :: * -> *) af bf where
finiteSum :: Proxy self -> [Gen ((a :+: b) p)]
instance
( FiniteSumElem self a, FiniteSumElem self b
, Finite self a ~ 'True
, Finite self b ~ 'True
) => FiniteSum self a b 'True 'True where
finiteSum _ = concat
[ fmap L1 <$> finiteElem (Proxy :: Proxy self)
, fmap R1 <$> finiteElem (Proxy :: Proxy self)]
instance
( FiniteSumElem self a
, Finite self a ~ 'True
, Finite self b ~ 'False
) => FiniteSum self a b 'True 'False where
finiteSum _ = fmap L1 <$> finiteElem (Proxy :: Proxy self)
instance
( FiniteSumElem self b
, Finite self a ~ 'False
, Finite self b ~ 'True
) => FiniteSum self a b 'False 'True where
finiteSum _ = fmap R1 <$> finiteElem (Proxy :: Proxy self)
class FiniteSumElem self a where
finiteElem :: Proxy self -> [Gen (a p)]
instance
( FiniteSum self a b af bf
) => FiniteSumElem self (a :+: b) where
finiteElem _ = finiteSum (Proxy :: Proxy self)
instance
( GArbitrary self (M1 C c f) 'True
) => FiniteSumElem self (M1 C c f) where
finiteElem _ = [gArbitrary (Proxy :: Proxy self)]
#if __GLASGOW_HASKELL__ >= 800
instance
( TypeError (ShowType self :<>: Text " refers to itself in all constructors")
, (Finite self a || Finite self b) ~ 'False
) => GArbitrary self (M1 D t (a :+: b)) 'False where
gArbitrary _ = error "Unreachable"
#endif
genericArbitrary
:: forall a ga some
. (Generic a, GArbitrary a ga some, ga ~ Rep a)
=> Gen a
genericArbitrary = G.to <$> gArbitrary (Proxy :: Proxy a)