quickcheck-dynamic-3.0.2: 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,
unsafeNextVarIndex
) 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 GHC.Word
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
deriving via HasNoVariables Word8 instance HasVariables Word8
deriving via HasNoVariables Word16 instance HasVariables Word16
deriving via HasNoVariables Word32 instance HasVariables Word32
deriving via HasNoVariables Word64 instance HasVariables Word64
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
unsafeNextVarIndex :: VarContext -> Int
unsafeNextVarIndex (VarCtx vs) = 1 + maximum (0 : [ i | Some (Var i) <- Set.toList vs ])
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