{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ExplicitForAll #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DeriveGeneric #-}
module Hedgehog.Gen.Generic
( mkGen
, mkGenWith
, emptyGens
, byType
, byField
, byPos
, GenList (..)
, GenMap
) where
import GHC.Generics
import GHC.TypeLits
import GHC.Exts
import Data.TypeRepMap (TypeRepMap)
import qualified Data.TypeRepMap as TMap
import Data.Proxy
import Data.Typeable
import Data.Kind
import Hedgehog
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
import Data.Text
mkGen :: forall a.(Generic a, GMkGen (Rep a) '[], Typeable a) =>
GenMap
-> Hedgehog.Gen a
mkGen genMap = mkGenWith GNil genMap
mkGenWith :: forall a glist.
( Generic a, GMkGen (Rep a) glist, Typeable a
) => GenList glist
-> GenMap
-> Hedgehog.Gen a
mkGenWith glist genMap = to <$> (gMkGen 1 glist (Proxy :: Proxy a) genMap)
data GenList (or :: [*]) where
GNil :: GenList '[]
(:&) :: Gen x -> GenList xs -> GenList (x ': xs)
type GenMap = TypeRepMap Hedgehog.Gen
emptyGens :: GenMap
emptyGens = TMap.empty
byType :: Typeable t => Hedgehog.Gen t -> GenMap -> GenMap
byType = TMap.insert
byField :: forall s (field :: Symbol) t.
( KnownSymbol field
, Typeable s
, Typeable t
, FromEither (ValidateSel s (Rep s) field t)
) => Hedgehog.Gen t -> GenMap -> GenMap
byField gen = TMap.insert (Field <$> gen :: Gen (Field s field t))
byPos :: forall s (pos :: Nat) t.
( KnownNat pos
, Typeable s
, Typeable t
, FromEither (ValidatePos s (Rep s) pos 1 t)
) => Hedgehog.Gen t -> GenMap -> GenMap
byPos gen = TMap.insert (Pos <$> gen :: Gen (Pos s pos t))
newtype Field s (field :: Symbol) t = Field {getFieldGen :: t}
newtype Pos s (pos :: Nat) t = Pos {getPosGen :: t}
class GMkGen f (or ::[*]) where
gMkGen :: (Typeable s) => Word -> GenList or -> Proxy s -> GenMap -> Hedgehog.Gen (f a)
instance (GMkGen f or) => GMkGen (D1 m f) or where
gMkGen pos glist pxyS genMap = M1 <$> gMkGen pos glist pxyS genMap
instance (GMkGen f or, GMkGen g or) => GMkGen (f :+: g) or where
gMkGen pos glist pxyS genMap = Gen.choice [ L1 <$> gMkGen pos glist pxyS genMap
, R1 <$> gMkGen pos glist pxyS genMap
]
instance (GMkGen f or) => GMkGen (C1 m f) or where
gMkGen pos glist pxyS genMap= M1 <$> gMkGen pos glist pxyS genMap
instance (GMkGen f or, GMkGen g or) => GMkGen (f :*: g) or where
gMkGen pos glist pxyS genMap = (:*:) <$> (gMkGen pos glist pxyS genMap) <*> (gMkGen (pos+1) glist pxyS genMap)
instance GMkGen U1 or where
gMkGen _ _ _ _ = pure U1
instance ( Typeable a
, KnownSymbol fn
, GMkGen (K1 f (DervType (IsCustom a or) a)) or
) => GMkGen (S1 ('MetaSel ('Just fn) up sst st) (K1 f a)) or where
gMkGen pos glist pxyS genMap=
let
typeGen = TMap.lookup genMap :: Maybe (Hedgehog.Gen a)
getFieldGen' :: (Typeable s, Typeable a, KnownSymbol fn) => Proxy s -> Maybe (Hedgehog.Gen (Field s fn a))
getFieldGen' _ = TMap.lookup genMap
posGen :: Maybe (Hedgehog.Gen a)
posGen = case someNatVal (toInteger pos) of
Just (SomeNat pxyPos) ->
let
getPosGen' :: (Typeable s, Typeable a, KnownNat pos) => Proxy s -> Proxy pos -> Maybe (Hedgehog.Gen (Pos s pos a))
getPosGen' _ _ = TMap.lookup genMap
in (fmap . fmap) getPosGen (getPosGen' pxyS pxyPos)
Nothing -> Nothing
fieldGen :: Maybe (Hedgehog.Gen a)
fieldGen = (fmap . fmap) getFieldGen (getFieldGen' pxyS)
in M1 <$> case fieldGen of
Just gen -> K1 <$> gen
Nothing -> case posGen of
Just gen -> K1 <$> gen
Nothing -> case typeGen of
Just gen -> K1 <$> gen
Nothing ->
let g = gMkGen pos glist pxyS genMap :: Hedgehog.Gen (K1 f (DervType (IsCustom a or) a) a)
in flip fmap g $ \case
K1 (Custom a) -> K1 a
K1 (Stock a) -> K1 a
instance GMkGen (K1 f (DervType 'False Int)) '[] where
gMkGen _ _ _ _ = (K1 . Stock) <$> Gen.int Range.exponentialBounded
instance GMkGen (K1 f (DervType 'False Word)) '[] where
gMkGen _ _ _ _ = (K1 . Stock) <$> Gen.word Range.exponentialBounded
instance GMkGen (K1 f (DervType 'False Text)) '[] where
gMkGen _ _ _ _ = (K1 . Stock) <$> Gen.text (Range.constant 0 100) Gen.alphaNum
instance GMkGen (K1 f (DervType 'False Bool)) '[] where
gMkGen _ _ _ _ = (K1 . Stock) <$> Gen.bool
instance ( Typeable t
, Typeable x
) => GMkGen (K1 f (DervType 'False t)) '[x] where
gMkGen _ (gen :& GNil) _ _ = case (eqT :: Maybe ( x :~: t)) of
Just Refl -> (K1 . Stock) <$> gen
Nothing -> error "Panic: Invariant violated"
instance ( Typeable t
, Typeable x1
, GMkGen (K1 f (DervType 'False t)) (x2 ': xs)
) => GMkGen (K1 f (DervType 'False t)) (x1 ': x2 ': xs) where
gMkGen pos (gen :& glist) pxy genMap = case (eqT :: Maybe ( x1 :~: t)) of
Just Refl -> (K1 . Stock) <$> gen
Nothing -> gMkGen pos glist pxy genMap
instance (Typeable a, Generic a, GMkGen (Rep a) '[]) => GMkGen (K1 f (DervType 'True a)) '[] where
gMkGen pos glist _ genMap = (K1 . Custom . to) <$> (gMkGen pos glist (Proxy :: Proxy a) genMap)
data DervType :: Bool -> Type -> Type where
Custom :: a -> DervType 'True a
Stock :: a -> DervType 'False a
type family IsCustom t (xs :: [*]) where
IsCustom Int _ = 'False
IsCustom Bool _ = 'False
IsCustom Word _ = 'False
IsCustom Text _ = 'False
IsCustom t ts = IsNotElem t ts
type family IsNotElem t (xs :: [*]) :: Bool where
IsNotElem t '[] = 'True
IsNotElem t (t ': ts) = 'False
IsNotElem t (_ ': ts) = IsNotElem t ts
type family FromEither (m :: Either ErrorMessage Constraint) :: Constraint where
FromEither ('Left ex) = TypeError ex
FromEither ('Right c) = c
type family ValidateSelK (m :: Either ErrorMessage Constraint) s (srep :: * -> *) (fld :: Symbol) t :: Either ErrorMessage Constraint where
ValidateSelK ('Left ex) s srep fld t = ValidateSel s srep fld t
ValidateSelK ('Right c) _ _ _ _ = 'Right c
type family ValidatePosK (m :: Either ErrorMessage Constraint) s (srep :: * -> *) (pos :: Nat) (cpos :: Nat) t :: Either ErrorMessage Constraint where
ValidatePosK ('Left ex) s srep pos newpos t = ValidatePos s srep pos newpos t
ValidatePosK ('Right c) _ _ _ _ _ = 'Right c
type family ValidateSel s (srep :: * -> *) (fld :: Symbol) t :: Either ErrorMessage Constraint where
ValidateSel s (D1 i f) fld t = ValidateSel s f fld t
ValidateSel s (f :+: g) fld t = ValidateSelK (ValidateSel s f fld t) s g fld t
ValidateSel s (C1 ('MetaCons cn _ 'False) _) fld t = 'Left ('Text "The constructor " ':<>: 'ShowType cn ':<>: 'Text " does not have named fields")
ValidateSel s (C1 i c) fld t = ValidateSel s c fld t
ValidateSel s (f :*: g) fld t = ValidateSelK (ValidateSel s f fld t) s g fld t
ValidateSel s (S1 ('MetaSel ('Just fld) _ _ _) (K1 i t1)) fld t2 = 'Right (t1 ~ t2)
ValidateSel s (S1 ('MetaSel ('Just fld1) _ _ _) (K1 i _)) fld2 _ = 'Left ('Text "type '" ':<>: 'ShowType s ':<>: 'Text "' does not have a field named: " ':<>: 'ShowType fld2)
type family ValidatePos s (srep :: * -> *) (pos :: Nat) (cpos ::Nat) t :: Either ErrorMessage Constraint where
ValidatePos s (D1 i f) pos cpos t = ValidatePos s f pos cpos t
ValidatePos s (f :+: g) pos cpos t = ValidatePosK (ValidatePos s f pos cpos t) s g pos cpos t
ValidatePos s (C1 i c) pos cpos t = ValidatePos s c pos cpos t
ValidatePos s (f :*: g) pos cpos t = ValidatePosK (ValidatePos s f pos cpos t) s g pos (cpos+1) t
ValidatePos s (S1 ('MetaSel _ _ _ _) (K1 i t1)) pos pos t2 = 'Right (t1 ~ t2)
ValidatePos s (S1 ('MetaSel _ _ _ _) (K1 i _)) pos2 _ _ = 'Left ('Text "type '" ':<>: 'ShowType s ':<>: 'Text "' does not have a positon numbered: " ':<>: 'ShowType pos2)