Skip to content

Commit 1beae75

Browse files
Resurrect shrinking and improve it (#51)
1 parent 7d7e190 commit 1beae75

File tree

11 files changed

+129
-20
lines changed

11 files changed

+129
-20
lines changed

examples/Constrained/Examples/BinTree.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ instance HasSpec a => HasSpec (BinTree a) where
7676
: (BinNode left a <$> shrinkWithTypeSpec s right)
7777
++ ((\l -> BinNode l a right) <$> shrinkWithTypeSpec s left)
7878

79+
fixupWithTypeSpec _ _ = Nothing
80+
7981
cardinalTypeSpec _ = mempty
8082

8183
toPreds t (BinTreeSpec msz s) =

src/Constrained/Base.hs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,12 @@ class
488488
-- | Shrink an `a` with the aide of a `TypeSpec`
489489
shrinkWithTypeSpec :: TypeSpec a -> a -> [a]
490490

491+
-- | Try to make an `a` conform to `TypeSpec` with minimal changes. When
492+
-- `fixupWithSpec ts a` returns `Just a'`, it should be the case that
493+
-- `conformsTo a' ts`. There are no constraints in the `Nothing` case. A
494+
-- non-trivial implementation of this function is important for shrinking.
495+
fixupWithTypeSpec :: TypeSpec a -> a -> Maybe a
496+
491497
-- | Convert a spec to predicates:
492498
-- The key property here is:
493499
-- ∀ a. a `conformsTo` spec == a `conformsTo` constrained (\t -> toPreds t spec)
@@ -598,6 +604,13 @@ class
598604
[a]
599605
shrinkWithTypeSpec spec a = map fromSimpleRep $ shrinkWithTypeSpec spec (toSimpleRep a)
600606

607+
default fixupWithTypeSpec ::
608+
GenericallyInstantiated a =>
609+
TypeSpec a ->
610+
a ->
611+
Maybe a
612+
fixupWithTypeSpec spec a = fromSimpleRep <$> fixupWithTypeSpec spec (toSimpleRep a)
613+
601614
default cardinalTypeSpec ::
602615
GenericallyInstantiated a =>
603616
TypeSpec a ->
@@ -618,6 +631,7 @@ instance HasSpec Bool where
618631
cardinalTypeSpec _ = equalSpec 2
619632
cardinalTrueSpec = equalSpec 2
620633
shrinkWithTypeSpec _ = shrink
634+
fixupWithTypeSpec _ = Just
621635
conformsTo _ _ = True
622636
toPreds _ _ = TruePred
623637

@@ -627,6 +641,7 @@ instance HasSpec () where
627641
combineSpec _ _ = typeSpec ()
628642
_ `conformsTo` _ = True
629643
shrinkWithTypeSpec _ _ = []
644+
fixupWithTypeSpec _ _ = pure ()
630645
genFromTypeSpec _ = pure ()
631646
toPreds _ _ = TruePred
632647
cardinalTypeSpec _ = MemberSpec (pure 1)

src/Constrained/Conformance.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ module Constrained.Conformance (
1414
conformsToSpec,
1515
conformsToSpecE,
1616
satisfies,
17+
checkPredE,
1718
checkPredsE,
1819
) where
1920

src/Constrained/Generation.hs

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ module Constrained.Generation (
2525
genFromSpecT,
2626
genFromSpecWithSeed,
2727
shrinkWithSpec,
28+
fixupWithSpec,
2829
simplifySpec,
2930

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

166+
shrinkFromPreds :: HasSpec a => Pred -> Var a -> a -> [a]
167+
shrinkFromPreds p
168+
| Result plan <- prepareLinearization p = \x a -> listFromGE $ do
169+
-- NOTE: we do this to e.g. guard against bad construction functions in Exists
170+
case checkPredE (Env.singleton x a) (NE.fromList []) p of
171+
Nothing -> pure ()
172+
Just err -> explainNE err $ fatalError "Trying to shrink a bad value, don't do that!"
173+
-- Get an `env` for the original value
174+
initialEnv <- envFromPred (Env.singleton x a) p
175+
return
176+
[ a'
177+
| -- Shrink the initialEnv
178+
env' <- shrinkEnvFromPlan initialEnv plan
179+
, -- Get the value of the constrained variable `x` in the shrunk env
180+
Just a' <- [Env.lookup env' x]
181+
, -- NOTE: this is necessary because it's possible that changing
182+
-- a particular value in the env during shrinking might not result
183+
-- in the value of `x` changing and there is no better way to know than
184+
-- to do this.
185+
a' /= a
186+
]
187+
| otherwise = error "Bad pred"
188+
189+
-- Start with a valid Env for the plan and try to shrink it
190+
shrinkEnvFromPlan :: Env -> SolverPlan -> [Env]
191+
shrinkEnvFromPlan initialEnv SolverPlan {..} = go mempty solverPlan
192+
where
193+
go :: Env -> [SolverStage] -> [Env]
194+
go _ [] = [] -- In this case we decided to keep every variable the same so nothing to return
195+
go env ((unsafeSubstStage env -> SolverStage {..}) : plan) = do
196+
Just a <- [Env.lookup initialEnv stageVar]
197+
-- Two cases:
198+
-- - either we shrink this value and try to fixup every value later on in the plan or
199+
[ fixedEnv
200+
| a' <- shrinkWithSpec stageSpec a
201+
, let env' = Env.extend stageVar a' env
202+
, Just fixedEnv <- [fixupPlan env' plan]
203+
]
204+
-- - we keep this value the way it is and try to shrink some later value
205+
++ go (Env.extend stageVar a env) plan
206+
207+
-- Fix the rest of the plan given an environment `env` for the plan so far
208+
fixupPlan :: Env -> [SolverStage] -> Maybe Env
209+
fixupPlan env [] = pure env
210+
fixupPlan env ((unsafeSubstStage env -> SolverStage {..}) : plan) =
211+
case Env.lookup (env <> initialEnv) stageVar >>= fixupWithSpec stageSpec of
212+
Nothing -> Nothing
213+
Just a -> fixupPlan (Env.extend stageVar a env) plan
214+
215+
-- Try to fix a value w.r.t a specification
216+
fixupWithSpec :: forall a. HasSpec a => Specification a -> a -> Maybe a
217+
fixupWithSpec spec a
218+
| a `conformsToSpec` spec = Just a
219+
| otherwise = case spec of
220+
MemberSpec (a' :| _) -> Just a'
221+
TypeSpec ts _ -> fixupWithTypeSpec ts a >>= \ a' -> a' <$ guard (conformsToSpec a' spec)
222+
_ -> listToMaybe $ filter (`conformsToSpec` spec) (shrinkWithSpec TrueSpec a)
223+
167224
-- Debugging --------------------------------------------------------------
168225

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

198255
-- ---------------------- Building a plan -----------------------------------
199256

257+
unsafeSubstStage :: Env -> SolverStage -> SolverStage
258+
unsafeSubstStage env (SolverStage y ps spec relevant) =
259+
normalizeSolverStage $ SolverStage y (substPred env <$> ps) spec relevant
260+
200261
substStage :: HasSpec a => Set Name -> Var a -> a -> SolverStage -> SolverStage
201262
substStage rel' x val (SolverStage y ps spec relevant) =
202263
normalizeSolverStage $ SolverStage y (substPred env <$> ps) spec relevant'
@@ -1105,6 +1166,9 @@ instance (HasSpec a, HasSpec b, KnownNat (CountCases b)) => HasSpec (Sum a b) wh
11051166
shrinkWithTypeSpec (SumSpec _ sa _) (SumLeft a) = SumLeft <$> shrinkWithSpec sa a
11061167
shrinkWithTypeSpec (SumSpec _ _ sb) (SumRight b) = SumRight <$> shrinkWithSpec sb b
11071168

1169+
fixupWithTypeSpec (SumSpec _ sa _) (SumLeft a) = SumLeft <$> fixupWithSpec sa a
1170+
fixupWithTypeSpec (SumSpec _ _ sb) (SumRight b) = SumRight <$> fixupWithSpec sb b
1171+
11081172
toPreds ct (SumSpec h sa sb) =
11091173
Case
11101174
ct

src/Constrained/NumOrd.hs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ module Constrained.NumOrd (
3939
combineNumSpec,
4040
genFromNumSpec,
4141
shrinkWithNumSpec,
42+
fixupWithTypeSpec,
4243
conformsToNumSpec,
4344
toPredsNumSpec,
4445
OrdLike (..),
@@ -320,6 +321,11 @@ genFromNumSpec (NumSpecInterval ml mu) = do
320321
shrinkWithNumSpec :: Arbitrary n => NumSpec n -> n -> [n]
321322
shrinkWithNumSpec _ = shrink
322323

324+
-- TODO: fixme
325+
326+
fixupWithNumSpec :: Arbitrary n => NumSpec n -> n -> Maybe n
327+
fixupWithNumSpec _ = listToMaybe . shrink
328+
323329
constrainInterval ::
324330
(MonadGenError m, Ord a, Num a, Show a) => Maybe a -> Maybe a -> Integer -> m (a, a)
325331
constrainInterval ml mu r =
@@ -1073,6 +1079,7 @@ instance HasSpec Integer where
10731079
combineSpec = combineNumSpec
10741080
genFromTypeSpec = genFromNumSpec
10751081
shrinkWithTypeSpec = shrinkWithNumSpec
1082+
fixupWithTypeSpec = fixupWithNumSpec
10761083
conformsTo = conformsToNumSpec
10771084
toPreds = toPredsNumSpec
10781085
cardinalTypeSpec = cardinalNumSpec
@@ -1084,6 +1091,7 @@ instance HasSpec Int where
10841091
combineSpec = combineNumSpec
10851092
genFromTypeSpec = genFromNumSpec
10861093
shrinkWithTypeSpec = shrinkWithNumSpec
1094+
fixupWithTypeSpec = fixupWithNumSpec
10871095
conformsTo = conformsToNumSpec
10881096
toPreds = toPredsNumSpec
10891097
cardinalTypeSpec = cardinalNumSpec
@@ -1095,6 +1103,7 @@ instance HasSpec (Ratio Integer) where
10951103
combineSpec = combineNumSpec
10961104
genFromTypeSpec = genFromNumSpec
10971105
shrinkWithTypeSpec = shrinkWithNumSpec
1106+
fixupWithTypeSpec = fixupWithNumSpec
10981107
conformsTo = conformsToNumSpec
10991108
toPreds = toPredsNumSpec
11001109
cardinalTypeSpec _ = TrueSpec
@@ -1106,6 +1115,7 @@ instance HasSpec Natural where
11061115
combineSpec = combineNumSpec
11071116
genFromTypeSpec = genFromNumSpec
11081117
shrinkWithTypeSpec = shrinkWithNumSpec
1118+
fixupWithTypeSpec = fixupWithNumSpec
11091119
conformsTo = conformsToNumSpec
11101120
toPreds = toPredsNumSpec
11111121
cardinalTypeSpec (NumSpecInterval (fromMaybe 0 -> lo) (Just hi)) =
@@ -1121,6 +1131,7 @@ instance HasSpec Word8 where
11211131
combineSpec = combineNumSpec
11221132
genFromTypeSpec = genFromNumSpec
11231133
shrinkWithTypeSpec = shrinkWithNumSpec
1134+
fixupWithTypeSpec = fixupWithNumSpec
11241135
conformsTo = conformsToNumSpec
11251136
toPreds = toPredsNumSpec
11261137
cardinalTypeSpec = cardinalNumSpec
@@ -1134,6 +1145,7 @@ instance HasSpec Word16 where
11341145
combineSpec = combineNumSpec
11351146
genFromTypeSpec = genFromNumSpec
11361147
shrinkWithTypeSpec = shrinkWithNumSpec
1148+
fixupWithTypeSpec = fixupWithNumSpec
11371149
conformsTo = conformsToNumSpec
11381150
toPreds = toPredsNumSpec
11391151
cardinalTypeSpec = cardinalNumSpec
@@ -1146,6 +1158,7 @@ instance HasSpec Word32 where
11461158
combineSpec = combineNumSpec
11471159
genFromTypeSpec = genFromNumSpec
11481160
shrinkWithTypeSpec = shrinkWithNumSpec
1161+
fixupWithTypeSpec = fixupWithNumSpec
11491162
conformsTo = conformsToNumSpec
11501163
toPreds = toPredsNumSpec
11511164
cardinalTypeSpec = cardinalNumSpec
@@ -1157,6 +1170,7 @@ instance HasSpec Word64 where
11571170
combineSpec = combineNumSpec
11581171
genFromTypeSpec = genFromNumSpec
11591172
shrinkWithTypeSpec = shrinkWithNumSpec
1173+
fixupWithTypeSpec = fixupWithNumSpec
11601174
conformsTo = conformsToNumSpec
11611175
toPreds = toPredsNumSpec
11621176
cardinalTypeSpec = cardinalNumSpec
@@ -1168,6 +1182,7 @@ instance HasSpec Int8 where
11681182
combineSpec = combineNumSpec
11691183
genFromTypeSpec = genFromNumSpec
11701184
shrinkWithTypeSpec = shrinkWithNumSpec
1185+
fixupWithTypeSpec = fixupWithNumSpec
11711186
conformsTo = conformsToNumSpec
11721187
toPreds = toPredsNumSpec
11731188
cardinalTrueSpec = equalSpec 256
@@ -1180,6 +1195,7 @@ instance HasSpec Int16 where
11801195
combineSpec = combineNumSpec
11811196
genFromTypeSpec = genFromNumSpec
11821197
shrinkWithTypeSpec = shrinkWithNumSpec
1198+
fixupWithTypeSpec = fixupWithNumSpec
11831199
conformsTo = conformsToNumSpec
11841200
toPreds = toPredsNumSpec
11851201
cardinalTypeSpec = cardinalNumSpec
@@ -1192,6 +1208,7 @@ instance HasSpec Int32 where
11921208
combineSpec = combineNumSpec
11931209
genFromTypeSpec = genFromNumSpec
11941210
shrinkWithTypeSpec = shrinkWithNumSpec
1211+
fixupWithTypeSpec = fixupWithNumSpec
11951212
conformsTo = conformsToNumSpec
11961213
toPreds = toPredsNumSpec
11971214
cardinalTypeSpec = cardinalNumSpec
@@ -1203,6 +1220,7 @@ instance HasSpec Int64 where
12031220
combineSpec = combineNumSpec
12041221
genFromTypeSpec = genFromNumSpec
12051222
shrinkWithTypeSpec = shrinkWithNumSpec
1223+
fixupWithTypeSpec = fixupWithNumSpec
12061224
conformsTo = conformsToNumSpec
12071225
toPreds = toPredsNumSpec
12081226
cardinalTypeSpec = cardinalNumSpec
@@ -1214,6 +1232,7 @@ instance HasSpec Float where
12141232
combineSpec = combineNumSpec
12151233
genFromTypeSpec = genFromNumSpec
12161234
shrinkWithTypeSpec = shrinkWithNumSpec
1235+
fixupWithTypeSpec = fixupWithNumSpec
12171236
conformsTo = conformsToNumSpec
12181237
toPreds = toPredsNumSpec
12191238
cardinalTypeSpec _ = TrueSpec
@@ -1225,6 +1244,7 @@ instance HasSpec Double where
12251244
combineSpec = combineNumSpec
12261245
genFromTypeSpec = genFromNumSpec
12271246
shrinkWithTypeSpec = shrinkWithNumSpec
1247+
fixupWithTypeSpec = fixupWithNumSpec
12281248
conformsTo = conformsToNumSpec
12291249
toPreds = toPredsNumSpec
12301250
cardinalTypeSpec _ = TrueSpec

src/Constrained/Spec/List.hs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,9 @@ instance HasSpec a => HasSpec [a] where
244244
shrinkWithTypeSpec (ListSpec _ _ _ es _) as =
245245
shrinkList (shrinkWithSpec es) as
246246

247+
-- TODO: fixme
248+
fixupWithTypeSpec _ _ = Nothing
249+
247250
cardinalTypeSpec _ = TrueSpec
248251

249252
guardTypeSpec = guardListSpec

src/Constrained/Spec/Map.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,8 @@ instance
278278

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

281+
fixupWithTypeSpec _ _ = Nothing
282+
281283
toPreds m (MapSpec mHint mustKeys mustVals size kvs foldSpec) =
282284
toPred
283285
[ Assert $ Lit mustKeys `subset_` dom_ m

src/Constrained/Spec/Set.hs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,9 @@ instance (Ord a, HasSpec a) => HasSpec (Set a) where
204204

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

207+
-- TODO: fixme
208+
fixupWithTypeSpec _ _ = Nothing
209+
207210
toPreds s (SetSpec m es size) =
208211
fold $
209212
-- Don't include this if the must set is empty

src/Constrained/Spec/Tree.hs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ instance HasSpec a => HasSpec (Tree a) where
109109
| ts' <- shrinkList (shrinkWithTypeSpec (TreeSpec Nothing Nothing TrueSpec ctxSpec)) ts
110110
]
111111

112+
-- TODO: fixme
113+
fixupWithTypeSpec _ _ = Nothing
114+
112115
cardinalTypeSpec _ = mempty
113116

114117
toPreds t (TreeSpec mal msz rs s) =

src/Constrained/TheKnot.hs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,9 @@ instance (HasSpec a, HasSpec b) => HasSpec (Prod a b) where
136136
[Prod a' b | a' <- shrinkWithSpec sa a]
137137
++ [Prod a b' | b' <- shrinkWithSpec sb b]
138138

139+
fixupWithTypeSpec (Cartesian sa sb) (Prod a b) =
140+
Prod <$> fixupWithSpec sa a <*> fixupWithSpec sb b
141+
139142
toPreds x (Cartesian sf ss) =
140143
satisfies (prodFst_ x) sf
141144
<> satisfies (prodSnd_ x) ss

0 commit comments

Comments
 (0)