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

input-output-hk / constrained-generators / 414

21 Nov 2025 07:55AM UTC coverage: 76.458% (-0.8%) from 77.228%
414

push

github

web-flow
Speed up generation by threading seeds instead of splitting them in non-`Gen` part of `GenT` (#57)

33 of 44 new or added lines in 1 file covered. (75.0%)

58 existing lines in 7 files now uncovered.

3907 of 5110 relevant lines covered (76.46%)

1.44 hits per line

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

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

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

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

151
-- ----------------------- Shrinking -------------------------------
152

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

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

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

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

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

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

232
-- Debugging --------------------------------------------------------------
233

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

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

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

263
-- ---------------------- Building a plan -----------------------------------
264

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

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

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

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

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

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

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

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

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

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

383
------------------------------------------------------------------------
384
-- Simplification of Specifications
385
------------------------------------------------------------------------
386

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

402
-- ------- Stages of simplifying -------------------------------
403

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

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

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

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

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

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

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

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

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

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

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

613
simplifyBinder :: Binder a -> Binder a
614
simplifyBinder (x :-> p) = x :-> simplifyPred p
2✔
615

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

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

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

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

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

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

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

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

746
-- Turning Preds into Specifications. Here is where Propagation occurs ----
747

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

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

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

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

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

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

869
------------------------------------------------------------------------
870
-- SumSpec et al
871
------------------------------------------------------------------------
872

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

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

888
{-# COMPLETE SumSpec #-}
889

890
sumType :: Maybe String -> String
891
sumType Nothing = ""
×
892
sumType (Just x) = " type=" ++ x
×
893

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

897
-- =================================
898
-- Operations on Stages and Plans
899

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

914
isEmptyPlan :: SolverPlan -> Bool
915
isEmptyPlan (SolverPlan plan) = null plan
2✔
916

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

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

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

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

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

981
deriving instance Eq (EqW dom rng)
×
982

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

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

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

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

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

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

1017
infix 4 ==.
1018

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

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

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

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

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

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

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

1088
-- ==================================================================
1089
-- HasSpec for Sums
1090
-- ==================================================================
1091

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

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

1121
combTypeName :: Maybe String -> Maybe String -> Maybe String
1122
combTypeName (Just x) (Just y) =
×
1123
  if x == y then Just x else Just ("(" ++ x ++ " | " ++ y ++ ")")
×
1124
combTypeName (Just x) Nothing = Just x
×
1125
combTypeName Nothing (Just x) = Just x
×
1126
combTypeName Nothing Nothing = Nothing
×
1127

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

1137
instance forall a b. (HasSpec a, HasSpec b, KnownNat (CountCases b)) => Monoid (SumSpec a b) where
×
1138
  mempty = SumSpec Nothing mempty mempty
2✔
1139

1140
-- | How many constructors are there in this type?
1141
type family CountCases a where
1142
  CountCases (Sum a b) = 1 + CountCases b
1143
  CountCases _ = 1
1144

1145
countCases :: forall a. KnownNat (CountCases a) => Int
1146
countCases = fromIntegral (natVal @(CountCases a) Proxy)
1✔
1147

1148
-- | The HasSpec Sum instance
1149
instance (HasSpec a, HasSpec b, KnownNat (CountCases b)) => HasSpec (Sum a b) where
1✔
1150
  type TypeSpec (Sum a b) = SumSpec a b
1151

1152
  type Prerequisites (Sum a b) = (HasSpec a, HasSpec b)
1153

1154
  emptySpec = mempty
2✔
1155

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

1158
  conformsTo (SumLeft a) (SumSpec _ sa _) = conformsToSpec a sa
2✔
1159
  conformsTo (SumRight b) (SumSpec _ _ sb) = conformsToSpec b sb
2✔
1160

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

1176
  shrinkWithTypeSpec (SumSpec _ sa _) (SumLeft a) = SumLeft <$> shrinkWithSpec sa a
2✔
1177
  shrinkWithTypeSpec (SumSpec _ _ sb) (SumRight b) = SumRight <$> shrinkWithSpec sb b
2✔
1178

1179
  fixupWithTypeSpec (SumSpec _ sa _) (SumLeft a) = SumLeft <$> fixupWithSpec sa a
1✔
1180
  fixupWithTypeSpec (SumSpec _ _ sb) (SumRight b) = SumRight <$> fixupWithSpec sb b
2✔
1181

1182
  toPreds ct (SumSpec h sa sb) =
2✔
1183
    Case
2✔
1184
      ct
2✔
1185
      ( (Weighted (fst <$> h) $ bind $ \a -> satisfies a sa)
2✔
1186
          :> (Weighted (snd <$> h) $ bind $ \b -> satisfies b sb)
2✔
1187
          :> Nil
2✔
1188
      )
1189

1190
  cardinalTypeSpec (SumSpec _ leftspec rightspec) = addSpecInt (cardinality leftspec) (cardinality rightspec)
2✔
1191

1192
  typeSpecHasError (SumSpec _ x y) =
2✔
1193
    case (isErrorLike x, isErrorLike y) of
2✔
1194
      (True, True) -> Just $ (errorLikeMessage x <> errorLikeMessage y)
×
1195
      _ -> Nothing
2✔
1196

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

1207
-- ======================================
1208
-- Here are the Logic Instances for Sum
1209

1210
-- | Function symbols for `injLeft_` and `injRight_`
1211
data SumW dom rng where
1212
  InjLeftW :: SumW '[a] (Sum a b)
1213
  InjRightW :: SumW '[b] (Sum a b)
1214

1215
instance Show (SumW dom rng) where
×
1216
  show InjLeftW = "injLeft_"
2✔
1217
  show InjRightW = "injRight_"
2✔
1218

1219
deriving instance (Eq (SumW dom rng))
×
1220

1221
instance Syntax SumW
×
1222

1223
instance Semantics SumW where
1224
  semantics InjLeftW = SumLeft
2✔
1225
  semantics InjRightW = SumRight
2✔
1226

1227
instance Logic SumW where
1✔
1228
  propagateTypeSpec InjLeftW (Unary HOLE) (SumSpec _ sl _) cant = sl <> foldMap notEqualSpec [a | SumLeft a <- cant]
2✔
1229
  propagateTypeSpec InjRightW (Unary HOLE) (SumSpec _ _ sr) cant = sr <> foldMap notEqualSpec [a | SumRight a <- cant]
2✔
1230

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

1246
  mapTypeSpec InjLeftW ts = typeSpec $ SumSpec Nothing (typeSpec ts) (ErrorSpec (pure "mapTypeSpec InjLeftW"))
1✔
1247
  mapTypeSpec InjRightW ts = typeSpec $ SumSpec Nothing (ErrorSpec (pure "mapTypeSpec InjRightW")) (typeSpec ts)
1✔
1248

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

1253
-- | Constructor for `Sum`
1254
injRight_ :: (HasSpec a, HasSpec b, KnownNat (CountCases b)) => Term b -> Term (Sum a b)
1255
injRight_ = appTerm InjRightW
2✔
1256

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

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

1281
sumWeightL, sumWeightR :: Maybe (Int, Int) -> Doc a
1282
sumWeightL Nothing = "1"
×
1283
sumWeightL (Just (x, _)) = fromString (show x)
×
1284
sumWeightR Nothing = "1"
×
1285
sumWeightR (Just (_, x)) = fromString (show x)
×
1286

1287
-- | Operations on Bool
1288
data BoolW (dom :: [Type]) (rng :: Type) where
1289
  NotW :: BoolW '[Bool] Bool
1290
  OrW :: BoolW '[Bool, Bool] Bool
1291

1292
deriving instance Eq (BoolW dom rng)
×
1293

1294
instance Show (BoolW dom rng) where
×
1295
  show NotW = "not_"
2✔
1296
  show OrW = "or_"
2✔
1297

1298
boolSem :: BoolW dom rng -> FunTy dom rng
1299
boolSem NotW = not
2✔
1300
boolSem OrW = (||)
2✔
1301

1302
instance Semantics BoolW where
1303
  semantics = boolSem
2✔
1304

1305
instance Syntax BoolW
×
1306

1307
-- ======= Logic instance BoolW
1308

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

1327
  mapTypeSpec NotW () = typeSpec ()
×
1328

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

1340
-- | Disjunction on @`Term` `Bool`@, note that this will not cause backtracking during generation
1341
or_ :: Term Bool -> Term Bool -> Term Bool
1342
or_ = appTerm OrW
2✔
1343

1344
-- | Negation of booleans
1345
not_ :: Term Bool -> Term Bool
1346
not_ = appTerm NotW
2✔
1347

1348
-- ===============================================================================
1349
-- Syntax for Solving : stages and plans
1350

1351
data SolverStage where
1352
  SolverStage ::
1353
    HasSpec a =>
1354
    { stageVar :: Var a
×
1355
    , stagePreds :: [Pred]
×
1356
    , stageSpec :: Specification a
×
1357
    , relevantVariables :: Set Name
×
1358
    } ->
1359
    SolverStage
1360

1361
docVar :: Typeable a => Var a -> Doc h
1362
docVar (v :: Var a) = fromString (show v ++ " :: " ++ showType @a)
2✔
1363

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

1375
newtype SolverPlan = SolverPlan { solverPlan :: [SolverStage] }
×
1376

1377
instance Pretty SolverPlan where
×
1378
  pretty SolverPlan {..} =
2✔
1379
    "SolverPlan" /> prettyLinear solverPlan
2✔
1380

1381
isTrueSpec :: Specification a -> Bool
1382
isTrueSpec TrueSpec = True
2✔
1383
isTrueSpec _ = False
2✔
1384

1385
prettyLinear :: [SolverStage] -> Doc ann
1386
prettyLinear = vsep' . map pretty
2✔
1387

1388
fromGESpec :: HasCallStack => GE (Specification a) -> Specification a
1389
fromGESpec ge = case ge of
2✔
1390
  Result s -> s
2✔
1391
  GenError xs -> ErrorSpec (catMessageList xs)
×
1392
  FatalError es -> error $ catMessages es
2✔
1393

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

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

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