@@ -155,15 +155,70 @@ shrinkWithSpec (simplifySpec -> spec) a = filter (`conformsToSpec` spec) $ case
155155 ExplainSpec _ s -> shrinkWithSpec s a
156156 -- TODO: filter on can't if we have a known to be sound shrinker
157157 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
158+ SuspendedSpec x p -> shrinkFromPreds p x a
161159 MemberSpec {} -> shr a
162160 TrueSpec -> shr a
163161 ErrorSpec {} -> []
164162 where
165163 shr = shrinkWithTypeSpec (emptySpec @ a )
166164
165+ shrinkFromPreds :: HasSpec a => Pred -> Var a -> a -> [a ]
166+ shrinkFromPreds p
167+ | Result plan <- prepareLinearization p = \ x a -> listFromGE $ do
168+ -- NOTE: we do this to e.g. guard against bad construction functions in Exists
169+ xaGood <- checkPred (Env. singleton x a) p
170+ unless xaGood $
171+ fatalError " Trying to shrink a bad value, don't do that!"
172+ -- Get an `env` for the original value
173+ initialEnv <- envFromPred (Env. singleton x a) p
174+ return
175+ [ a'
176+ | -- Shrink the initialEnv
177+ env' <- shrinkEnvFromPlan initialEnv plan
178+ , -- Get the value of the constrained variable `x` in the shrunk env
179+ Just a' <- [Env. lookup env' x]
180+ , -- NOTE: this is necessary because it's possible that changing
181+ -- a particular value in the env during shrinking might not result
182+ -- in the value of `x` changing and there is no better way to know than
183+ -- to do this.
184+ a' /= a
185+ ]
186+ | otherwise = error " Bad pred"
187+
188+ -- Start with a valid Env for the plan and try to shrink it
189+ shrinkEnvFromPlan :: Env -> SolverPlan -> [Env ]
190+ shrinkEnvFromPlan initialEnv SolverPlan {.. } = go mempty solverPlan
191+ where
192+ go :: Env -> [SolverStage ] -> [Env ]
193+ go _ [] = [] -- In this case we decided to keep every variable the same so nothing to return
194+ go env ((unsafeSubstStage env -> SolverStage {.. }) : plan) = do
195+ Just a <- [Env. lookup initialEnv stageVar]
196+ -- Two cases:
197+ -- - either we shrink this value and try to fixup every value later on in the plan or
198+ [ fixedEnv
199+ | a' <- shrinkWithSpec stageSpec a
200+ , let env' = Env. extend stageVar a' env
201+ , Just fixedEnv <- [fixupPlan env' plan]
202+ ]
203+ -- - we keep this value the way it is and try to shrink some later value
204+ ++ go (Env. extend stageVar a env) plan
205+
206+ -- Fix the rest of the plan given an environment `env` for the plan so far
207+ fixupPlan :: Env -> [SolverStage ] -> Maybe Env
208+ fixupPlan env [] = pure env
209+ fixupPlan env ((unsafeSubstStage env -> SolverStage {.. }) : plan) =
210+ case Env. lookup initialEnv stageVar >>= fixupWithSpec stageSpec of
211+ Nothing -> Nothing
212+ Just a -> fixupPlan (Env. extend stageVar a env) plan
213+
214+ -- Try to fix a value w.r.t a specification
215+ fixupWithSpec :: forall a . HasSpec a => Specification a -> a -> Maybe a
216+ fixupWithSpec spec a
217+ | a `conformsToSpec` spec = Just a
218+ | otherwise = case spec of
219+ MemberSpec (a' :| _) -> Just a'
220+ _ -> listToMaybe $ filter (`conformsToSpec` spec) (shrinkWithSpec TrueSpec a)
221+
167222-- Debugging --------------------------------------------------------------
168223
169224-- | A version of `genFromSpecT` that runs in the IO monad. Good for debugging.
@@ -197,6 +252,10 @@ prettyPlan (simplifySpec -> spec)
197252
198253-- ---------------------- Building a plan -----------------------------------
199254
255+ unsafeSubstStage :: Env -> SolverStage -> SolverStage
256+ unsafeSubstStage env (SolverStage y ps spec relevant) =
257+ normalizeSolverStage $ SolverStage y (substPred env <$> ps) spec relevant
258+
200259substStage :: HasSpec a => Set Name -> Var a -> a -> SolverStage -> SolverStage
201260substStage rel' x val (SolverStage y ps spec relevant) =
202261 normalizeSolverStage $ SolverStage y (substPred env <$> ps) spec relevant'
0 commit comments