{-# 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' creates a 'Gen.Gen' for any __/a/__ having 'Generic'. -- 'mkGen' assumes 'Generic' instance is present for all the types in the the transitive dependency of __/a/__. mkGen :: forall a.(Generic a, GMkGen (Rep a) '[], Typeable a) => GenMap -- ^ Map containing type-based and field-based overrides of `Gen.Gen` -> Hedgehog.Gen a -- ^ Generator for any __/a/__ having 'Generic' instance mkGen genMap = mkGenWith GNil genMap -- |'mkGenWith' creates a 'Gen.Gen' for any __/a/__ having 'Generic'. -- It is same as 'mkGen', except that it takes HList of 'Gen.Gen's for all types not having 'Generic' instance in the transitive dependency of __/a/__ mkGenWith :: forall a glist. ( Generic a, GMkGen (Rep a) glist, Typeable a ) => GenList glist -- ^ HList of 'Gen.Gen's for types not having 'Generic' instance -> GenMap -- ^ Map containing type-based and field-based overrides of `Gen.Gen` -> Hedgehog.Gen a -- ^ Generator for any __/a/__ having 'Generic' instance mkGenWith glist genMap = to <$> (gMkGen 1 glist (Proxy :: Proxy a) genMap) -- | Hetrogenous List of Gen data GenList (or :: [*]) where GNil :: GenList '[] (:&) :: Gen x -> GenList xs -> GenList (x ': xs) -- | Map to hold type-based and field-based overrides of `Gen.Gen` type GenMap = TypeRepMap Hedgehog.Gen -- | 'emptyGens' creates a empty map of overrides. emptyGens :: GenMap emptyGens = TMap.empty -- | 'byType' is used to override the 'Gen.Gen' for type __/t/__. -- This have lower precedences than 'byField' & 'byPos' i.e. when there is field or position based override applied to the target type, that gets the percedence. If there is no such overrides, all the occurrences of this type uses the 'Gen.Gen' provided by this override. byType :: Typeable t => Hedgehog.Gen t -> GenMap -> GenMap byType = TMap.insert -- | 'byField' is used to override the 'Gen.Gen' for type __/t/__ of field named __/field/__ in type __/s/__ . -- This has higher precedences than 'byType' and 'byPos' 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' is used to override the 'Gen.Gen' for type __/t/__ at positon __/pos/__ in type __/s/__ . -- This has higher precedences than 'byType' and lower precedence thab 'byField' 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 'Nothing 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 (Pos s pos a)) getFieldGen' _ = TMap.lookup genMap fieldGen :: Maybe (Hedgehog.Gen a) fieldGen = (fmap . fmap) getFieldGen (getFieldGen' pxyS) in M1 <$> case fieldGen 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 ( 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)