packages feed

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)