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

input-output-hk / constrained-generators / 416

21 Nov 2025 08:35AM UTC coverage: 76.66% (+0.2%) from 76.458%
416

push

github

web-flow
fourmolu (#60)

179 of 191 new or added lines in 14 files covered. (93.72%)

10 existing lines in 4 files now uncovered.

3948 of 5150 relevant lines covered (76.66%)

1.45 hits per line

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

77.66
/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
  mapSpec,
54
  forwardPropagateSpec,
55
) where
56

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

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

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

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

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

150
-- ----------------------- Shrinking -------------------------------
151

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

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

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

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

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

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

231
-- Debugging --------------------------------------------------------------
232

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

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

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

262
-- ---------------------- Building a plan -----------------------------------
263

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

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

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

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

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

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

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

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

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

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

384
------------------------------------------------------------------------
385
-- Simplification of Specifications
386
------------------------------------------------------------------------
387

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

403
-- ------- Stages of simplifying -------------------------------
404

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

870
------------------------------------------------------------------------
871
-- SumSpec et al
872
------------------------------------------------------------------------
873

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

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

889
{-# COMPLETE SumSpec #-}
890

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

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

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

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

916
isEmptyPlan :: SolverPlan -> Bool
917
isEmptyPlan (SolverPlan plan) = null plan
2✔
918

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

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

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

969
        -- Note use of the Term Pattern Equal
970
        newStage (Assert (Equal tl tr))
2✔
971
          | [Name xl] <- Set.toList $ freeVarSet tl
2✔
972
          , [Name xr] <- Set.toList $ freeVarSet tr
2✔
973
          , Name x `elem` [Name xl, Name xr]
1✔
974
          , Result ctxL <- toCtx xl tl
2✔
975
          , Result ctxR <- toCtx xr tr =
2✔
976
              case (eqVar x xl, eqVar x xr) of
2✔
977
                (Just Refl, _) ->
978
                  [ SolverStage
2✔
979
                      xr
2✔
980
                      []
2✔
981
                      (propagateSpec (forwardPropagateSpec spec ctxL) ctxR)
2✔
982
                      (Set.insert (Name x) relevant)
2✔
983
                  ]
984
                (_, Just Refl) ->
985
                  [ SolverStage
2✔
986
                      xl
2✔
987
                      []
2✔
988
                      (propagateSpec (forwardPropagateSpec spec ctxR) ctxL)
2✔
989
                      (Set.insert (Name x) relevant)
2✔
990
                  ]
NEW
991
                _ -> error "The impossible happened"
×
992
        newStage _ = []
2✔
993

994
-- | Function symbols for `(==.)`
995
data EqW :: [Type] -> Type -> Type where
996
  EqualW :: (Eq a, HasSpec a) => EqW '[a, a] Bool
997

998
deriving instance Eq (EqW dom rng)
×
999

1000
instance Show (EqW d r) where
×
1001
  show EqualW = "==."
2✔
1002

1003
instance Syntax EqW where
2✔
1004
  isInfix EqualW = True
2✔
1005

1006
instance Semantics EqW where
1007
  semantics EqualW = (==)
2✔
1008

1009
instance Logic EqW where
×
1010
  propagate f ctxt (ExplainSpec es s) = explainSpec es $ propagate f ctxt s
2✔
1011
  propagate _ _ TrueSpec = TrueSpec
2✔
1012
  propagate _ _ (ErrorSpec msgs) = ErrorSpec msgs
1✔
1013
  propagate EqualW (HOLE :? Value x :> Nil) (SuspendedSpec v ps) =
1014
    constrained $ \v' -> Let (App EqualW (v' :> Lit x :> Nil)) (v :-> ps)
2✔
1015
  propagate EqualW (Value x :! Unary HOLE) (SuspendedSpec v ps) =
1016
    constrained $ \v' -> Let (App EqualW (Lit x :> v' :> Nil)) (v :-> ps)
2✔
1017
  propagate EqualW (HOLE :? Value s :> Nil) spec =
1018
    caseBoolSpec spec $ \case
2✔
1019
      True -> equalSpec s
2✔
1020
      False -> notEqualSpec s
2✔
1021
  propagate EqualW (Value s :! Unary HOLE) spec =
1022
    caseBoolSpec spec $ \case
2✔
1023
      True -> equalSpec s
2✔
1024
      False -> notEqualSpec s
2✔
1025

1026
  rewriteRules EqualW (t :> t' :> Nil) Evidence
2✔
1027
    | t == t' = Just $ lit True
2✔
1028
    | otherwise = Nothing
1✔
1029

1030
  saturate EqualW (FromGeneric (InjLeft _) :> t :> Nil) = [toPreds t (SumSpec Nothing TrueSpec (ErrorSpec (pure "saturatePred")))]
1✔
1031
  saturate EqualW (FromGeneric (InjRight _) :> t :> Nil) = [toPreds t (SumSpec Nothing (ErrorSpec (pure "saturatePred")) TrueSpec)]
1✔
1032
  saturate _ _ = []
2✔
1033

1034
infix 4 ==.
1035

1036
-- | Equality on the constraint-level
1037
(==.) :: HasSpec a => Term a -> Term a -> Term Bool
1038
(==.) = appTerm EqualW
2✔
1039

1040
-- | Pattern version of `(==.)` for rewrite rules
1041
pattern Equal ::
1042
  forall b.
1043
  () =>
1044
  forall a.
1045
  (b ~ Bool, Eq a, HasSpec a) =>
1046
  Term a ->
1047
  Term a ->
1048
  Term b
1049
pattern Equal x y <-
1050
  ( App
2✔
1051
      (getWitness -> Just EqualW)
1052
      (x :> y :> Nil)
1053
    )
1054

1055
-- | Like @if b then p else assert True@ in constraint-land
1056
whenTrue :: forall p. IsPred p => Term Bool -> p -> Pred
1057
whenTrue (Lit True) (toPred -> p) = p
2✔
1058
whenTrue (Lit False) _ = TruePred
2✔
1059
whenTrue b (toPred -> FalsePred {}) = assert (not_ b)
2✔
1060
whenTrue _ (toPred -> TruePred) = TruePred
2✔
1061
whenTrue b (toPred -> p) = When b p
2✔
1062

1063
-- | Is the variable x pinned to some free term in p? (free term
1064
-- meaning that all the variables in the term are free in p).
1065
--
1066
-- TODO: complete this with more cases!
1067
pinnedBy :: forall a. HasSpec a => Var a -> Pred -> Maybe (Term a)
1068
pinnedBy x (Assert (Equal t t'))
2✔
1069
  | V x' <- t, Just Refl <- eqVar x x' = Just t'
2✔
1070
  | V x' <- t', Just Refl <- eqVar x x' = Just t
2✔
1071
pinnedBy x (ElemPred True (V x') (xs NE.:| []))
1072
  | Just Refl <- eqVar x x' = Just (lit xs)
2✔
1073
pinnedBy x (And ps) = listToMaybe $ catMaybes $ map (pinnedBy x) ps
2✔
1074
pinnedBy x (Explain _ p) = pinnedBy x p
2✔
1075
pinnedBy _ _ = Nothing
2✔
1076

1077
-- ==================================================================================================
1078
-- TODO: generalize this to make it more flexible and extensible
1079
--
1080
-- The idea here is that we turn constraints into _extra_ constraints. C.f. the
1081
-- `mapIsJust` example in `Constrained.Examples.Map`:
1082

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

1087
-- Without this code the example wouldn't work because `y` is completely unconstrained during
1088
-- generation. With this code we _essentially_ rewrite occurences of `just_ A == B` to
1089
-- `[just_ A == B, case B of Nothing -> False; Just _ -> True]` to add extra information
1090
-- about the variables in `B`. Consequently, `y` in the example above is
1091
-- constrained to `MemberSpec [100 .. 102]` in the plan. This is implemented using the `saturate`
1092
-- function in the logic type class - in the example above for `==`.
1093
saturatePred :: Pred -> [Pred]
1094
saturatePred p =
2✔
1095
  -- [p]
1096
  --  + ---- if there is an Explain, it is still on 'p' here
1097
  --  |
1098
  --  v
1099
  p : case p of
2✔
1100
    Explain _es x -> saturatePred x -- Note that the Explain is still on the original 'p', so it is not lost
2✔
1101
    -- Note how the saturation is done by the 'saturate' method of the Logic class
1102
    Assert (App sym xs) -> saturate sym xs
2✔
1103
    _ -> []
2✔
1104

1105
-- ==================================================================
1106
-- HasSpec for Sums
1107
-- ==================================================================
1108

1109
guardSumSpec ::
1110
  forall a b.
1111
  (HasSpec a, HasSpec b, KnownNat (CountCases b)) =>
1112
  [String] ->
1113
  SumSpec a b ->
1114
  Specification (Sum a b)
1115
guardSumSpec msgs s@(SumSpecRaw tString _ sa sb)
2✔
1116
  | isErrorLike sa
2✔
1117
  , isErrorLike sb =
1✔
1118
      ErrorSpec $
×
1119
        NE.fromList $
×
1120
          msgs ++ ["All branches in a caseOn" ++ sumType tString ++ " simplify to False.", show s]
×
1121
  | otherwise = typeSpec s
1✔
1122

1123
instance (KnownNat (CountCases b), HasSpec a, HasSpec b) => Show (SumSpec a b) where
×
1124
  show sumspec@(SumSpecRaw tstring hint l r) = case alternateShow @(Sum a b) sumspec of
×
1125
    (BinaryShow _ ps) -> show $ parens (fromString ("SumSpec" ++ sumType tstring) /> vsep ps)
×
1126
    NonBinary ->
1127
      "(SumSpec"
×
1128
        ++ sumType tstring
×
1129
        ++ show (sumWeightL hint)
×
1130
        ++ " ("
×
1131
        ++ show l
×
1132
        ++ ") "
×
1133
        ++ show (sumWeightR hint)
×
1134
        ++ " ("
×
1135
        ++ show r
×
1136
        ++ "))"
×
1137

1138
combTypeName :: Maybe String -> Maybe String -> Maybe String
1139
combTypeName (Just x) (Just y) =
×
1140
  if x == y then Just x else Just ("(" ++ x ++ " | " ++ y ++ ")")
×
1141
combTypeName (Just x) Nothing = Just x
×
1142
combTypeName Nothing (Just x) = Just x
×
1143
combTypeName Nothing Nothing = Nothing
×
1144

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

1154
instance forall a b. (HasSpec a, HasSpec b, KnownNat (CountCases b)) => Monoid (SumSpec a b) where
×
1155
  mempty = SumSpec Nothing mempty mempty
2✔
1156

1157
-- | How many constructors are there in this type?
1158
type family CountCases a where
1159
  CountCases (Sum a b) = 1 + CountCases b
1160
  CountCases _ = 1
1161

1162
countCases :: forall a. KnownNat (CountCases a) => Int
1163
countCases = fromIntegral (natVal @(CountCases a) Proxy)
1✔
1164

1165
-- | The HasSpec Sum instance
1166
instance (HasSpec a, HasSpec b, KnownNat (CountCases b)) => HasSpec (Sum a b) where
1✔
1167
  type TypeSpec (Sum a b) = SumSpec a b
1168

1169
  type Prerequisites (Sum a b) = (HasSpec a, HasSpec b)
1170

1171
  emptySpec = mempty
2✔
1172

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

1175
  conformsTo (SumLeft a) (SumSpec _ sa _) = conformsToSpec a sa
2✔
1176
  conformsTo (SumRight b) (SumSpec _ _ sb) = conformsToSpec b sb
2✔
1177

1178
  genFromTypeSpec (SumSpec h sa sb)
2✔
1179
    | emptyA, emptyB = genError "genFromTypeSpec @SumSpec: empty"
1✔
1180
    | emptyA = SumRight <$> genFromSpecT sb
2✔
1181
    | emptyB = SumLeft <$> genFromSpecT sa
2✔
1182
    | fA == 0, fB == 0 = genError "All frequencies 0"
1✔
1183
    | otherwise =
×
1184
        frequencyT
2✔
1185
          [ (fA, SumLeft <$> genFromSpecT sa)
2✔
1186
          , (fB, SumRight <$> genFromSpecT sb)
2✔
1187
          ]
1188
    where
1189
      (max 0 -> fA, max 0 -> fB) = fromMaybe (1, countCases @b) h
2✔
1190
      emptyA = isErrorLike sa
2✔
1191
      emptyB = isErrorLike sb
2✔
1192

1193
  shrinkWithTypeSpec (SumSpec _ sa _) (SumLeft a) = SumLeft <$> shrinkWithSpec sa a
2✔
1194
  shrinkWithTypeSpec (SumSpec _ _ sb) (SumRight b) = SumRight <$> shrinkWithSpec sb b
2✔
1195

1196
  fixupWithTypeSpec (SumSpec _ sa _) (SumLeft a) = SumLeft <$> fixupWithSpec sa a
1✔
1197
  fixupWithTypeSpec (SumSpec _ _ sb) (SumRight b) = SumRight <$> fixupWithSpec sb b
2✔
1198

1199
  toPreds ct (SumSpec h sa sb) =
2✔
1200
    Case
2✔
1201
      ct
2✔
1202
      ( (Weighted (fst <$> h) $ bind $ \a -> satisfies a sa)
2✔
1203
          :> (Weighted (snd <$> h) $ bind $ \b -> satisfies b sb)
2✔
1204
          :> Nil
2✔
1205
      )
1206

1207
  cardinalTypeSpec (SumSpec _ leftspec rightspec) = addSpecInt (cardinality leftspec) (cardinality rightspec)
2✔
1208

1209
  typeSpecHasError (SumSpec _ x y) =
2✔
1210
    case (isErrorLike x, isErrorLike y) of
2✔
1211
      (True, True) -> Just $ (errorLikeMessage x <> errorLikeMessage y)
×
1212
      _ -> Nothing
2✔
1213

1214
  alternateShow (SumSpec h left right@(TypeSpec r [])) =
×
1215
    case alternateShow @b r of
×
1216
      (BinaryShow "SumSpec" ps) -> BinaryShow "SumSpec" ("|" <+> sumWeightL h <+> viaShow left : ps)
×
1217
      (BinaryShow "Cartesian" ps) ->
1218
        BinaryShow "SumSpec" ("|" <+> sumWeightL h <+> viaShow left : [parens ("Cartesian" /> vsep ps)])
×
1219
      _ ->
1220
        BinaryShow "SumSpec" ["|" <+> sumWeightL h <+> viaShow left, "|" <+> sumWeightR h <+> viaShow right]
×
1221
  alternateShow (SumSpec h left right) =
1222
    BinaryShow "SumSpec" ["|" <+> sumWeightL h <+> viaShow left, "|" <+> sumWeightR h <+> viaShow right]
×
1223

1224
-- ======================================
1225
-- Here are the Logic Instances for Sum
1226

1227
-- | Function symbols for `injLeft_` and `injRight_`
1228
data SumW dom rng where
1229
  InjLeftW :: SumW '[a] (Sum a b)
1230
  InjRightW :: SumW '[b] (Sum a b)
1231

1232
instance Show (SumW dom rng) where
×
1233
  show InjLeftW = "injLeft_"
2✔
1234
  show InjRightW = "injRight_"
2✔
1235

1236
deriving instance (Eq (SumW dom rng))
×
1237

1238
instance Syntax SumW
×
1239

1240
instance Semantics SumW where
1241
  semantics InjLeftW = SumLeft
2✔
1242
  semantics InjRightW = SumRight
2✔
1243

1244
instance Logic SumW where
1✔
1245
  propagateTypeSpec InjLeftW (Unary HOLE) (SumSpec _ sl _) cant = sl <> foldMap notEqualSpec [a | SumLeft a <- cant]
2✔
1246
  propagateTypeSpec InjRightW (Unary HOLE) (SumSpec _ _ sr) cant = sr <> foldMap notEqualSpec [a | SumRight a <- cant]
2✔
1247

1248
  propagateMemberSpec InjLeftW (Unary HOLE) es =
2✔
1249
    case [a | SumLeft a <- NE.toList es] of
2✔
1250
      (x : xs) -> MemberSpec (x :| xs)
2✔
1251
      [] ->
1252
        ErrorSpec $
2✔
1253
          pure $
×
1254
            "propMemberSpec (sumleft_ HOLE) on (MemberSpec es) with no SumLeft in es: " ++ show (NE.toList es)
×
1255
  propagateMemberSpec InjRightW (Unary HOLE) es =
1256
    case [a | SumRight a <- NE.toList es] of
2✔
1257
      (x : xs) -> MemberSpec (x :| xs)
2✔
1258
      [] ->
1259
        ErrorSpec $
2✔
1260
          pure $
×
1261
            "propagate(InjRight HOLE) on (MemberSpec es) with no SumLeft in es: " ++ show (NE.toList es)
×
1262

1263
  mapTypeSpec InjLeftW ts = typeSpec $ SumSpec Nothing (typeSpec ts) (ErrorSpec (pure "mapTypeSpec InjLeftW"))
1✔
1264
  mapTypeSpec InjRightW ts = typeSpec $ SumSpec Nothing (ErrorSpec (pure "mapTypeSpec InjRightW")) (typeSpec ts)
1✔
1265

1266
-- | Constructor for `Sum`
1267
injLeft_ :: (HasSpec a, HasSpec b, KnownNat (CountCases b)) => Term a -> Term (Sum a b)
1268
injLeft_ = appTerm InjLeftW
2✔
1269

1270
-- | Constructor for `Sum`
1271
injRight_ :: (HasSpec a, HasSpec b, KnownNat (CountCases b)) => Term b -> Term (Sum a b)
1272
injRight_ = appTerm InjRightW
2✔
1273

1274
-- | Pattern for building custom rewrite rules
1275
pattern InjRight ::
1276
  forall c.
1277
  () =>
1278
  forall a b.
1279
  ( c ~ Sum a b
1280
  , AppRequires SumW '[b] c
1281
  ) =>
1282
  Term b ->
1283
  Term c
1284
pattern InjRight x <- (App (getWitness -> Just InjRightW) (x :> Nil))
2✔
1285

1286
-- | Pattern for building custom rewrite rules
1287
pattern InjLeft ::
1288
  forall c.
1289
  () =>
1290
  forall a b.
1291
  ( c ~ Sum a b
1292
  , AppRequires SumW '[a] c
1293
  ) =>
1294
  Term a ->
1295
  Term c
1296
pattern InjLeft x <- App (getWitness -> Just InjLeftW) (x :> Nil)
2✔
1297

1298
sumWeightL, sumWeightR :: Maybe (Int, Int) -> Doc a
1299
sumWeightL Nothing = "1"
×
1300
sumWeightL (Just (x, _)) = fromString (show x)
×
1301
sumWeightR Nothing = "1"
×
1302
sumWeightR (Just (_, x)) = fromString (show x)
×
1303

1304
-- | Operations on Bool
1305
data BoolW (dom :: [Type]) (rng :: Type) where
1306
  NotW :: BoolW '[Bool] Bool
1307
  OrW :: BoolW '[Bool, Bool] Bool
1308

1309
deriving instance Eq (BoolW dom rng)
×
1310

1311
instance Show (BoolW dom rng) where
×
1312
  show NotW = "not_"
2✔
1313
  show OrW = "or_"
2✔
1314

1315
boolSem :: BoolW dom rng -> FunTy dom rng
1316
boolSem NotW = not
2✔
1317
boolSem OrW = (||)
2✔
1318

1319
instance Semantics BoolW where
1320
  semantics = boolSem
2✔
1321

1322
instance Syntax BoolW
×
1323

1324
-- ======= Logic instance BoolW
1325

1326
instance Logic BoolW where
1✔
1327
  propagate f ctxt (ExplainSpec [] s) = propagate f ctxt s
1✔
1328
  propagate f ctxt (ExplainSpec es s) = ExplainSpec es $ propagate f ctxt s
2✔
1329
  propagate _ _ TrueSpec = TrueSpec
2✔
1330
  propagate _ _ (ErrorSpec msgs) = ErrorSpec msgs
1✔
1331
  propagate NotW (Unary HOLE) (SuspendedSpec v ps) =
1332
    constrained $ \v' -> Let (App NotW (v' :> Nil)) (v :-> ps)
2✔
1333
  propagate NotW (Unary HOLE) spec =
1334
    caseBoolSpec spec (equalSpec . not)
2✔
1335
  propagate OrW (HOLE :<: x) (SuspendedSpec v ps) =
1336
    constrained $ \v' -> Let (App OrW (v' :> Lit x :> Nil)) (v :-> ps)
2✔
1337
  propagate OrW (x :>: HOLE) (SuspendedSpec v ps) =
1338
    constrained $ \v' -> Let (App OrW (Lit x :> v' :> Nil)) (v :-> ps)
2✔
1339
  propagate OrW (HOLE :<: s) spec =
1340
    caseBoolSpec spec (okOr s)
2✔
1341
  propagate OrW (s :>: HOLE) spec =
1342
    caseBoolSpec spec (okOr s)
2✔
1343

1344
  mapTypeSpec NotW () = typeSpec ()
×
1345

1346
-- | We have something like ('constant' ||. HOLE) must evaluate to 'need'.
1347
--   Return a (Specification Bool) for HOLE, that makes that True.
1348
okOr :: Bool -> Bool -> Specification Bool
1349
okOr constant need = case (constant, need) of
2✔
1350
  (True, True) -> TrueSpec
2✔
1351
  (True, False) ->
1352
    ErrorSpec
2✔
1353
      (pure ("(" ++ show constant ++ "||. HOLE) must equal False. That cannot be the case."))
×
1354
  (False, False) -> MemberSpec (pure False)
2✔
1355
  (False, True) -> MemberSpec (pure True)
2✔
1356

1357
-- | Disjunction on @`Term` `Bool`@, note that this will not cause backtracking during generation
1358
or_ :: Term Bool -> Term Bool -> Term Bool
1359
or_ = appTerm OrW
2✔
1360

1361
-- | Negation of booleans
1362
not_ :: Term Bool -> Term Bool
1363
not_ = appTerm NotW
2✔
1364

1365
-- ===============================================================================
1366
-- Syntax for Solving : stages and plans
1367

1368
data SolverStage where
1369
  SolverStage ::
1370
    HasSpec a =>
1371
    { stageVar :: Var a
×
1372
    , stagePreds :: [Pred]
×
1373
    , stageSpec :: Specification a
×
1374
    , relevantVariables :: Set Name
×
1375
    } ->
1376
    SolverStage
1377

1378
docVar :: Typeable a => Var a -> Doc h
1379
docVar (v :: Var a) = fromString (show v ++ " :: " ++ showType @a)
2✔
1380

1381
instance Pretty SolverStage where
×
1382
  pretty SolverStage {..} =
2✔
1383
    docVar stageVar
2✔
1384
      <+> "<-"
2✔
1385
        /> vsep'
2✔
1386
          ( [pretty stageSpec | not $ isTrueSpec stageSpec]
2✔
1387
              ++ ["---" | not $ null stagePreds, not $ isTrueSpec stageSpec]
2✔
1388
              ++ map pretty stagePreds
2✔
1389
              ++ ["_" | null stagePreds && isTrueSpec stageSpec]
1✔
1390
          )
1391

NEW
1392
newtype SolverPlan = SolverPlan {solverPlan :: [SolverStage]}
×
1393

1394
instance Pretty SolverPlan where
×
1395
  pretty SolverPlan {..} =
2✔
1396
    "SolverPlan" /> prettyLinear solverPlan
2✔
1397

1398
isTrueSpec :: Specification a -> Bool
1399
isTrueSpec TrueSpec = True
2✔
1400
isTrueSpec _ = False
2✔
1401

1402
prettyLinear :: [SolverStage] -> Doc ann
1403
prettyLinear = vsep' . map pretty
2✔
1404

1405
fromGESpec :: HasCallStack => GE (Specification a) -> Specification a
1406
fromGESpec ge = case ge of
2✔
1407
  Result s -> s
2✔
1408
  GenError xs -> ErrorSpec (catMessageList xs)
×
1409
  FatalError es -> error $ catMessages es
2✔
1410

1411
-- | Functor like property for Specification, but instead of a Haskell function (a -> b),
1412
--   it takes a function symbol (t '[a] b) from a to b.
1413
--   Note, in this context, a function symbol is some constructor of a witnesstype.
1414
--   Eg. ProdFstW, InjRightW, SingletonW, etc. NOT the lifted versions like fst_ singleton_,
1415
--   which construct Terms. We had to wait until here to define this because it
1416
--   depends on Semigroup property of Specification, and Asserting equality
1417
mapSpec ::
1418
  forall t a b.
1419
  AppRequires t '[a] b =>
1420
  t '[a] b ->
1421
  Specification a ->
1422
  Specification b
1423
mapSpec f (ExplainSpec es s) = explainSpec es (mapSpec f s)
2✔
1424
mapSpec f TrueSpec = mapTypeSpec f (emptySpec @a)
2✔
1425
mapSpec _ (ErrorSpec err) = ErrorSpec err
1✔
1426
mapSpec f (MemberSpec as) = MemberSpec $ NE.nub $ fmap (semantics f) as
2✔
1427
mapSpec f (SuspendedSpec x p) =
1428
  constrained $ \x' ->
2✔
1429
    Exists (\_ -> fatalError "mapSpec") (x :-> fold [Assert $ (x' ==. appTerm f (V x)), p])
1✔
1430
mapSpec f (TypeSpec ts cant) = mapTypeSpec f ts <> notMemberSpec (map (semantics f) cant)
2✔
1431

1432
-- TODO generalizeme!
1433
forwardPropagateSpec :: HasSpec a => Specification a -> Ctx a b -> Specification b
1434
forwardPropagateSpec s CtxHOLE = s
2✔
1435
forwardPropagateSpec s (CtxApp f (c :? Nil))
1436
  | Evidence <- ctxHasSpec c = mapSpec f (forwardPropagateSpec s c)
2✔
1437
forwardPropagateSpec _ _ = TrueSpec
2✔
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