@@ -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+
200261substStage :: HasSpec a => Set Name -> Var a -> a -> SolverStage -> SolverStage
201262substStage 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
0 commit comments