Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions examples/Constrained/Examples/BinTree.hs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ instance HasSpec a => HasSpec (BinTree a) where
: (BinNode left a <$> shrinkWithTypeSpec s right)
++ ((\l -> BinNode l a right) <$> shrinkWithTypeSpec s left)

fixupWithTypeSpec _ _ = Nothing

cardinalTypeSpec _ = mempty

toPreds t (BinTreeSpec msz s) =
Expand Down
12 changes: 12 additions & 0 deletions src/Constrained/Base.hs
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,9 @@ class
-- | Shrink an `a` with the aide of a `TypeSpec`
shrinkWithTypeSpec :: TypeSpec a -> a -> [a]

-- | Try to make an `a` conform to `TypeSpec` with minimal changes
fixupWithTypeSpec :: TypeSpec a -> a -> Maybe a

-- | Convert a spec to predicates:
-- The key property here is:
-- ∀ a. a `conformsTo` spec == a `conformsTo` constrained (\t -> toPreds t spec)
Expand Down Expand Up @@ -598,6 +601,13 @@ class
[a]
shrinkWithTypeSpec spec a = map fromSimpleRep $ shrinkWithTypeSpec spec (toSimpleRep a)

default fixupWithTypeSpec ::
GenericallyInstantiated a =>
TypeSpec a ->
a ->
Maybe a
fixupWithTypeSpec spec a = fromSimpleRep <$> fixupWithTypeSpec spec (toSimpleRep a)

default cardinalTypeSpec ::
GenericallyInstantiated a =>
TypeSpec a ->
Expand All @@ -618,6 +628,7 @@ instance HasSpec Bool where
cardinalTypeSpec _ = equalSpec 2
cardinalTrueSpec = equalSpec 2
shrinkWithTypeSpec _ = shrink
fixupWithTypeSpec _ = Just
conformsTo _ _ = True
toPreds _ _ = TruePred

Expand All @@ -627,6 +638,7 @@ instance HasSpec () where
combineSpec _ _ = typeSpec ()
_ `conformsTo` _ = True
shrinkWithTypeSpec _ _ = []
fixupWithTypeSpec _ _ = pure ()
genFromTypeSpec _ = pure ()
toPreds _ _ = TruePred
cardinalTypeSpec _ = MemberSpec (pure 1)
Expand Down
1 change: 1 addition & 0 deletions src/Constrained/Conformance.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module Constrained.Conformance (
conformsToSpec,
conformsToSpecE,
satisfies,
checkPredE,
checkPredsE,
) where

Expand Down
70 changes: 67 additions & 3 deletions src/Constrained/Generation.hs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ module Constrained.Generation (
genFromSpecT,
genFromSpecWithSeed,
shrinkWithSpec,
fixupWithSpec,
simplifySpec,

-- ** Debugging
Expand Down Expand Up @@ -155,15 +156,71 @@ shrinkWithSpec (simplifySpec -> spec) a = filter (`conformsToSpec` spec) $ case
ExplainSpec _ s -> shrinkWithSpec s a
-- TODO: filter on can't if we have a known to be sound shrinker
TypeSpec s _ -> shrinkWithTypeSpec s a
-- TODO: The better way of doing this is to compute the dependency graph,
-- shrink one variable at a time, and fixup the rest of the variables
SuspendedSpec {} -> shr a
SuspendedSpec x p -> shrinkFromPreds p x a ++ shr a
MemberSpec {} -> shr a
TrueSpec -> shr a
ErrorSpec {} -> []
where
shr = shrinkWithTypeSpec (emptySpec @a)

shrinkFromPreds :: HasSpec a => Pred -> Var a -> a -> [a]
shrinkFromPreds p
| Result plan <- prepareLinearization p = \x a -> listFromGE $ do
-- NOTE: we do this to e.g. guard against bad construction functions in Exists
case checkPredE (Env.singleton x a) (NE.fromList []) p of
Nothing -> pure ()
Just err -> explainNE err $ fatalError "Trying to shrink a bad value, don't do that!"
-- Get an `env` for the original value
initialEnv <- envFromPred (Env.singleton x a) p
return
[ a'
| -- Shrink the initialEnv
env' <- shrinkEnvFromPlan initialEnv plan
, -- Get the value of the constrained variable `x` in the shrunk env
Just a' <- [Env.lookup env' x]
, -- NOTE: this is necessary because it's possible that changing
-- a particular value in the env during shrinking might not result
-- in the value of `x` changing and there is no better way to know than
-- to do this.
a' /= a
]
| otherwise = error "Bad pred"

-- Start with a valid Env for the plan and try to shrink it
shrinkEnvFromPlan :: Env -> SolverPlan -> [Env]
shrinkEnvFromPlan initialEnv SolverPlan {..} = go mempty solverPlan
where
go :: Env -> [SolverStage] -> [Env]
go _ [] = [] -- In this case we decided to keep every variable the same so nothing to return
go env ((unsafeSubstStage env -> SolverStage {..}) : plan) = do
Just a <- [Env.lookup initialEnv stageVar]
-- Two cases:
-- - either we shrink this value and try to fixup every value later on in the plan or
[ fixedEnv
| a' <- shrinkWithSpec stageSpec a
, let env' = Env.extend stageVar a' env
, Just fixedEnv <- [fixupPlan env' plan]
]
-- - we keep this value the way it is and try to shrink some later value
++ go (Env.extend stageVar a env) plan

-- Fix the rest of the plan given an environment `env` for the plan so far
fixupPlan :: Env -> [SolverStage] -> Maybe Env
fixupPlan env [] = pure env
fixupPlan env ((unsafeSubstStage env -> SolverStage {..}) : plan) =
case Env.lookup (env <> initialEnv) stageVar >>= fixupWithSpec stageSpec of
Nothing -> Nothing
Just a -> fixupPlan (Env.extend stageVar a env) plan

-- Try to fix a value w.r.t a specification
fixupWithSpec :: forall a. HasSpec a => Specification a -> a -> Maybe a
fixupWithSpec spec a
| a `conformsToSpec` spec = Just a
| otherwise = case spec of
MemberSpec (a' :| _) -> Just a'
TypeSpec ts _ -> fixupWithTypeSpec ts a >>= \ a' -> a' <$ guard (conformsToSpec a' spec)
_ -> listToMaybe $ filter (`conformsToSpec` spec) (shrinkWithSpec TrueSpec a)

-- Debugging --------------------------------------------------------------

-- | A version of `genFromSpecT` that runs in the IO monad. Good for debugging.
Expand Down Expand Up @@ -197,6 +254,10 @@ prettyPlan (simplifySpec -> spec)

-- ---------------------- Building a plan -----------------------------------

unsafeSubstStage :: Env -> SolverStage -> SolverStage
unsafeSubstStage env (SolverStage y ps spec relevant) =
normalizeSolverStage $ SolverStage y (substPred env <$> ps) spec relevant

substStage :: HasSpec a => Set Name -> Var a -> a -> SolverStage -> SolverStage
substStage rel' x val (SolverStage y ps spec relevant) =
normalizeSolverStage $ SolverStage y (substPred env <$> ps) spec relevant'
Expand Down Expand Up @@ -1105,6 +1166,9 @@ instance (HasSpec a, HasSpec b, KnownNat (CountCases b)) => HasSpec (Sum a b) wh
shrinkWithTypeSpec (SumSpec _ sa _) (SumLeft a) = SumLeft <$> shrinkWithSpec sa a
shrinkWithTypeSpec (SumSpec _ _ sb) (SumRight b) = SumRight <$> shrinkWithSpec sb b

fixupWithTypeSpec (SumSpec _ sa _) (SumLeft a) = SumLeft <$> fixupWithSpec sa a
fixupWithTypeSpec (SumSpec _ _ sb) (SumRight b) = SumRight <$> fixupWithSpec sb b

toPreds ct (SumSpec h sa sb) =
Case
ct
Expand Down
20 changes: 20 additions & 0 deletions src/Constrained/NumOrd.hs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ module Constrained.NumOrd (
combineNumSpec,
genFromNumSpec,
shrinkWithNumSpec,
fixupWithTypeSpec,
conformsToNumSpec,
toPredsNumSpec,
OrdLike (..),
Expand Down Expand Up @@ -320,6 +321,11 @@ genFromNumSpec (NumSpecInterval ml mu) = do
shrinkWithNumSpec :: Arbitrary n => NumSpec n -> n -> [n]
shrinkWithNumSpec _ = shrink

-- TODO: fixme

fixupWithNumSpec :: Arbitrary n => NumSpec n -> n -> Maybe n
fixupWithNumSpec _ = listToMaybe . shrink

constrainInterval ::
(MonadGenError m, Ord a, Num a, Show a) => Maybe a -> Maybe a -> Integer -> m (a, a)
constrainInterval ml mu r =
Expand Down Expand Up @@ -1073,6 +1079,7 @@ instance HasSpec Integer where
combineSpec = combineNumSpec
genFromTypeSpec = genFromNumSpec
shrinkWithTypeSpec = shrinkWithNumSpec
fixupWithTypeSpec = fixupWithNumSpec
conformsTo = conformsToNumSpec
toPreds = toPredsNumSpec
cardinalTypeSpec = cardinalNumSpec
Expand All @@ -1084,6 +1091,7 @@ instance HasSpec Int where
combineSpec = combineNumSpec
genFromTypeSpec = genFromNumSpec
shrinkWithTypeSpec = shrinkWithNumSpec
fixupWithTypeSpec = fixupWithNumSpec
conformsTo = conformsToNumSpec
toPreds = toPredsNumSpec
cardinalTypeSpec = cardinalNumSpec
Expand All @@ -1095,6 +1103,7 @@ instance HasSpec (Ratio Integer) where
combineSpec = combineNumSpec
genFromTypeSpec = genFromNumSpec
shrinkWithTypeSpec = shrinkWithNumSpec
fixupWithTypeSpec = fixupWithNumSpec
conformsTo = conformsToNumSpec
toPreds = toPredsNumSpec
cardinalTypeSpec _ = TrueSpec
Expand All @@ -1106,6 +1115,7 @@ instance HasSpec Natural where
combineSpec = combineNumSpec
genFromTypeSpec = genFromNumSpec
shrinkWithTypeSpec = shrinkWithNumSpec
fixupWithTypeSpec = fixupWithNumSpec
conformsTo = conformsToNumSpec
toPreds = toPredsNumSpec
cardinalTypeSpec (NumSpecInterval (fromMaybe 0 -> lo) (Just hi)) =
Expand All @@ -1121,6 +1131,7 @@ instance HasSpec Word8 where
combineSpec = combineNumSpec
genFromTypeSpec = genFromNumSpec
shrinkWithTypeSpec = shrinkWithNumSpec
fixupWithTypeSpec = fixupWithNumSpec
conformsTo = conformsToNumSpec
toPreds = toPredsNumSpec
cardinalTypeSpec = cardinalNumSpec
Expand All @@ -1134,6 +1145,7 @@ instance HasSpec Word16 where
combineSpec = combineNumSpec
genFromTypeSpec = genFromNumSpec
shrinkWithTypeSpec = shrinkWithNumSpec
fixupWithTypeSpec = fixupWithNumSpec
conformsTo = conformsToNumSpec
toPreds = toPredsNumSpec
cardinalTypeSpec = cardinalNumSpec
Expand All @@ -1146,6 +1158,7 @@ instance HasSpec Word32 where
combineSpec = combineNumSpec
genFromTypeSpec = genFromNumSpec
shrinkWithTypeSpec = shrinkWithNumSpec
fixupWithTypeSpec = fixupWithNumSpec
conformsTo = conformsToNumSpec
toPreds = toPredsNumSpec
cardinalTypeSpec = cardinalNumSpec
Expand All @@ -1157,6 +1170,7 @@ instance HasSpec Word64 where
combineSpec = combineNumSpec
genFromTypeSpec = genFromNumSpec
shrinkWithTypeSpec = shrinkWithNumSpec
fixupWithTypeSpec = fixupWithNumSpec
conformsTo = conformsToNumSpec
toPreds = toPredsNumSpec
cardinalTypeSpec = cardinalNumSpec
Expand All @@ -1168,6 +1182,7 @@ instance HasSpec Int8 where
combineSpec = combineNumSpec
genFromTypeSpec = genFromNumSpec
shrinkWithTypeSpec = shrinkWithNumSpec
fixupWithTypeSpec = fixupWithNumSpec
conformsTo = conformsToNumSpec
toPreds = toPredsNumSpec
cardinalTrueSpec = equalSpec 256
Expand All @@ -1180,6 +1195,7 @@ instance HasSpec Int16 where
combineSpec = combineNumSpec
genFromTypeSpec = genFromNumSpec
shrinkWithTypeSpec = shrinkWithNumSpec
fixupWithTypeSpec = fixupWithNumSpec
conformsTo = conformsToNumSpec
toPreds = toPredsNumSpec
cardinalTypeSpec = cardinalNumSpec
Expand All @@ -1192,6 +1208,7 @@ instance HasSpec Int32 where
combineSpec = combineNumSpec
genFromTypeSpec = genFromNumSpec
shrinkWithTypeSpec = shrinkWithNumSpec
fixupWithTypeSpec = fixupWithNumSpec
conformsTo = conformsToNumSpec
toPreds = toPredsNumSpec
cardinalTypeSpec = cardinalNumSpec
Expand All @@ -1203,6 +1220,7 @@ instance HasSpec Int64 where
combineSpec = combineNumSpec
genFromTypeSpec = genFromNumSpec
shrinkWithTypeSpec = shrinkWithNumSpec
fixupWithTypeSpec = fixupWithNumSpec
conformsTo = conformsToNumSpec
toPreds = toPredsNumSpec
cardinalTypeSpec = cardinalNumSpec
Expand All @@ -1214,6 +1232,7 @@ instance HasSpec Float where
combineSpec = combineNumSpec
genFromTypeSpec = genFromNumSpec
shrinkWithTypeSpec = shrinkWithNumSpec
fixupWithTypeSpec = fixupWithNumSpec
conformsTo = conformsToNumSpec
toPreds = toPredsNumSpec
cardinalTypeSpec _ = TrueSpec
Expand All @@ -1225,6 +1244,7 @@ instance HasSpec Double where
combineSpec = combineNumSpec
genFromTypeSpec = genFromNumSpec
shrinkWithTypeSpec = shrinkWithNumSpec
fixupWithTypeSpec = fixupWithNumSpec
conformsTo = conformsToNumSpec
toPreds = toPredsNumSpec
cardinalTypeSpec _ = TrueSpec
Expand Down
3 changes: 3 additions & 0 deletions src/Constrained/Spec/List.hs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,9 @@ instance HasSpec a => HasSpec [a] where
shrinkWithTypeSpec (ListSpec _ _ _ es _) as =
shrinkList (shrinkWithSpec es) as

-- TODO: fixme
fixupWithTypeSpec _ _ = Nothing

cardinalTypeSpec _ = TrueSpec

guardTypeSpec = guardListSpec
Expand Down
2 changes: 2 additions & 0 deletions src/Constrained/Spec/Map.hs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,8 @@ instance

shrinkWithTypeSpec (MapSpec _ _ _ _ kvs _) m = map Map.fromList $ shrinkList (shrinkWithSpec kvs) (Map.toList m)

fixupWithTypeSpec _ _ = Nothing

toPreds m (MapSpec mHint mustKeys mustVals size kvs foldSpec) =
toPred
[ Assert $ Lit mustKeys `subset_` dom_ m
Expand Down
3 changes: 3 additions & 0 deletions src/Constrained/Spec/Set.hs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,9 @@ instance (Ord a, HasSpec a) => HasSpec (Set a) where

shrinkWithTypeSpec (SetSpec _ es _) as = map Set.fromList $ shrinkList (shrinkWithSpec es) (Set.toList as)

-- TODO: fixme
fixupWithTypeSpec _ _ = Nothing

toPreds s (SetSpec m es size) =
fold $
-- Don't include this if the must set is empty
Expand Down
3 changes: 3 additions & 0 deletions src/Constrained/Spec/Tree.hs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ instance HasSpec a => HasSpec (Tree a) where
| ts' <- shrinkList (shrinkWithTypeSpec (TreeSpec Nothing Nothing TrueSpec ctxSpec)) ts
]

-- TODO: fixme
fixupWithTypeSpec _ _ = Nothing

cardinalTypeSpec _ = mempty

toPreds t (TreeSpec mal msz rs s) =
Expand Down
3 changes: 3 additions & 0 deletions src/Constrained/TheKnot.hs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ instance (HasSpec a, HasSpec b) => HasSpec (Prod a b) where
[Prod a' b | a' <- shrinkWithSpec sa a]
++ [Prod a b' | b' <- shrinkWithSpec sb b]

fixupWithTypeSpec (Cartesian sa sb) (Prod a b) =
Prod <$> fixupWithSpec sa a <*> fixupWithSpec sb b

toPreds x (Cartesian sf ss) =
satisfies (prodFst_ x) sf
<> satisfies (prodSnd_ x) ss
Expand Down
Loading