• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

input-output-hk / constrained-generators / 371

04 Nov 2025 12:59PM UTC coverage: 77.171% (+0.06%) from 77.113%
371

push

github

web-flow
Improve shrinking performance (#53)

16 of 17 new or added lines in 1 file covered. (94.12%)

89 existing lines in 2 files now uncovered.

3901 of 5055 relevant lines covered (77.17%)

1.46 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.4
/src/Constrained/Generation.hs
1
{-# LANGUAGE AllowAmbiguousTypes #-}
2
{-# LANGUAGE BangPatterns #-}
3
{-# LANGUAGE DataKinds #-}
4
{-# LANGUAGE FlexibleContexts #-}
5
{-# LANGUAGE GADTs #-}
6
{-# LANGUAGE ImportQualifiedPost #-}
7
{-# LANGUAGE LambdaCase #-}
8
{-# LANGUAGE OverloadedStrings #-}
9
{-# LANGUAGE PatternSynonyms #-}
10
{-# LANGUAGE RankNTypes #-}
11
{-# LANGUAGE RecordWildCards #-}
12
{-# LANGUAGE ScopedTypeVariables #-}
13
{-# LANGUAGE StandaloneDeriving #-}
14
{-# LANGUAGE TypeApplications #-}
15
{-# LANGUAGE TypeFamilies #-}
16
{-# LANGUAGE TypeOperators #-}
17
{-# LANGUAGE UndecidableInstances #-}
18
{-# LANGUAGE ViewPatterns #-}
19
{-# OPTIONS_GHC -Wno-orphans #-}
20

21
-- | All the things that are necessary for generation and shrinking.
22
module Constrained.Generation (
23
  -- * Generation and shrinking
24
  genFromSpec,
25
  genFromSpecT,
26
  genFromSpecWithSeed,
27
  shrinkWithSpec,
28
  fixupWithSpec,
29
  simplifySpec,
30

31
  -- ** Debugging
32
  printPlan,
33
  debugSpec,
34
  prettyPlan,
35

36
  -- * Function Symbols
37
  or_,
38
  not_,
39
  injRight_,
40
  injLeft_,
41
  (==.),
42

43
  -- * Other syntax
44
  whenTrue,
45

46
  -- * Internals
47
  CountCases,
48
  SumW (..),
49
  BoolW (..),
50
  EqW (..),
51
  SumSpec (..),
52
  pattern SumSpec,
53

54
  mapSpec,
55
  forwardPropagateSpec,
56
) where
57

58
import Constrained.AbstractSyntax
59
import Constrained.Base
60
import Constrained.Conformance
61
import Constrained.Core
62
import Constrained.Env (Env)
63
import Constrained.Env qualified as Env
64
import Constrained.FunctionSymbol
65
import Constrained.GenT
66
import Constrained.Generic
67
import Constrained.Graph hiding (irreflexiveDependencyOn)
68
import Constrained.List
69
import Constrained.NumOrd
70
import Constrained.PrettyUtils
71
import Constrained.Syntax
72
import Control.Applicative
73
import Control.Monad
74
import Control.Monad.Writer (Writer, runWriter, tell)
75
import Data.Foldable
76
import Data.Int
77
import Data.Kind
78
import Data.List (partition)
79
import Data.List.NonEmpty qualified as NE
80
import Data.Maybe
81
import Data.Semigroup (Any (..), getSum)
82
import Data.Semigroup qualified as Semigroup
83
import Data.Set (Set)
84
import Data.Set qualified as Set
85
import Data.String
86
import Data.Typeable
87
import GHC.Stack
88
import GHC.TypeLits
89
import Prettyprinter hiding (cat)
90
import Test.QuickCheck hiding (Args, Fun, Witness, forAll, witness)
91
import Test.QuickCheck.Gen
92
import Test.QuickCheck.Random hiding (left, right)
93
import Prelude hiding (cycle, pred)
94

95
------------------------------------------------------------------------
96
-- Generation, shrinking, and debugging
97
------------------------------------------------------------------------
98

99
-- | Generate a value that satisfies the spec. This function can fail if the
100
-- spec is inconsistent, there is a dependency error, or if the underlying
101
-- generators are not flexible enough.
102
genFromSpecT ::
103
  forall a m. (HasCallStack, HasSpec a, MonadGenError m) => Specification a -> GenT m a
104
genFromSpecT (simplifySpec -> spec) = case spec of
2✔
105
  ExplainSpec [] s -> genFromSpecT s
×
106
  ExplainSpec es s -> push es (genFromSpecT s)
2✔
107
  MemberSpec as -> explain ("genFromSpecT on spec" ++ show spec) $ pureGen (elements (NE.toList as))
1✔
108
  TrueSpec -> genFromSpecT (typeSpec $ emptySpec @a)
2✔
109
  SuspendedSpec x p
110
    -- NOTE: If `x` isn't free in `p` we still have to try to generate things
111
    -- from `p` to make sure `p` is sat and then we can throw it away. A better
112
    -- approach would be to only do this in the case where we don't know if `p`
113
    -- is sat. The proper way to implement such a sat check is to remove
114
    -- sat-but-unnecessary variables in the optimiser.
115
    | not $ Name x `appearsIn` p -> do
2✔
116
        !_ <- genFromPreds mempty p
2✔
117
        genFromSpecT TrueSpec
2✔
118
    | otherwise -> do
1✔
119
        env <- genFromPreds mempty p
2✔
120
        Env.find env x
2✔
121
  TypeSpec s cant -> do
2✔
122
    mode <- getMode
2✔
123
    explainNE
2✔
124
      ( NE.fromList
2✔
125
          [ "genFromSpecT on (TypeSpec tspec cant) at type " ++ showType @a
2✔
126
          , "tspec = "
2✔
127
          , show s
2✔
128
          , "cant = " ++ show cant
2✔
129
          , "with mode " ++ show mode
2✔
130
          ]
131
      )
132
      $
133
      -- TODO: we could consider giving `cant` as an argument to `genFromTypeSpec` if this
134
      -- starts giving us trouble.
135
      genFromTypeSpec s `suchThatT` (`notElem` cant)
2✔
136
  ErrorSpec e -> genErrorNE e
2✔
137

138
-- | A version of `genFromSpecT` that simply errors if the generator fails
139
genFromSpec :: forall a. (HasCallStack, HasSpec a) => Specification a -> Gen a
140
genFromSpec spec = do
2✔
141
  res <- catchGen $ genFromSpecT @a @GE spec
2✔
142
  either (error . ('\n' :) . catMessages) pure res
1✔
143

144
-- | A version of `genFromSpecT` that takes a seed and a size and gives you a result
145
genFromSpecWithSeed ::
146
  forall a. (HasCallStack, HasSpec a) => Int -> Int -> Specification a -> a
147
genFromSpecWithSeed seed size spec = unGen (genFromSpec spec) (mkQCGen seed) size
×
148

149
-- ----------------------- Shrinking -------------------------------
150

151
unconstrainedShrink :: forall a. HasSpec a => a -> [a]
152
unconstrainedShrink = shrinkWithTypeSpec (emptySpec @a)
2✔
153

154
-- | Shrink a value while preserving adherence to a `Specification`
155
shrinkWithSpec :: forall a. HasSpec a => Specification a -> a -> [a]
156
shrinkWithSpec (ExplainSpec _ s) a = shrinkWithSpec s a
2✔
157
shrinkWithSpec (simplifySpec -> spec) a = case spec of
2✔
158
  -- TODO: It would be nice to avoid the extra `conformsToSpec` check here and only look
159
  -- at the cant set instead
160
  TypeSpec s _ -> [ a' | a' <- shrinkWithTypeSpec s a, a' `conformsToSpec` spec ]
2✔
161
  SuspendedSpec x p -> shrinkFromPreds p x a
2✔
162
  -- TODO: it would be nice if there was some better way of doing this
163
  MemberSpec as -> [ a' | a' <- unconstrainedShrink a, a' `elem` as ]
2✔
164
  TrueSpec -> unconstrainedShrink a
2✔
UNCOV
165
  ErrorSpec {} -> []
×
166
  -- Should be impossible?
NEW
167
  ExplainSpec _ s -> shrinkWithSpec s a
×
168

169
shrinkFromPreds :: forall a. HasSpec a => Pred -> Var a -> a -> [a]
170
shrinkFromPreds p
2✔
171
  | Result plan <- prepareLinearization p = \x a -> listFromGE $ do
2✔
172
      -- NOTE: we do this to e.g. guard against bad construction functions in Exists
173
      case checkPredE (Env.singleton x a) (NE.fromList []) p of
1✔
174
        Nothing -> pure ()
1✔
UNCOV
175
        Just err -> explainNE err $ fatalError "Trying to shrink a bad value, don't do that!"
×
176
      if not $ Name x `appearsIn` p -- NOTE: this is safe because we just checked that p is SAT above
2✔
177
      then return $ unconstrainedShrink a
2✔
178
      else do
2✔
179
        -- Get an `env` for the original value
180
        initialEnv <- envFromPred (Env.singleton x a) p
2✔
181
        return
2✔
182
          [ a'
2✔
183
          | -- Shrink the initialEnv
184
          env' <- shrinkEnvFromPlan initialEnv plan
2✔
185
          , -- Get the value of the constrained variable `x` in the shrunk env
186
          Just a' <- [Env.lookup env' x]
2✔
187
          , -- NOTE: this is necessary because it's possible that changing
188
          -- a particular value in the env during shrinking might not result
189
          -- in the value of `x` changing and there is no better way to know than
190
          -- to do this.
191
          a' /= a
2✔
192
          ]
UNCOV
193
  | otherwise = error "Bad pred"
×
194

195
-- Start with a valid Env for the plan and try to shrink it
196
shrinkEnvFromPlan :: Env -> SolverPlan -> [Env]
197
shrinkEnvFromPlan initialEnv SolverPlan {..} = go mempty solverPlan
2✔
198
  where
199
    go :: Env -> [SolverStage] -> [Env]
200
    go _ [] = [] -- In this case we decided to keep every variable the same so nothing to return
2✔
201
    go env ((unsafeSubstStage env -> SolverStage {..}) : plan) = do
2✔
202
      Just a <- [Env.lookup initialEnv stageVar]
2✔
203
      -- Two cases:
204
      --  - either we shrink this value and try to fixup every value later on in the plan or
205
      [ fixedEnv
2✔
206
        | a' <- shrinkWithSpec stageSpec a
2✔
207
        , let env' = Env.extend stageVar a' env
2✔
208
        , Just fixedEnv <- [fixupPlan env' plan]
2✔
209
        ]
210
        --  - we keep this value the way it is and try to shrink some later value
211
        ++ go (Env.extend stageVar a env) plan
2✔
212

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

221
-- Try to fix a value w.r.t a specification
222
fixupWithSpec :: forall a. HasSpec a => Specification a -> a -> Maybe a
223
fixupWithSpec spec a
2✔
224
  | a `conformsToSpec` spec = Just a
2✔
225
  | otherwise = case spec of
1✔
226
      MemberSpec (a' :| _) -> Just a'
2✔
227
      TypeSpec ts _ -> fixupWithTypeSpec ts a >>= \ a' -> a' <$  guard (conformsToSpec a' spec)
2✔
228
      _ -> listToMaybe $ filter (`conformsToSpec` spec) (shrinkWithSpec TrueSpec a)
2✔
229

230
-- Debugging --------------------------------------------------------------
231

232
-- | A version of `genFromSpecT` that runs in the IO monad. Good for debugging.
233
debugSpec :: forall a. HasSpec a => Specification a -> IO ()
UNCOV
234
debugSpec spec = do
×
UNCOV
235
  ans <- generate $ genFromGenT $ inspect (genFromSpecT spec)
×
236
  let f x = putStrLn (unlines (NE.toList x))
×
237
      ok x =
×
238
        if conformsToSpec x spec
×
239
          then putStrLn "True"
×
240
          else putStrLn "False, perhaps there is an unsafeExists in the spec?"
×
241
  case ans of
×
242
    FatalError xs -> mapM_ f xs
×
243
    GenError xs -> mapM_ f xs
×
244
    Result x -> print spec >> print x >> ok x
×
245

246
-- | Pretty-print the plan for a `Specifcation` in the terminal for debugging
247
printPlan :: HasSpec a => Specification a -> IO ()
UNCOV
248
printPlan = print . prettyPlan
×
249

250
-- | Plan pretty-printer for debugging
251
prettyPlan :: HasSpec a => Specification a -> Doc ann
UNCOV
252
prettyPlan (simplifySpec -> spec)
×
UNCOV
253
  | SuspendedSpec _ p <- spec
×
254
  , Result plan <- prepareLinearization p =
×
255
      vsep'
×
256
        [ "Simplified spec:" /> pretty spec
×
257
        , pretty plan
×
258
        ]
259
  | otherwise = "Simplfied spec:" /> pretty spec
×
260

261
-- ---------------------- Building a plan -----------------------------------
262

263
unsafeSubstStage :: Env -> SolverStage -> SolverStage
264
unsafeSubstStage env (SolverStage y ps spec relevant) =
2✔
265
  normalizeSolverStage $ SolverStage y (substPred env <$> ps) spec relevant
1✔
266

267
substStage :: HasSpec a => Set Name -> Var a -> a -> SolverStage -> SolverStage
268
substStage rel' x val (SolverStage y ps spec relevant) =
2✔
269
  normalizeSolverStage $ SolverStage y (substPred env <$> ps) spec relevant'
2✔
270
  where env = Env.singleton x val
2✔
271
        relevant' | Name x `appearsIn` ps = rel' <> relevant
2✔
272
                  | otherwise = relevant
1✔
273

274
normalizeSolverStage :: SolverStage -> SolverStage
275
normalizeSolverStage (SolverStage x ps spec relevant) = SolverStage x ps'' (spec <> spec') relevant
2✔
276
  where
277
    (ps', ps'') = partition ((1 ==) . Set.size . freeVarSet) ps
2✔
278
    spec' = fromGESpec $ computeSpec x (And ps')
2✔
279

280
-- TODO: here we can compute both the explicit hints (i.e. constraints that
281
-- define the order of two variables) and any whole-program smarts.
282
computeHints :: [Pred] -> Hints
283
computeHints ps =
2✔
284
  transitiveClosure $ fold [x `irreflexiveDependencyOn` y | DependsOn x y <- ps]
2✔
285

286
-- | Linearize a predicate, turning it into a list of variables to solve and
287
-- their defining constraints such that each variable can be solved independently.
288
prepareLinearization :: Pred -> GE SolverPlan
289
prepareLinearization p = do
2✔
290
  let preds = concatMap saturatePred $ flattenPred p
2✔
291
      hints = computeHints preds
2✔
292
      graph = transitiveClosure $ hints <> respecting hints (foldMap computeDependencies preds)
2✔
293
  plan <-
294
    explainNE
2✔
UNCOV
295
      ( NE.fromList
×
UNCOV
296
          [ "Linearizing"
×
297
          , show $
×
298
              "  preds: "
×
299
                <> pretty (take 3 preds)
×
300
                <> (if length preds > 3 then fromString (" ... " ++ show (length preds - 3) ++ " more.") else "")
×
301
          , show $ "  graph: " <> pretty graph
×
302
          ]
303
      )
304
      $ linearize preds graph
2✔
305
  pure $ backPropagation mempty $ SolverPlan plan
2✔
306

307
-- | Flatten nested `Let`, `Exists`, and `And` in a `Pred fn`. `Let` and
308
-- `Exists` bound variables become free in the result.
309
flattenPred :: Pred -> [Pred]
310
flattenPred pIn = go (freeVarNames pIn) [pIn]
2✔
311
  where
312
    go _ [] = []
2✔
313
    go fvs (p : ps) = case p of
2✔
314
      And ps' -> go fvs (ps' ++ ps)
2✔
315
      -- NOTE: the order of the arguments to `==.` here are important.
316
      -- The whole point of `Let` is that it allows us to solve all of `t`
317
      -- before we solve the variables in `t`.
318
      Let t b -> goBinder fvs b ps (\x -> (assert (t ==. (V x)) :))
2✔
319
      Exists _ b -> goBinder fvs b ps (const id)
2✔
320
      When b pp -> map (When b) (go fvs [pp]) ++ go fvs ps
2✔
321
      Explain es pp -> map (explanation es) (go fvs [pp]) ++ go fvs ps
2✔
322
      _ -> p : go fvs ps
2✔
323

324
    goBinder ::
325
      Set Int ->
326
      Binder a ->
327
      [Pred] ->
328
      (HasSpec a => Var a -> [Pred] -> [Pred]) ->
329
      [Pred]
330
    goBinder fvs (x :-> p) ps k = k x' $ go (Set.insert (nameOf x') fvs) (p' : ps)
2✔
331
      where
332
        (x', p') = freshen x p fvs
2✔
333

334
-- Consider: A + B = C + D
335
-- We want to fail if A and B are independent.
336
-- Consider: A + B = A + C, A <- B
337
-- Here we want to consider this constraint defining for A
338
linearize ::
339
  MonadGenError m => [Pred] -> DependGraph -> m [SolverStage]
340
linearize preds graph = do
2✔
341
  sorted <- case topsort graph of
2✔
342
    Left cycle ->
UNCOV
343
      fatalError
×
UNCOV
344
        ( show $
×
345
            "linearize: Dependency cycle in graph:"
×
346
              /> vsep'
×
347
                [ "cycle:" /> pretty cycle
×
348
                , "graph:" /> pretty graph
×
349
                ]
350
        )
351
    Right sorted -> pure sorted
2✔
352
  go sorted [(freeVarSet ps, ps) | ps <- filter isRelevantPred preds]
2✔
353
  where
354
    isRelevantPred TruePred = False
2✔
355
    isRelevantPred DependsOn {} = False
2✔
356
    isRelevantPred (Assert (Lit True)) = False
2✔
357
    isRelevantPred _ = True
2✔
358

359
    go [] [] = pure []
2✔
360
    go [] ps
361
      | null $ foldMap fst ps =
1✔
362
          case checkPredsE (pure "Linearizing fails") mempty (map snd ps) of
1✔
363
            Nothing -> pure []
2✔
364
            Just msgs -> genErrorNE msgs
1✔
UNCOV
365
      | otherwise =
×
UNCOV
366
          fatalErrorNE $
×
367
            NE.fromList
×
368
              [ "Dependency error in `linearize`: "
×
369
              , show $ indent 2 $ "graph: " /> pretty graph
×
370
              , show $
×
371
                  indent 2 $
×
372
                    "the following left-over constraints are not defining constraints for a unique variable:"
×
373
                      /> vsep' (map (pretty . snd) ps)
×
374
              ]
375
    go (n@(Name x) : ns) ps = do
2✔
376
      let (nps, ops) = partition (isLastVariable n . fst) ps
2✔
377
      (normalizeSolverStage (SolverStage x (map snd nps) mempty mempty):) <$> go ns ops
2✔
378

379
    isLastVariable n set = n `Set.member` set && solvableFrom n (Set.delete n set) graph
2✔
380

381
------------------------------------------------------------------------
382
-- Simplification of Specifications
383
------------------------------------------------------------------------
384

385
-- | Spec simplification, use with care and don't modify the spec after using this!
386
simplifySpec :: HasSpec a => Specification a -> Specification a
387
simplifySpec spec = case applyNameHints spec of
2✔
388
  SuspendedSpec x p ->
389
    let optP = optimisePred p
2✔
390
     in fromGESpec $
2✔
391
          explain
2✔
392
            ("\nWhile calling simplifySpec on var " ++ show x ++ "\noptP=\n" ++ show optP ++ "\n")
2✔
393
            (computeSpecSimplified x optP)
2✔
394
  MemberSpec xs -> MemberSpec xs
2✔
395
  ErrorSpec es -> ErrorSpec es
1✔
396
  TypeSpec ts cant -> TypeSpec ts cant
2✔
397
  TrueSpec -> TrueSpec
2✔
398
  ExplainSpec es s -> explainSpec es (simplifySpec s)
2✔
399

400
-- ------- Stages of simplifying -------------------------------
401

402
-- TODO: it might be necessary to run aggressiveInlining again after the let floating etc.
403
optimisePred :: Pred -> Pred
404
optimisePred p =
2✔
405
  simplifyPred
2✔
406
    . letSubexpressionElimination
2✔
407
    . letFloating
2✔
408
    . aggressiveInlining
2✔
409
    . simplifyPred
2✔
410
    $ p
2✔
411

412
aggressiveInlining :: Pred -> Pred
413
aggressiveInlining pred
2✔
414
  | inlined = aggressiveInlining pInlined
2✔
415
  | otherwise = pred
1✔
416
  where
417
    (pInlined, Any inlined) = runWriter $ go (freeVars pred) [] pred
2✔
418

419
    underBinder fvs x p = fvs `without` [Name x] <> singleton (Name x) (countOf (Name x) p)
2✔
420

421
    underBinderSub :: HasSpec a => Subst -> Var a -> Subst
422
    underBinderSub sub x =
2✔
423
      [ x' := t
2✔
424
      | x' := t <- sub
2✔
425
      , isNothing $ eqVar x x'
1✔
426
      ]
427

428
    -- NOTE: this is safe because we only use the `Subst` when it results in a literal so there
429
    -- is no risk of variable capture.
430
    goBinder :: FreeVars -> Subst -> Binder a -> Writer Any (Binder a)
431
    goBinder fvs sub (x :-> p) = (x :->) <$> go (underBinder fvs x p) (underBinderSub sub x) p
2✔
432

433
    -- Check that the name `n` is only ever used as the only variable
434
    -- in the expressions where it appears. This ensures that it doesn't
435
    -- interact with anything.
436
    onlyUsedUniquely n p = case p of
2✔
437
      Assert t
438
        | n `appearsIn` t -> Set.size (freeVarSet t) == 1
2✔
439
        | otherwise -> True
1✔
440
      And ps -> all (onlyUsedUniquely n) ps
2✔
441
      -- TODO: we can (and should) probably add a bunch of cases to this.
442
      _ -> False
2✔
443

444
    go fvs sub pred2 = case pred2 of
2✔
445
      ElemPred bool t xs
446
        | not (isLit t)
1✔
447
        , Lit a <- substituteAndSimplifyTerm sub t -> do
1✔
UNCOV
448
            tell $ Any True
×
UNCOV
449
            pure $ ElemPred bool (Lit a) xs
×
450
        | otherwise -> pure $ ElemPred bool t xs
1✔
451
      Subst x t p -> go fvs sub (substitutePred x t p)
×
452
      Reifies t' t f
453
        | not (isLit t)
1✔
454
        , Lit a <- substituteAndSimplifyTerm sub t -> do
1✔
UNCOV
455
            tell $ Any True
×
UNCOV
456
            pure $ Reifies t' (Lit a) f
×
457
        | otherwise -> pure $ Reifies t' t f
1✔
458
      ForAll set b
459
        | not (isLit set)
1✔
460
        , Lit a <- substituteAndSimplifyTerm sub set -> do
2✔
461
            tell $ Any True
2✔
462
            pure $ foldMap (`unBind` b) (forAllToList a)
2✔
463
        | otherwise -> ForAll set <$> goBinder fvs sub b
1✔
464
      Case t bs
465
        | not (isLit t)
1✔
466
        , Lit a <- substituteAndSimplifyTerm sub t -> do
1✔
UNCOV
467
            tell $ Any True
×
UNCOV
468
            pure $ runCaseOn a (mapList thing bs) $ \x v p -> substPred (Env.singleton x v) p
×
469
        | (Weighted w (x :-> p) :> Nil) <- bs -> do
1✔
470
            let t' = substituteAndSimplifyTerm sub t
×
UNCOV
471
            p' <- go (underBinder fvs x p) (x := t' : sub) p
×
472
            pure $ Case t (Weighted w (x :-> p') :> Nil)
×
473
        | otherwise -> Case t <$> mapMList (traverseWeighted $ goBinder fvs sub) bs
1✔
474
      When b tp
475
        | not (isLit b)
1✔
476
        , Lit a <- substituteAndSimplifyTerm sub b -> do
1✔
UNCOV
477
            tell $ Any True
×
UNCOV
478
            pure $ if a then tp else TruePred
×
479
        | otherwise -> whenTrue b <$> go fvs sub tp
1✔
480
      Let t (x :-> p)
481
        | all (\n -> count n fvs <= 1) (freeVarSet t) -> do
2✔
482
            tell $ Any True
2✔
483
            pure $ substitutePred x t p
2✔
484
        | onlyUsedUniquely (Name x) p -> do
2✔
485
            tell $ Any True
2✔
486
            pure $ substitutePred x t p
2✔
487
        | not $ Name x `appearsIn` p -> do
2✔
488
            tell $ Any True
2✔
489
            pure p
2✔
490
        | not (isLit t)
1✔
491
        , Lit a <- substituteAndSimplifyTerm sub t -> do
1✔
UNCOV
492
            tell $ Any True
×
UNCOV
493
            pure $ unBind a (x :-> p)
×
494
        | otherwise -> Let t . (x :->) <$> go (underBinder fvs x p) (x := t : sub) p
1✔
495
      Exists k b -> Exists k <$> goBinder fvs sub b
2✔
496
      And ps -> fold <$> mapM (go fvs sub) ps
2✔
497
      Assert t
498
        | not (isLit t)
2✔
499
        , Lit b <- substituteAndSimplifyTerm sub t -> do
1✔
UNCOV
500
            tell $ Any True
×
UNCOV
501
            pure $ toPred b
×
502
        | otherwise -> pure pred2
1✔
503
      -- If the term turns into a literal, there is no more generation to do here
504
      -- so we can ignore the `GenHint`
505
      GenHint _ t
506
        | not (isLit t)
1✔
507
        , Lit {} <- substituteAndSimplifyTerm sub t -> do
1✔
UNCOV
508
            tell $ Any True
×
UNCOV
509
            pure TruePred
×
510
        | otherwise -> pure pred2
1✔
511
      DependsOn t t'
512
        | not (isLit t)
1✔
513
        , Lit {} <- substituteAndSimplifyTerm sub t -> do
1✔
UNCOV
514
            tell $ Any True
×
UNCOV
515
            pure $ TruePred
×
516
        | not (isLit t')
1✔
517
        , Lit {} <- substituteAndSimplifyTerm sub t' -> do
1✔
UNCOV
518
            tell $ Any True
×
UNCOV
519
            pure $ TruePred
×
520
        | otherwise -> pure pred2
1✔
521
      TruePred -> pure pred2
2✔
522
      FalsePred {} -> pure pred2
1✔
UNCOV
523
      Monitor {} -> pure pred2
×
524
      Explain es p -> Explain es <$> go fvs sub p
1✔
525

526
-- | Apply a substitution and simplify the resulting term if the
527
-- substitution changed the term.
528
substituteAndSimplifyTerm :: Subst -> Term a -> Term a
529
substituteAndSimplifyTerm sub t =
2✔
530
  case runWriter $ substituteTerm' sub t of
2✔
531
    (t', Any b)
532
      | b -> simplifyTerm t'
2✔
533
      | otherwise -> t'
1✔
534

535
-- | Simplify a Term, if the Term is an 'App', apply the rewrite rules
536
--   chosen by the (Logic sym t bs a) instance attached
537
--   to the function witness 'f'
538
simplifyTerm :: forall a. Term a -> Term a
539
simplifyTerm = \case
2✔
540
  V v -> V v
2✔
541
  Lit l -> Lit l
2✔
542
  App (f :: t bs a) (mapList simplifyTerm -> ts)
543
    | Just vs <- fromLits ts -> Lit $ uncurryList_ unValue (semantics f) vs
2✔
544
    | Just t <- rewriteRules f ts (Evidence @(AppRequires t bs a)) -> simplifyTerm t
2✔
545
    | otherwise -> App f ts
1✔
546

547
simplifyPred :: Pred -> Pred
548
simplifyPred = \case
2✔
549
  -- If the term simplifies away to a literal, that means there is no
550
  -- more generation to do so we can get rid of `GenHint`
551
  GenHint h t -> case simplifyTerm t of
2✔
UNCOV
552
    Lit {} -> TruePred
×
553
    t' -> GenHint h t'
2✔
554
  p@(ElemPred bool t xs) -> case simplifyTerm t of
2✔
555
    Lit x -> case (elem x xs, bool) of
2✔
556
      (True, True) -> TruePred
2✔
557
      (True, False) -> FalsePred ("notElemPred reduces to True" :| [show p])
1✔
558
      (False, True) -> FalsePred ("elemPred reduces to False" :| [show p])
1✔
559
      (False, False) -> TruePred
2✔
560
    t' -> ElemPred bool t' xs
2✔
561
  Subst x t p -> simplifyPred $ substitutePred x t p
2✔
562
  Assert t -> Assert $ simplifyTerm t
2✔
563
  Reifies t' t f -> case simplifyTerm t of
2✔
564
    Lit a ->
565
      -- Assert $ simplifyTerm t' ==. Lit (f a)
566
      ElemPred True (simplifyTerm t') (pure (f a))
2✔
567
    t'' -> Reifies (simplifyTerm t') t'' f
2✔
568
  ForAll (ts :: Term t) (b :: Binder a) -> case simplifyTerm ts of
2✔
569
    Lit as -> foldMap (`unBind` b) (forAllToList as)
2✔
570
    -- (App (extractW (UnionW @t) -> Just Refl) xs) -> error "MADE IT"
571
    {- Has to wait until we have HasSpec(Set a) instance
572
    UnionPat (xs :: Term (Set a)) ys ->
573
       let b' = simplifyBinder b
574
       in mkForAll xs b' <> mkForAll ys b' -}
575
    set' -> case simplifyBinder b of
2✔
576
      _ :-> TruePred -> TruePred
2✔
577
      b' -> ForAll set' b'
2✔
578
  DependsOn _ Lit {} -> TruePred
2✔
UNCOV
579
  DependsOn Lit {} _ -> TruePred
×
580
  DependsOn x y -> DependsOn x y
2✔
581
  -- Here is where we need the SumSpec instance
582
  Case t bs -> mkCase (simplifyTerm t) (mapList (mapWeighted simplifyBinder) bs)
2✔
583
  When b p -> whenTrue (simplifyTerm b) (simplifyPred p)
2✔
584
  TruePred -> TruePred
2✔
585
  FalsePred es -> FalsePred es
1✔
586
  And ps -> fold (simplifyPreds ps)
2✔
587
  Let t b -> case simplifyTerm t of
2✔
588
    t'@App {} -> Let t' (simplifyBinder b)
2✔
589
    -- Variable or literal
590
    t' | x :-> p <- b -> simplifyPred $ substitutePred x t' p
2✔
591
  Exists k b -> case simplifyBinder b of
2✔
592
    _ :-> TruePred -> TruePred
2✔
593
    -- This is to get rid of exisentials like:
594
    -- `constrained $ \ x -> exists $ \ y -> [x ==. y, y + 2 <. 10]`
595
    x :-> p | Just t <- pinnedBy x p -> simplifyPred $ substitutePred x t p
2✔
596
    b' -> Exists k b'
2✔
597
  Monitor {} -> TruePred
2✔
598
  -- TODO: This is a bit questionable. On the one hand we could get rid of `Explain` here
599
  -- and just return `simplifyPred p` but doing so risks missing explanations when things
600
  -- do go wrong.
601
  Explain es p -> explanation es $ simplifyPred p
2✔
602

603
simplifyPreds :: [Pred] -> [Pred]
604
simplifyPreds = go [] . map simplifyPred
2✔
605
  where
606
    go acc [] = reverse acc
2✔
607
    go _ (FalsePred err : _) = [FalsePred err]
1✔
608
    go acc (TruePred : ps) = go acc ps
2✔
609
    go acc (p : ps) = go (p : acc) ps
2✔
610

611
simplifyBinder :: Binder a -> Binder a
612
simplifyBinder (x :-> p) = x :-> simplifyPred p
2✔
613

614
-- TODO: this can probably be cleaned up and generalized along with generalizing
615
-- to make sure we float lets in some missing cases.
616
letFloating :: Pred -> Pred
617
letFloating = fold . go []
2✔
618
  where
619
    goBlock ctx ps = goBlock' (freeVarNames ctx <> freeVarNames ps) ctx ps
2✔
620

621
    goBlock' :: Set Int -> [Pred] -> [Pred] -> [Pred]
622
    goBlock' _ ctx [] = ctx
2✔
623
    goBlock' fvs ctx (Let t (x :-> p) : ps) =
624
      -- We can do `goBlock'` here because we've already done let floating
625
      -- on the inner `p`
626
      [Let t (x' :-> fold (goBlock' (Set.insert (nameOf x') fvs) ctx (p' : ps)))]
2✔
627
      where
628
        (x', p') = freshen x p fvs
2✔
629
    goBlock' fvs ctx (And ps : ps') = goBlock' fvs ctx (ps ++ ps')
2✔
630
    goBlock' fvs ctx (p : ps) = goBlock' fvs (p : ctx) ps
2✔
631

632
    goExists ::
633
      HasSpec a =>
634
      [Pred] ->
635
      (Binder a -> Pred) ->
636
      Var a ->
637
      Pred ->
638
      [Pred]
639
    goExists ctx ex x (Let t (y :-> p))
2✔
640
      | not $ Name x `appearsIn` t =
1✔
641
          let (y', p') = freshen y p (Set.insert (nameOf x) $ freeVarNames p <> freeVarNames t)
2✔
642
           in go ctx (Let t (y' :-> ex (x :-> p')))
2✔
643
    goExists ctx ex x p = ex (x :-> p) : ctx
2✔
644

645
    pushExplain es (Let t (x :-> p)) = Let t (x :-> pushExplain es p)
1✔
646
    pushExplain es (And ps) = And (pushExplain es <$> ps)
2✔
647
    pushExplain es (Exists k (x :-> p)) =
648
      Exists (explainSemantics k) (x :-> pushExplain es p)
2✔
649
      where
650
        -- TODO: Unfortunately this is necessary on ghc 8.10.7
651
        explainSemantics ::
652
          forall a.
653
          ((forall b. Term b -> b) -> GE a) ->
654
          (forall b. Term b -> b) ->
655
          GE a
656
        explainSemantics k2 env = explainNE es $ k2 env
1✔
657
    -- TODO: possibly one wants to have a `Term` level explanation in case
658
    -- the `b` propagates to ErrorSpec for some reason?
659
    pushExplain es (When b p) = When b (pushExplain es p)
1✔
660
    pushExplain es p = explanation es p
2✔
661

662
    go ctx = \case
2✔
663
      ElemPred bool t xs -> ElemPred bool t xs : ctx
2✔
664
      And ps0 -> goBlock ctx (map letFloating ps0)
2✔
665
      Let t (x :-> p) -> goBlock ctx [Let t (x :-> letFloating p)]
2✔
666
      Exists k (x :-> p) -> goExists ctx (Exists k) x (letFloating p)
2✔
UNCOV
667
      Subst x t p -> go ctx (substitutePred x t p)
×
668
      Reifies t' t f -> Reifies t' t f : ctx
2✔
669
      Explain es p -> pushExplain es p : ctx
2✔
670
      -- TODO: float let through forall if possible
671
      ForAll t (x :-> p) -> ForAll t (x :-> letFloating p) : ctx
2✔
672
      -- TODO: float let through the cases if possible
673
      Case t bs -> Case t (mapList (mapWeighted (\(x :-> p) -> x :-> letFloating p)) bs) : ctx
2✔
674
      -- TODO: float let through if possible
675
      When b p -> When b (letFloating p) : ctx
2✔
676
      -- Boring cases
677
      Assert t -> Assert t : ctx
2✔
678
      GenHint h t -> GenHint h t : ctx
2✔
679
      DependsOn t t' -> DependsOn t t' : ctx
2✔
680
      TruePred -> TruePred : ctx
2✔
681
      FalsePred es -> FalsePred es : ctx
1✔
UNCOV
682
      Monitor m -> Monitor m : ctx
×
683

684
-- Common subexpression elimination but only on terms that are already let-bound.
685
letSubexpressionElimination :: Pred -> Pred
686
letSubexpressionElimination = go []
2✔
687
  where
688
    adjustSub :: HasSpec a => Var a -> Subst -> Subst
689
    adjustSub x sub =
2✔
690
      [ x' := t
2✔
691
      | x' := t <- sub
2✔
692
      , isNothing $ eqVar x x'
2✔
693
      , -- TODO: possibly freshen the binder where
694
      -- `x` appears instead?
695
      not $ Name x `appearsIn` t
1✔
696
      ]
697

698
    goBinder :: Subst -> Binder a -> Binder a
699
    goBinder sub (x :-> p) = x :-> go (adjustSub x sub) p
2✔
700

701
    go sub = \case
2✔
702
      ElemPred bool t xs -> ElemPred bool (backwardsSubstitution sub t) xs
2✔
703
      GenHint h t -> GenHint h (backwardsSubstitution sub t)
2✔
704
      And ps -> And (go sub <$> ps)
2✔
705
      Let t (x :-> p) -> Let t' (x :-> go (x := t' : sub') p)
2✔
706
        where
707
          t' = backwardsSubstitution sub t
2✔
708
          sub' = adjustSub x sub
2✔
709
      Exists k b -> Exists k (goBinder sub b)
2✔
UNCOV
710
      Subst x t p -> go sub (substitutePred x t p)
×
711
      Assert t -> Assert (backwardsSubstitution sub t)
2✔
712
      Reifies t' t f -> Reifies (backwardsSubstitution sub t') (backwardsSubstitution sub t) f
2✔
713
      -- NOTE: this is a tricky case. One possible thing to do here is to keep the old `DependsOn t t'`
714
      -- and have the new DependsOn if `backwardsSubstitution` changed something. With this semantics you
715
      -- risk running into unintuitive behaviour if you have something like:
716
      -- ```
717
      -- let x = y + z in
718
      --  {y + z `dependsOn` w
719
      --   assert $ w <. y + 2
720
      --   ...}
721
      -- ```
722
      -- This will be rewritten as:
723
      -- ```
724
      -- let x = y + z in
725
      --  {z `dependsOn` w
726
      --   assert $ w <. y + 2
727
      --   ...}
728
      -- ```
729
      -- which changes the dependency order of `w` and `y`. However, fixing
730
      -- this behaviour in turn makes it more difficult to detect when
731
      -- variables are no longer used after being substituted away - which
732
      -- blocks some other optimizations. As we strongly encourage users not to
733
      -- use `letBind` in their own code most users will never encounter this issue
734
      -- so the tradeoff is probably worth it.
735
      DependsOn t t' -> DependsOn (backwardsSubstitution sub t) (backwardsSubstitution sub t')
2✔
736
      ForAll t b -> ForAll (backwardsSubstitution sub t) (goBinder sub b)
2✔
737
      Case t bs -> Case (backwardsSubstitution sub t) (mapList (mapWeighted $ goBinder sub) bs)
2✔
738
      When b p -> When (backwardsSubstitution sub b) (go sub p)
2✔
739
      TruePred -> TruePred
2✔
740
      FalsePred es -> FalsePred es
1✔
UNCOV
741
      Monitor m -> Monitor m
×
742
      Explain es p -> Explain es $ go sub p
2✔
743

744
-- Turning Preds into Specifications. Here is where Propagation occurs ----
745

746
-- | Precondition: the `Pred` defines the `Var a`
747
-- Runs in `GE` in order for us to have detailed context on failure.
748
computeSpecSimplified ::
749
  forall a. (HasSpec a, HasCallStack) => Var a -> Pred -> GE (Specification a)
750
computeSpecSimplified x pred3 = localGESpec $ case simplifyPred pred3 of
2✔
751
  ElemPred True t xs -> propagateSpec (MemberSpec xs) <$> toCtx x t
2✔
752
  ElemPred False (t :: Term b) xs -> propagateSpec (TypeSpec @b (emptySpec @b) (NE.toList xs)) <$> toCtx x t
2✔
UNCOV
753
  Monitor {} -> pure mempty
×
754
  GenHint h t -> propagateSpec (giveHint h) <$> toCtx x t
2✔
755
  Subst x' t p' -> computeSpec x (substitutePred x' t p') -- NOTE: this is impossible as it should have gone away already
×
756
  TruePred -> pure mempty
2✔
757
  FalsePred es -> genErrorNE es
1✔
758
  And ps -> do
2✔
759
    spec <- fold <$> mapM (computeSpecSimplified x) ps
2✔
760
    case spec of
2✔
UNCOV
761
      ExplainSpec es (SuspendedSpec y ps') -> pure $ explainSpec es (SuspendedSpec y $ simplifyPred ps')
×
762
      SuspendedSpec y ps' -> pure $ SuspendedSpec y $ simplifyPred ps'
2✔
763
      s -> pure s
2✔
764
  Let t b -> pure $ SuspendedSpec x (Let t b)
2✔
765
  Exists k b -> pure $ SuspendedSpec x (Exists k b)
2✔
766
  Assert (Lit True) -> pure mempty
2✔
767
  Assert (Lit False) -> genError (show pred3)
1✔
768
  Assert t -> propagateSpec (equalSpec True) <$> toCtx x t
2✔
UNCOV
769
  ForAll (Lit s) b -> fold <$> mapM (\val -> computeSpec x $ unBind val b) (forAllToList s)
×
770
  ForAll t b -> do
2✔
771
    bSpec <- computeSpecBinderSimplified b
2✔
772
    propagateSpec (fromForAllSpec bSpec) <$> toCtx x t
2✔
UNCOV
773
  Case (Lit val) bs -> runCaseOn val (mapList thing bs) $ \va vaVal psa -> computeSpec x (substPred (Env.singleton va vaVal) psa)
×
774
  Case t branches -> do
2✔
775
    branchSpecs <- mapMList (traverseWeighted computeSpecBinderSimplified) branches
2✔
776
    propagateSpec (caseSpec (Just (showType @a)) branchSpecs) <$> toCtx x t
1✔
UNCOV
777
  When (Lit b) tp -> if b then computeSpecSimplified x tp else pure TrueSpec
×
778
  -- This shouldn't happen a lot of the time because when the body is trivial we mostly get rid of the `When` entirely
779
  When {} -> pure $ SuspendedSpec x pred3
2✔
780
  Reifies (Lit a) (Lit val) f
UNCOV
781
    | f val == a -> pure TrueSpec
×
UNCOV
782
    | otherwise ->
×
783
        pure $
×
784
          ErrorSpec (NE.fromList ["Value does not reify to literal: " ++ show val ++ " -/> " ++ show a])
×
785
  Reifies t' (Lit val) f ->
786
    propagateSpec (equalSpec (f val)) <$> toCtx x t'
×
787
  Reifies Lit {} _ _ ->
788
    fatalErrorNE $ NE.fromList ["Dependency error in computeSpec: Reifies", "  " ++ show pred3]
2✔
789
  Explain es p -> do
2✔
790
    -- In case things crash in here we want the explanation
791
    s <- pushGE (NE.toList es) (computeSpecSimplified x p)
2✔
792
    -- This is because while we do want to propagate `explanation`s into `SuspendedSpec`
793
    -- we probably don't want to propagate the full "currently simplifying xyz" explanation.
794
    case s of
2✔
795
      SuspendedSpec x2 p2 -> pure $ SuspendedSpec x2 (explanation es p2)
1✔
796
      _ -> pure $ addToErrorSpec es s
1✔
797
  -- Impossible cases that should be ruled out by the dependency analysis and linearizer
798
  DependsOn {} ->
UNCOV
799
    fatalErrorNE $
×
UNCOV
800
      NE.fromList
×
801
        [ "The impossible happened in computeSpec: DependsOn"
×
802
        , "  " ++ show x
×
803
        , show $ indent 2 (pretty pred3)
×
804
        ]
805
  Reifies {} ->
UNCOV
806
    fatalErrorNE $
×
UNCOV
807
      NE.fromList
×
808
        ["The impossible happened in computeSpec: Reifies", "  " ++ show x, show $ indent 2 (pretty pred3)]
×
809
  where
810
    -- We want `genError` to turn into `ErrorSpec` and we want `FatalError` to turn into `FatalError`
811
    localGESpec ge = case ge of
2✔
812
      (GenError xs) -> Result $ ErrorSpec (catMessageList xs)
1✔
813
      (FatalError es) -> FatalError es
2✔
814
      (Result v) -> Result v
2✔
815

816
-- | Precondition: the `Pred fn` defines the `Var a`.
817
--   Runs in `GE` in order for us to have detailed context on failure.
818
computeSpec ::
819
  forall a. (HasSpec a, HasCallStack) => Var a -> Pred -> GE (Specification a)
820
computeSpec x p = computeSpecSimplified x (simplifyPred p)
2✔
821

822
computeSpecBinderSimplified :: Binder a -> GE (Specification a)
823
computeSpecBinderSimplified (x :-> p) = computeSpecSimplified x p
2✔
824

825
-- | Turn a list of branches into a SumSpec. If all the branches fail return an ErrorSpec.
826
--   Note the requirement of HasSpec(SumOver).
827
caseSpec ::
828
  forall as.
829
  HasSpec (SumOver as) =>
830
  Maybe String ->
831
  List (Weighted (Specification)) as ->
832
  Specification (SumOver as)
833
caseSpec tString ss
2✔
834
  | allBranchesFail ss =
2✔
835
      ErrorSpec
2✔
UNCOV
836
        ( NE.fromList
×
UNCOV
837
            [ "When simplifying SumSpec, all branches in a caseOn" ++ sumType tString ++ " simplify to False."
×
838
            , show spec
×
839
            ]
840
        )
841
  | True = spec
1✔
842
  where
843
    spec = loop tString ss
1✔
844

845
    allBranchesFail :: forall as2. List (Weighted Specification) as2 -> Bool
846
    allBranchesFail Nil = error "The impossible happened in allBranchesFail"
1✔
847
    allBranchesFail (Weighted _ s :> Nil) = isErrorLike s
2✔
848
    allBranchesFail (Weighted _ s :> ss2@(_ :> _)) = isErrorLike s && allBranchesFail ss2
2✔
849

850
    loop ::
851
      forall as3.
852
      HasSpec (SumOver as3) =>
853
      Maybe String ->
854
      List (Weighted Specification) as3 ->
855
      Specification (SumOver as3)
856
    loop _ Nil = error "The impossible happened in caseSpec"
1✔
857
    loop _ (s :> Nil) = thing s
2✔
858
    loop mTypeString (s :> ss1@(_ :> _))
859
      | Evidence <- prerequisites @(SumOver as3) =
2✔
860
          (typeSpec $ SumSpecRaw mTypeString theWeights (thing s) (loop Nothing ss1))
1✔
861
      where
862
        theWeights =
2✔
863
          case (weight s, totalWeight ss1) of
2✔
864
            (Nothing, Nothing) -> Nothing
2✔
865
            (a, b) -> Just (fromMaybe 1 a, fromMaybe (lengthList ss1) b)
1✔
866

867
------------------------------------------------------------------------
868
-- SumSpec et al
869
------------------------------------------------------------------------
870

871
-- | The Specification for Sums.
872
data SumSpec a b
873
  = SumSpecRaw
874
      (Maybe String) -- A String which is the type of arg in (caseOn arg branch1 .. branchN)
875
      (Maybe (Int, Int))
876
      (Specification a)
877
      (Specification b)
878

879
-- | The "normal" view of t`SumSpec` that doesn't take weights into account
880
pattern SumSpec ::
881
  (Maybe (Int, Int)) -> (Specification a) -> (Specification b) -> SumSpec a b
882
pattern SumSpec a b c <- SumSpecRaw _ a b c
2✔
883
  where
884
    SumSpec a b c = SumSpecRaw Nothing a b c
1✔
885

886
{-# COMPLETE SumSpec #-}
887

888
sumType :: Maybe String -> String
UNCOV
889
sumType Nothing = ""
×
UNCOV
890
sumType (Just x) = " type=" ++ x
×
891

892
totalWeight :: List (Weighted f) as -> Maybe Int
893
totalWeight = fmap getSum . foldMapList (fmap Semigroup.Sum . weight)
2✔
894

895
-- =================================
896
-- Operations on Stages and Plans
897

898
-- | Does nothing if the variable is not in the plan already.
899
mergeSolverStage :: SolverStage -> [SolverStage] -> [SolverStage]
900
mergeSolverStage (SolverStage x ps spec relevant) plan =
2✔
901
  [ case eqVar x y of
2✔
902
      Just Refl ->
903
        normalizeSolverStage $ SolverStage
2✔
904
            y
2✔
905
            (ps ++ ps')
2✔
906
            (spec <> spec')
2✔
907
            (relevant <> relevant')
2✔
908
      Nothing -> stage
2✔
909
  | stage@(SolverStage y ps' spec' relevant') <- plan
2✔
910
  ]
911

912
isEmptyPlan :: SolverPlan -> Bool
913
isEmptyPlan (SolverPlan plan) = null plan
2✔
914

915
stepPlan :: MonadGenError m => SolverPlan -> Env -> SolverPlan -> GenT m (Env, SolverPlan)
916
stepPlan _ env plan@(SolverPlan []) = pure (env, plan)
1✔
917
stepPlan (SolverPlan origStages) env (SolverPlan (stage@(SolverStage (x :: Var a) ps spec relevant) : pl)) = do
2✔
918
  let errorMessage = "Failed to step the plan" />
2✔
919
                        vsep [ "Relevant parts of the original plan:" //> pretty narrowedOrigPlan
2✔
920
                             , "Already generated variables:" //> pretty narrowedEnv
2✔
921
                             , "Current stage:" //> pretty stage
2✔
922
                             ]
923
      relevant' = Set.insert (Name x) relevant
2✔
924
      narrowedOrigPlan = SolverPlan $ [ st | st@(SolverStage v _ _ _) <- origStages, Name v `Set.member` relevant' ]
2✔
925
      narrowedEnv = Env.filterKeys env (\v -> nameOf v `Set.member` (Set.map (\ (Name n) -> nameOf n) relevant'))
2✔
926
  explain (show errorMessage) $ do
2✔
927
    when (isErrorLike spec) $
2✔
928
      genError "The specification in the current stage is unsatisfiable, giving up."
2✔
929
    when (not $ null ps) $
2✔
UNCOV
930
      fatalError "Something went wrong and not all predicates have been discharged. Report this as a bug in Constrained.Generation"
×
931
    val <- genFromSpecT spec
2✔
932
    let env1 = Env.extend x val env
2✔
933
    pure (env1, backPropagation relevant' $ SolverPlan (substStage relevant' x val <$> pl) )
2✔
934

935
-- | Generate a satisfying `Env` for a `p : Pred fn`. The `Env` contains values for
936
-- all the free variables in `flattenPred p`.
937
genFromPreds :: forall m. MonadGenError m => Env -> Pred -> GenT m Env
938
-- TODO: remove this once optimisePred does a proper fixpoint computation
939
genFromPreds env0 (optimisePred . optimisePred -> preds) = do
2✔
940
    -- NOTE: this is just lazy enough that the work of flattening,
941
    -- computing dependencies, and linearizing is memoized in
942
    -- properties that use `genFromPreds`.
943
    origPlan <- runGE $ prepareLinearization preds
2✔
944
    let go :: Env -> SolverPlan -> GenT m Env
945
        go env plan | isEmptyPlan plan = pure env
2✔
946
        go env plan = do
2✔
947
          (env', plan') <- stepPlan origPlan env plan
2✔
948
          go env' plan'
2✔
949
    go env0 origPlan
2✔
950
  where
951

952
-- | Push as much information we can backwards through the plan.
953
backPropagation :: Set Name -> SolverPlan -> SolverPlan
954
backPropagation relevant (SolverPlan initplan) = SolverPlan (go [] (reverse initplan))
2✔
955
  where
956
    go :: [SolverStage] -> [SolverStage] -> [SolverStage]
957
    go acc [] = acc
2✔
958
    go acc (s@(SolverStage (x :: Var a) ps spec _) : plan) = go (s : acc) plan'
2✔
959
      where
960
        newStages = concatMap newStage ps
2✔
961
        plan' = foldr mergeSolverStage plan newStages
2✔
962

963
        -- Note use of the Term Pattern Equal
964
        newStage (Assert (Equal tl tr))
2✔
965
          | [Name xl] <- Set.toList $ freeVarSet tl
2✔
966
          , [Name xr] <- Set.toList $ freeVarSet tr
2✔
967
          , Name x `elem` [Name xl, Name xr]
1✔
968
          , Result ctxL <- toCtx xl tl
2✔
969
          , Result ctxR <- toCtx xr tr
2✔
970
          = case (eqVar x xl, eqVar x xr) of
2✔
971
              (Just Refl, _) -> [SolverStage xr [] (propagateSpec (forwardPropagateSpec spec ctxL) ctxR) (Set.insert (Name x) relevant)]
2✔
972
              (_, Just Refl) -> [SolverStage xl [] (propagateSpec (forwardPropagateSpec spec ctxR) ctxL) (Set.insert (Name x) relevant)]
2✔
UNCOV
973
              _ -> error "The impossible happened"
×
974
        newStage _ = []
2✔
975

976
-- | Function symbols for `(==.)`
977
data EqW :: [Type] -> Type -> Type where
978
  EqualW :: (Eq a, HasSpec a) => EqW '[a, a] Bool
979

UNCOV
980
deriving instance Eq (EqW dom rng)
×
981

982
instance Show (EqW d r) where
×
983
  show EqualW = "==."
2✔
984

985
instance Syntax EqW where
2✔
986
  isInfix EqualW = True
2✔
987

988
instance Semantics EqW where
989
  semantics EqualW = (==)
2✔
990

UNCOV
991
instance Logic EqW where
×
992
  propagate f ctxt (ExplainSpec es s) = explainSpec es $ propagate f ctxt s
2✔
993
  propagate _ _ TrueSpec = TrueSpec
2✔
994
  propagate _ _ (ErrorSpec msgs) = ErrorSpec msgs
1✔
995
  propagate EqualW (HOLE :? Value x :> Nil) (SuspendedSpec v ps) =
996
    constrained $ \v' -> Let (App EqualW (v' :> Lit x :> Nil)) (v :-> ps)
2✔
997
  propagate EqualW (Value x :! Unary HOLE) (SuspendedSpec v ps) =
998
    constrained $ \v' -> Let (App EqualW (Lit x :> v' :> Nil)) (v :-> ps)
2✔
999
  propagate EqualW (HOLE :? Value s :> Nil) spec =
1000
    caseBoolSpec spec $ \case
2✔
1001
      True -> equalSpec s
2✔
1002
      False -> notEqualSpec s
2✔
1003
  propagate EqualW (Value s :! Unary HOLE) spec =
1004
    caseBoolSpec spec $ \case
2✔
1005
      True -> equalSpec s
2✔
1006
      False -> notEqualSpec s
2✔
1007

1008
  rewriteRules EqualW (t :> t' :> Nil) Evidence
2✔
1009
    | t == t' = Just $ lit True
2✔
1010
    | otherwise = Nothing
1✔
1011

1012
  saturate EqualW (FromGeneric (InjLeft _) :> t :> Nil) = [toPreds t (SumSpec Nothing TrueSpec (ErrorSpec (pure "saturatePred")))]
1✔
1013
  saturate EqualW (FromGeneric (InjRight _) :> t :> Nil) = [toPreds t (SumSpec Nothing (ErrorSpec (pure "saturatePred")) TrueSpec)]
1✔
1014
  saturate _ _ = []
2✔
1015

1016
infix 4 ==.
1017

1018
-- | Equality on the constraint-level
1019
(==.) :: HasSpec a => Term a -> Term a -> Term Bool
1020
(==.) = appTerm EqualW
2✔
1021

1022
-- | Pattern version of `(==.)` for rewrite rules
1023
pattern Equal ::
1024
  forall b.
1025
  () =>
1026
  forall a.
1027
  (b ~ Bool, Eq a, HasSpec a) =>
1028
  Term a ->
1029
  Term a ->
1030
  Term b
1031
pattern Equal x y <-
1032
  ( App
2✔
1033
      (getWitness -> Just EqualW)
1034
      (x :> y :> Nil)
1035
    )
1036

1037
-- | Like @if b then p else assert True@ in constraint-land
1038
whenTrue :: forall p. IsPred p => Term Bool -> p -> Pred
1039
whenTrue (Lit True) (toPred -> p) = p
2✔
1040
whenTrue (Lit False) _ = TruePred
2✔
1041
whenTrue b (toPred -> FalsePred {}) = assert (not_ b)
2✔
1042
whenTrue _ (toPred -> TruePred) = TruePred
2✔
1043
whenTrue b (toPred -> p) = When b p
2✔
1044

1045
-- | Is the variable x pinned to some free term in p? (free term
1046
-- meaning that all the variables in the term are free in p).
1047
--
1048
-- TODO: complete this with more cases!
1049
pinnedBy :: forall a. HasSpec a => Var a -> Pred -> Maybe (Term a)
1050
pinnedBy x (Assert (Equal t t'))
2✔
1051
  | V x' <- t, Just Refl <- eqVar x x' = Just t'
2✔
1052
  | V x' <- t', Just Refl <- eqVar x x' = Just t
2✔
1053
pinnedBy x (And ps) = listToMaybe $ catMaybes $ map (pinnedBy x) ps
2✔
1054
pinnedBy _ _ = Nothing
2✔
1055

1056
-- ==================================================================================================
1057
-- TODO: generalize this to make it more flexible and extensible
1058
--
1059
-- The idea here is that we turn constraints into _extra_ constraints. C.f. the
1060
-- `mapIsJust` example in `Constrained.Examples.Map`:
1061

1062
--    mapIsJust :: Specification BaseFn (Int, Int)
1063
--    mapIsJust = constrained' $ \ [var| x |] [var| y |] ->
1064
--      assert $ just_ x ==. lookup_ y (lit $ Map.fromList [(z, z) | z <- [100 .. 102]])
1065

1066
-- Without this code the example wouldn't work because `y` is completely unconstrained during
1067
-- generation. With this code we _essentially_ rewrite occurences of `just_ A == B` to
1068
-- `[just_ A == B, case B of Nothing -> False; Just _ -> True]` to add extra information
1069
-- about the variables in `B`. Consequently, `y` in the example above is
1070
-- constrained to `MemberSpec [100 .. 102]` in the plan. This is implemented using the `saturate`
1071
-- function in the logic type class - in the example above for `==`.
1072
saturatePred :: Pred -> [Pred]
1073
saturatePred p =
2✔
1074
  -- [p]
1075
  --  + ---- if there is an Explain, it is still on 'p' here
1076
  --  |
1077
  --  v
1078
  p : case p of
2✔
1079
    Explain _es x -> saturatePred x -- Note that the Explain is still on the original 'p', so it is not lost
2✔
1080
    -- Note how the saturation is done by the 'saturate' method of the Logic class
1081
    Assert (App sym xs) -> saturate sym xs
2✔
1082
    _ -> []
2✔
1083

1084
-- ==================================================================
1085
-- HasSpec for Sums
1086
-- ==================================================================
1087

1088
guardSumSpec ::
1089
  forall a b.
1090
  (HasSpec a, HasSpec b, KnownNat (CountCases b)) =>
1091
  [String] ->
1092
  SumSpec a b ->
1093
  Specification (Sum a b)
1094
guardSumSpec msgs s@(SumSpecRaw tString _ sa sb)
2✔
1095
  | isErrorLike sa
2✔
1096
  , isErrorLike sb =
1✔
UNCOV
1097
      ErrorSpec $
×
UNCOV
1098
        NE.fromList $
×
1099
          msgs ++ ["All branches in a caseOn" ++ sumType tString ++ " simplify to False.", show s]
×
1100
  | otherwise = typeSpec s
1✔
1101

UNCOV
1102
instance (KnownNat (CountCases b), HasSpec a, HasSpec b) => Show (SumSpec a b) where
×
UNCOV
1103
  show sumspec@(SumSpecRaw tstring hint l r) = case alternateShow @(Sum a b) sumspec of
×
1104
    (BinaryShow _ ps) -> show $ parens (fromString ("SumSpec" ++ sumType tstring) /> vsep ps)
×
1105
    NonBinary ->
1106
      "(SumSpec"
×
UNCOV
1107
        ++ sumType tstring
×
1108
        ++ show (sumWeightL hint)
×
1109
        ++ " ("
×
1110
        ++ show l
×
1111
        ++ ") "
×
1112
        ++ show (sumWeightR hint)
×
1113
        ++ " ("
×
1114
        ++ show r
×
1115
        ++ "))"
×
1116

1117
combTypeName :: Maybe String -> Maybe String -> Maybe String
UNCOV
1118
combTypeName (Just x) (Just y) =
×
UNCOV
1119
  if x == y then Just x else Just ("(" ++ x ++ " | " ++ y ++ ")")
×
1120
combTypeName (Just x) Nothing = Just x
×
1121
combTypeName Nothing (Just x) = Just x
×
1122
combTypeName Nothing Nothing = Nothing
×
1123

1124
instance (HasSpec a, HasSpec b) => Semigroup (SumSpec a b) where
×
1125
  SumSpecRaw t h sa sb <> SumSpecRaw t' h' sa' sb' =
2✔
1126
    SumSpecRaw (combTypeName t t') (unionWithMaybe mergeH h h') (sa <> sa') (sb <> sb')
1✔
1127
    where
1128
      -- TODO: think more carefully about this, now weights like 2 2 and 10 15 give more weight to 10 15
1129
      -- than would be the case if you had 2 2 and 2 3. But on the other hand this approach is associative
1130
      -- whereas actually averaging the ratios is not. One could keep a list. Future work.
1131
      mergeH (fA, fB) (fA', fB') = (fA + fA', fB + fB')
2✔
1132

UNCOV
1133
instance forall a b. (HasSpec a, HasSpec b, KnownNat (CountCases b)) => Monoid (SumSpec a b) where
×
1134
  mempty = SumSpec Nothing mempty mempty
2✔
1135

1136
-- | How many constructors are there in this type?
1137
type family CountCases a where
1138
  CountCases (Sum a b) = 1 + CountCases b
1139
  CountCases _ = 1
1140

1141
countCases :: forall a. KnownNat (CountCases a) => Int
1142
countCases = fromIntegral (natVal @(CountCases a) Proxy)
1✔
1143

1144
-- | The HasSpec Sum instance
1145
instance (HasSpec a, HasSpec b, KnownNat (CountCases b)) => HasSpec (Sum a b) where
1✔
1146
  type TypeSpec (Sum a b) = SumSpec a b
1147

1148
  type Prerequisites (Sum a b) = (HasSpec a, HasSpec b)
1149

1150
  emptySpec = mempty
2✔
1151

1152
  combineSpec s s' = guardSumSpec ["When combining SumSpecs", "  " ++ show s, "  " ++ show s'] (s <> s')
1✔
1153

1154
  conformsTo (SumLeft a) (SumSpec _ sa _) = conformsToSpec a sa
2✔
1155
  conformsTo (SumRight b) (SumSpec _ _ sb) = conformsToSpec b sb
2✔
1156

1157
  genFromTypeSpec (SumSpec h sa sb)
2✔
1158
    | emptyA, emptyB = genError "genFromTypeSpec @SumSpec: empty"
1✔
1159
    | emptyA = SumRight <$> genFromSpecT sb
2✔
1160
    | emptyB = SumLeft <$> genFromSpecT sa
2✔
1161
    | fA == 0, fB == 0 = genError "All frequencies 0"
1✔
UNCOV
1162
    | otherwise =
×
1163
        frequencyT
2✔
1164
          [ (fA, SumLeft <$> genFromSpecT sa)
2✔
1165
          , (fB, SumRight <$> genFromSpecT sb)
2✔
1166
          ]
1167
    where
1168
      (max 0 -> fA, max 0 -> fB) = fromMaybe (1, countCases @b) h
2✔
1169
      emptyA = isErrorLike sa
2✔
1170
      emptyB = isErrorLike sb
2✔
1171

1172
  shrinkWithTypeSpec (SumSpec _ sa _) (SumLeft a) = SumLeft <$> shrinkWithSpec sa a
2✔
1173
  shrinkWithTypeSpec (SumSpec _ _ sb) (SumRight b) = SumRight <$> shrinkWithSpec sb b
2✔
1174

1175
  fixupWithTypeSpec (SumSpec _ sa _) (SumLeft a) = SumLeft <$> fixupWithSpec sa a
1✔
1176
  fixupWithTypeSpec (SumSpec _ _ sb) (SumRight b) = SumRight <$> fixupWithSpec sb b
2✔
1177

1178
  toPreds ct (SumSpec h sa sb) =
2✔
1179
    Case
2✔
1180
      ct
2✔
1181
      ( (Weighted (fst <$> h) $ bind $ \a -> satisfies a sa)
2✔
1182
          :> (Weighted (snd <$> h) $ bind $ \b -> satisfies b sb)
2✔
1183
          :> Nil
2✔
1184
      )
1185

1186
  cardinalTypeSpec (SumSpec _ leftspec rightspec) = addSpecInt (cardinality leftspec) (cardinality rightspec)
2✔
1187

1188
  typeSpecHasError (SumSpec _ x y) =
2✔
1189
    case (isErrorLike x, isErrorLike y) of
2✔
UNCOV
1190
      (True, True) -> Just $ (errorLikeMessage x <> errorLikeMessage y)
×
1191
      _ -> Nothing
2✔
1192

UNCOV
1193
  alternateShow (SumSpec h left right@(TypeSpec r [])) =
×
UNCOV
1194
    case alternateShow @b r of
×
1195
      (BinaryShow "SumSpec" ps) -> BinaryShow "SumSpec" ("|" <+> sumWeightL h <+> viaShow left : ps)
×
1196
      (BinaryShow "Cartesian" ps) ->
1197
        BinaryShow "SumSpec" ("|" <+> sumWeightL h <+> viaShow left : [parens ("Cartesian" /> vsep ps)])
×
1198
      _ ->
1199
        BinaryShow "SumSpec" ["|" <+> sumWeightL h <+> viaShow left, "|" <+> sumWeightR h <+> viaShow right]
×
1200
  alternateShow (SumSpec h left right) =
1201
    BinaryShow "SumSpec" ["|" <+> sumWeightL h <+> viaShow left, "|" <+> sumWeightR h <+> viaShow right]
×
1202

1203
-- ======================================
1204
-- Here are the Logic Instances for Sum
1205

1206
-- | Function symbols for `injLeft_` and `injRight_`
1207
data SumW dom rng where
1208
  InjLeftW :: SumW '[a] (Sum a b)
1209
  InjRightW :: SumW '[b] (Sum a b)
1210

UNCOV
1211
instance Show (SumW dom rng) where
×
1212
  show InjLeftW = "injLeft_"
2✔
1213
  show InjRightW = "injRight_"
2✔
1214

UNCOV
1215
deriving instance (Eq (SumW dom rng))
×
1216

1217
instance Syntax SumW
×
1218

1219
instance Semantics SumW where
1220
  semantics InjLeftW = SumLeft
2✔
1221
  semantics InjRightW = SumRight
2✔
1222

1223
instance Logic SumW where
1✔
1224
  propagateTypeSpec InjLeftW (Unary HOLE) (SumSpec _ sl _) cant = sl <> foldMap notEqualSpec [a | SumLeft a <- cant]
2✔
1225
  propagateTypeSpec InjRightW (Unary HOLE) (SumSpec _ _ sr) cant = sr <> foldMap notEqualSpec [a | SumRight a <- cant]
2✔
1226

1227
  propagateMemberSpec InjLeftW (Unary HOLE) es =
2✔
1228
    case [a | SumLeft a <- NE.toList es] of
2✔
1229
      (x : xs) -> MemberSpec (x :| xs)
2✔
1230
      [] ->
1231
        ErrorSpec $
2✔
UNCOV
1232
          pure $
×
UNCOV
1233
            "propMemberSpec (sumleft_ HOLE) on (MemberSpec es) with no SumLeft in es: " ++ show (NE.toList es)
×
1234
  propagateMemberSpec InjRightW (Unary HOLE) es =
1235
    case [a | SumRight a <- NE.toList es] of
2✔
1236
      (x : xs) -> MemberSpec (x :| xs)
2✔
1237
      [] ->
1238
        ErrorSpec $
2✔
UNCOV
1239
          pure $
×
UNCOV
1240
            "propagate(InjRight HOLE) on (MemberSpec es) with no SumLeft in es: " ++ show (NE.toList es)
×
1241

1242
  mapTypeSpec InjLeftW ts = typeSpec $ SumSpec Nothing (typeSpec ts) (ErrorSpec (pure "mapTypeSpec InjLeftW"))
1✔
1243
  mapTypeSpec InjRightW ts = typeSpec $ SumSpec Nothing (ErrorSpec (pure "mapTypeSpec InjRightW")) (typeSpec ts)
1✔
1244

1245
-- | Constructor for `Sum`
1246
injLeft_ :: (HasSpec a, HasSpec b, KnownNat (CountCases b)) => Term a -> Term (Sum a b)
1247
injLeft_ = appTerm InjLeftW
2✔
1248

1249
-- | Constructor for `Sum`
1250
injRight_ :: (HasSpec a, HasSpec b, KnownNat (CountCases b)) => Term b -> Term (Sum a b)
1251
injRight_ = appTerm InjRightW
2✔
1252

1253
-- | Pattern for building custom rewrite rules
1254
pattern InjRight ::
1255
  forall c.
1256
  () =>
1257
  forall a b.
1258
  ( c ~ Sum a b
1259
  , AppRequires SumW '[b] c
1260
  ) =>
1261
  Term b ->
1262
  Term c
1263
pattern InjRight x <- (App (getWitness -> Just InjRightW) (x :> Nil))
2✔
1264

1265
-- | Pattern for building custom rewrite rules
1266
pattern InjLeft ::
1267
  forall c.
1268
  () =>
1269
  forall a b.
1270
  ( c ~ Sum a b
1271
  , AppRequires SumW '[a] c
1272
  ) =>
1273
  Term a ->
1274
  Term c
1275
pattern InjLeft x <- App (getWitness -> Just InjLeftW) (x :> Nil)
2✔
1276

1277
sumWeightL, sumWeightR :: Maybe (Int, Int) -> Doc a
UNCOV
1278
sumWeightL Nothing = "1"
×
UNCOV
1279
sumWeightL (Just (x, _)) = fromString (show x)
×
1280
sumWeightR Nothing = "1"
×
1281
sumWeightR (Just (_, x)) = fromString (show x)
×
1282

1283
-- | Operations on Bool
1284
data BoolW (dom :: [Type]) (rng :: Type) where
1285
  NotW :: BoolW '[Bool] Bool
1286
  OrW :: BoolW '[Bool, Bool] Bool
1287

UNCOV
1288
deriving instance Eq (BoolW dom rng)
×
1289

1290
instance Show (BoolW dom rng) where
×
1291
  show NotW = "not_"
2✔
1292
  show OrW = "or_"
2✔
1293

1294
boolSem :: BoolW dom rng -> FunTy dom rng
1295
boolSem NotW = not
2✔
1296
boolSem OrW = (||)
2✔
1297

1298
instance Semantics BoolW where
1299
  semantics = boolSem
2✔
1300

UNCOV
1301
instance Syntax BoolW
×
1302

1303
-- ======= Logic instance BoolW
1304

1305
instance Logic BoolW where
1✔
1306
  propagate f ctxt (ExplainSpec [] s) = propagate f ctxt s
1✔
1307
  propagate f ctxt (ExplainSpec es s) = ExplainSpec es $ propagate f ctxt s
2✔
1308
  propagate _ _ TrueSpec = TrueSpec
2✔
1309
  propagate _ _ (ErrorSpec msgs) = ErrorSpec msgs
1✔
1310
  propagate NotW (Unary HOLE) (SuspendedSpec v ps) =
1311
    constrained $ \v' -> Let (App NotW (v' :> Nil)) (v :-> ps)
2✔
1312
  propagate NotW (Unary HOLE) spec =
1313
    caseBoolSpec spec (equalSpec . not)
2✔
1314
  propagate OrW (HOLE :<: x) (SuspendedSpec v ps) =
1315
    constrained $ \v' -> Let (App OrW (v' :> Lit x :> Nil)) (v :-> ps)
2✔
1316
  propagate OrW (x :>: HOLE) (SuspendedSpec v ps) =
1317
    constrained $ \v' -> Let (App OrW (Lit x :> v' :> Nil)) (v :-> ps)
2✔
1318
  propagate OrW (HOLE :<: s) spec =
1319
    caseBoolSpec spec (okOr s)
2✔
1320
  propagate OrW (s :>: HOLE) spec =
1321
    caseBoolSpec spec (okOr s)
2✔
1322

UNCOV
1323
  mapTypeSpec NotW () = typeSpec ()
×
1324

1325
-- | We have something like ('constant' ||. HOLE) must evaluate to 'need'.
1326
--   Return a (Specification Bool) for HOLE, that makes that True.
1327
okOr :: Bool -> Bool -> Specification Bool
1328
okOr constant need = case (constant, need) of
2✔
1329
  (True, True) -> TrueSpec
2✔
1330
  (True, False) ->
1331
    ErrorSpec
2✔
UNCOV
1332
      (pure ("(" ++ show constant ++ "||. HOLE) must equal False. That cannot be the case."))
×
1333
  (False, False) -> MemberSpec (pure False)
2✔
1334
  (False, True) -> MemberSpec (pure True)
2✔
1335

1336
-- | Disjunction on @`Term` `Bool`@, note that this will not cause backtracking during generation
1337
or_ :: Term Bool -> Term Bool -> Term Bool
1338
or_ = appTerm OrW
2✔
1339

1340
-- | Negation of booleans
1341
not_ :: Term Bool -> Term Bool
1342
not_ = appTerm NotW
2✔
1343

1344
-- ===============================================================================
1345
-- Syntax for Solving : stages and plans
1346

1347
data SolverStage where
1348
  SolverStage ::
1349
    HasSpec a =>
UNCOV
1350
    { stageVar :: Var a
×
UNCOV
1351
    , stagePreds :: [Pred]
×
1352
    , stageSpec :: Specification a
×
1353
    , relevantVariables :: Set Name
×
1354
    } ->
1355
    SolverStage
1356

1357
docVar :: Typeable a => Var a -> Doc h
1358
docVar (v :: Var a) = fromString (show v ++ " :: " ++ showType @a)
2✔
1359

UNCOV
1360
instance Pretty SolverStage where
×
1361
  pretty SolverStage {..} =
2✔
1362
    docVar stageVar
2✔
1363
      <+> "<-"
2✔
1364
        /> vsep'
2✔
1365
          ( [pretty stageSpec | not $ isTrueSpec stageSpec]
2✔
1366
              ++ ["---" | not $ null stagePreds, not $ isTrueSpec stageSpec]
2✔
1367
              ++ map pretty stagePreds
2✔
1368
              ++ ["_" | null stagePreds && isTrueSpec stageSpec]
2✔
1369
          )
1370

UNCOV
1371
newtype SolverPlan = SolverPlan { solverPlan :: [SolverStage] }
×
1372

1373
instance Pretty SolverPlan where
×
1374
  pretty SolverPlan {..} =
2✔
1375
    "SolverPlan" /> prettyLinear solverPlan
2✔
1376

1377
isTrueSpec :: Specification a -> Bool
1378
isTrueSpec TrueSpec = True
2✔
1379
isTrueSpec _ = False
2✔
1380

1381
prettyLinear :: [SolverStage] -> Doc ann
1382
prettyLinear = vsep' . map pretty
2✔
1383

1384
fromGESpec :: HasCallStack => GE (Specification a) -> Specification a
1385
fromGESpec ge = case ge of
2✔
1386
  Result s -> s
2✔
UNCOV
1387
  GenError xs -> ErrorSpec (catMessageList xs)
×
1388
  FatalError es -> error $ catMessages es
2✔
1389

1390
-- | Functor like property for Specification, but instead of a Haskell function (a -> b),
1391
--   it takes a function symbol (t '[a] b) from a to b.
1392
--   Note, in this context, a function symbol is some constructor of a witnesstype.
1393
--   Eg. ProdFstW, InjRightW, SingletonW, etc. NOT the lifted versions like fst_ singleton_,
1394
--   which construct Terms. We had to wait until here to define this because it
1395
--   depends on Semigroup property of Specification, and Asserting equality
1396
mapSpec ::
1397
  forall t a b.
1398
  AppRequires t '[a] b =>
1399
  t '[a] b ->
1400
  Specification a ->
1401
  Specification b
1402
mapSpec f (ExplainSpec es s) = explainSpec es (mapSpec f s)
2✔
1403
mapSpec f TrueSpec = mapTypeSpec f (emptySpec @a)
2✔
1404
mapSpec _ (ErrorSpec err) = ErrorSpec err
1✔
1405
mapSpec f (MemberSpec as) = MemberSpec $ NE.nub $ fmap (semantics f) as
2✔
1406
mapSpec f (SuspendedSpec x p) =
1407
  constrained $ \x' ->
2✔
1408
    Exists (\_ -> fatalError "mapSpec") (x :-> fold [Assert $ (x' ==. appTerm f (V x)), p])
1✔
1409
mapSpec f (TypeSpec ts cant) = mapTypeSpec f ts <> notMemberSpec (map (semantics f) cant)
2✔
1410

1411
-- TODO generalizeme!
1412
forwardPropagateSpec :: HasSpec a => Specification a -> Ctx a b -> Specification b
1413
forwardPropagateSpec s CtxHOLE = s
2✔
1414
forwardPropagateSpec s (CtxApp f (c :? Nil))
1415
  | Evidence <- ctxHasSpec c = mapSpec f (forwardPropagateSpec s c)
2✔
1416
forwardPropagateSpec _ _ = TrueSpec
2✔
1417

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc