packages feed

falsify-0.4.0: src/Test/Falsify/GenDefault.hs

{-# LANGUAGE UndecidableInstances #-}

-- | Default generators
--
-- 'GenDefault' (as well as t'Test.Falsify.GenDefault.Std.Std') are exported from
-- "Test.Falsify", so you will only need to import this module if you want to
-- make use of the deriving-via support.
--
-- Intended for unqualified import.
module Test.Falsify.GenDefault (
    GenDefault(..)
    -- * Deriving-via support
  , ViaTag(..)
  , ViaIntegral(..)
  , ViaEnum(..)
  , ViaList(..)
  , ViaString(..)
  , ViaGeneric(..)
  , GGenDefault -- opaque
  ) where

import Data.Bits (FiniteBits)
import Data.Proxy
import GHC.Exts (IsList (..), IsString (..))
import GHC.Generics
import GHC.TypeLits (KnownNat, natVal, Nat)

import qualified Control.Applicative as Ap

import Test.Falsify.Generator (Gen)
import qualified Test.Falsify.Generator as Gen
import qualified Test.Falsify.Range     as Range

-- | Default generators
--
-- 'GenDefault' is similar to QuickCheck's 'Test.QuickCheck.Arbitrary' class
-- along with some @deriving via@ helpers. Unlike @Arbitrary@, 'GenDefault'
-- allows one to choose between sets of default generators with user-defined
-- tags. See "Test.Falsify.GenDefault.Std" for the standard tag with a few
-- useful instances.
class GenDefault tag a where
  -- | Default generator for @a@
  --
  -- The type-level @tag@ allows types @a@ to have multiple defaults.
  genDefault :: Proxy tag -> Gen a

-- | DerivingVia wrapper for types with default instances under other tags
newtype ViaTag tag' a = ViaTag {unViaTag :: a}

instance GenDefault tag' a => GenDefault tag (ViaTag tag' a) where
  genDefault _ = fmap ViaTag (genDefault @tag' Proxy)

{-------------------------------------------------------------------------------
  Deriving-via helpers for types of specific shape
-------------------------------------------------------------------------------}

-- | DerivingVia wrapper for Integral types
newtype ViaIntegral a = ViaIntegral {unViaIntegral :: a}

instance (Integral a, FiniteBits a, Bounded a) => GenDefault tag (ViaIntegral a) where
  genDefault _ = fmap ViaIntegral (Gen.inRange Range.uniform)

-- | DerivingVia wrapper for Enum types
newtype ViaEnum a = ViaEnum {unViaEnum :: a}

instance (Enum a, Bounded a) => GenDefault tag (ViaEnum a) where
  genDefault _ = fmap ViaEnum (Gen.inRange (Range.enum (minBound, maxBound)))

-- | DerivingVia wrapper for FromList types
newtype ViaList l (mn :: Nat) (mx :: Nat) = ViaList {unViaList :: l}

instance (IsList l, GenDefault tag (Item l), KnownNat mn, KnownNat mx) => GenDefault tag (ViaList l mn mx) where
  genDefault p =
    let bn = fromInteger (natVal (Proxy @mn))
        bx = fromInteger (natVal (Proxy @mx))
    in fmap (ViaList . fromList) (Gen.list (Range.inclusive (bn, bx)) (genDefault p))

-- | DerivingVia wrapper for FromString types
newtype ViaString s (mn :: Nat) (mx :: Nat) = ViaString {unViaString :: s}

instance (IsString s, GenDefault tag Char, KnownNat mn, KnownNat mx) => GenDefault tag (ViaString s mn mx) where
  genDefault p =
    let bn = fromInteger (natVal (Proxy @mn))
        bx = fromInteger (natVal (Proxy @mx))
    in fmap (ViaString . fromString) (Gen.list (Range.inclusive (bn, bx)) (genDefault p))

{-------------------------------------------------------------------------------
  Generics
-------------------------------------------------------------------------------}

-- | Generic generator construction
--
-- For use with t'ViaGeneric'.
class GGenDefault tag f where
  {-# MINIMAL #-}
  ggenDefault :: Proxy tag -> Gen (f a)
  ggenDefault _ = error "ggenDefault not implemented"

instance GGenDefault tag U1 where
  ggenDefault _ = pure U1

instance GGenDefault tag a => GGenDefault tag (M1 i c a) where
  ggenDefault = fmap M1 . ggenDefault

instance (GGenDefault tag a, GGenDefault tag b) => GGenDefault tag (a :*: b) where
  ggenDefault p = Ap.liftA2 (:*:) (ggenDefault p) (ggenDefault p)

instance (GGenDefault tag a, GGenDefault tag b) => GGenDefault tag (a :+: b) where
  ggenDefault p = Gen.choose (fmap L1 (ggenDefault p)) (fmap R1 (ggenDefault p))

instance GenDefault tag a => GGenDefault tag (K1 i a) where
  ggenDefault = fmap K1 . genDefault

-- | DerivingVia wrapper for Generic types
newtype ViaGeneric tag a = ViaGeneric {unViaGeneric :: a}

instance (Generic t, GGenDefault tag (Rep t)) => GenDefault tag (ViaGeneric tag t) where
  genDefault = fmap (ViaGeneric . to) . ggenDefault