quickcheck-dynamic-3.0.0: src/Test/QuickCheck/StateModel/Variables.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE QuantifiedConstraints #-}
{-# LANGUAGE UndecidableInstances #-}
module Test.QuickCheck.StateModel.Variables (
Var,
Any (..),
HasVariables (..),
HasNoVariables,
VarContext,
mkVar,
ctxAtType,
arbitraryVar,
shrinkVar,
extendContext,
isWellTyped,
allVariables,
unsafeCoerceVar,
) where
import Data.Data
import Data.List
import Data.Map (Map)
import Data.Map qualified as Map
import Data.Ord
import Data.Set (Set)
import Data.Set qualified as Set
import GHC.Generics
import Test.QuickCheck as QC
-- | A symbolic variable for a value of type `a`
newtype Var a = Var Int
deriving (Eq, Ord, Typeable, Data)
mkVar :: Int -> Var a
mkVar = Var
instance Show (Var a) where
show (Var i) = "var" ++ show i
-- | This type class gives you a way to get all the symbolic variables that
-- appear in a value.
class HasVariables a where
getAllVariables :: a -> Set (Any Var)
default getAllVariables :: (Generic a, GenericHasVariables (Rep a)) => a -> Set (Any Var)
getAllVariables = genericGetAllVariables . from
instance HasVariables a => HasVariables (Smart a) where
getAllVariables (Smart _ a) = getAllVariables a
instance Typeable a => HasVariables (Var a) where
getAllVariables = Set.singleton . Some
instance (HasVariables k, HasVariables v) => HasVariables (Map k v) where
getAllVariables = getAllVariables . Map.toList
instance HasVariables a => HasVariables (Set a) where
getAllVariables = getAllVariables . Set.toList
newtype HasNoVariables a = HasNoVariables a
instance HasVariables (HasNoVariables a) where
getAllVariables _ = mempty
deriving via HasNoVariables Integer instance HasVariables Integer
deriving via HasNoVariables Int instance HasVariables Int
deriving via HasNoVariables Char instance HasVariables Char
data Any f where
Some :: (Typeable a, Eq (f a)) => f a -> Any f
instance Eq (Any f) where
Some (a :: f a) == Some (b :: f b) =
case eqT @a @b of
Just Refl -> a == b
Nothing -> False
instance (forall a. Ord (f a)) => Ord (Any f) where
compare (Some (a :: f a)) (Some (a' :: f a')) =
case eqT @a @a' of
Just Refl -> compare a a'
Nothing -> compare (typeRep a) (typeRep a')
newtype VarContext = VarCtx (Set (Any Var))
deriving (Semigroup, Monoid) via Set (Any Var)
instance Show VarContext where
show (VarCtx vs) =
"[" ++ intercalate ", " (map showBinding . sortBy (comparing getIdx) $ Set.toList vs) ++ "]"
where
getIdx (Some (Var i)) = i
showBinding :: Any Var -> String
-- The use of typeRep here is on purpose to avoid printing `Var` unnecessarily.
showBinding (Some v) = show v ++ " :: " ++ show (typeRep v)
isWellTyped :: Typeable a => Var a -> VarContext -> Bool
isWellTyped v (VarCtx ctx) = Some v `Set.member` ctx
-- TODO: check the invariant that no variable index is used
-- twice at different types. This is generally not an issue
-- because lookups take types into account (so it *shouldn't*
-- cause an issue, but it might be good practise to crash
-- if the invariant is violated anyway as it is evidence that
-- something is horribly broken at the use site).
extendContext :: Typeable a => VarContext -> Var a -> VarContext
extendContext (VarCtx ctx) v = VarCtx $ Set.insert (Some v) ctx
allVariables :: HasVariables a => a -> VarContext
allVariables = VarCtx . getAllVariables
ctxAtType :: Typeable a => VarContext -> [Var a]
ctxAtType (VarCtx vs) = [v | Some (cast -> Just v) <- Set.toList vs]
arbitraryVar :: Typeable a => VarContext -> Gen (Var a)
arbitraryVar = elements . ctxAtType
shrinkVar :: Typeable a => VarContext -> Var a -> [Var a]
shrinkVar ctx v = filter (< v) $ ctxAtType ctx
unsafeCoerceVar :: Var a -> Var b
unsafeCoerceVar (Var i) = Var i
instance {-# OVERLAPPABLE #-} (Generic a, GenericHasVariables (Rep a)) => HasVariables a
class GenericHasVariables f where
genericGetAllVariables :: f k -> Set (Any Var)
instance GenericHasVariables f => GenericHasVariables (M1 i c f) where
genericGetAllVariables = genericGetAllVariables . unM1
instance HasVariables c => GenericHasVariables (K1 i c) where
genericGetAllVariables = getAllVariables . unK1
instance GenericHasVariables U1 where
genericGetAllVariables _ = mempty
instance (GenericHasVariables f, GenericHasVariables g) => GenericHasVariables (f :*: g) where
genericGetAllVariables (x :*: y) = genericGetAllVariables x <> genericGetAllVariables y
instance (GenericHasVariables f, GenericHasVariables g) => GenericHasVariables (f :+: g) where
genericGetAllVariables (L1 x) = genericGetAllVariables x
genericGetAllVariables (R1 x) = genericGetAllVariables x