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

input-output-hk / constrained-generators / 500

08 Jan 2026 10:49AM UTC coverage: 77.318% (+0.1%) from 77.177%
500

push

github

web-flow
Test and fix for bad behaviour of chooseSpec (#68)

32 of 34 new or added lines in 4 files covered. (94.12%)

2 existing lines in 2 files now uncovered.

4019 of 5198 relevant lines covered (77.32%)

1.46 hits per line

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

78.12
/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
            ]
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
586
    | Just es <- buildElemList bs -> ElemPred True (simplifyTerm t) es
2✔
587
    | otherwise -> mkCase (simplifyTerm t) (mapList (mapWeighted simplifyBinder) bs)
1✔
588
    where
589
      buildElemList :: List (Weighted Binder) as -> Maybe (NE.NonEmpty (SumOver as))
590
      buildElemList Nil = Nothing
1✔
591
      buildElemList (Weighted Nothing (x :-> ElemPred True (V x') as) :> xs)
592
        | Just Refl <- eqVar x x' =
2✔
593
            case xs of
2✔
594
              Nil -> Just as
2✔
595
              _ :> _ -> do
2✔
596
                rest <- buildElemList xs
2✔
597
                return $ fmap SumLeft as <> fmap SumRight rest
2✔
598
      buildElemList _ = Nothing
2✔
599
  When b p -> whenTrue (simplifyTerm b) (simplifyPred p)
2✔
600
  TruePred -> TruePred
2✔
601
  FalsePred es -> FalsePred es
1✔
602
  And ps -> fold (simplifyPreds ps)
2✔
603
  Let t b -> case simplifyTerm t of
2✔
604
    t'@App {} -> Let t' (simplifyBinder b)
2✔
605
    -- Variable or literal
606
    t' | x :-> p <- b -> simplifyPred $ substitutePred x t' p
2✔
607
  Exists k b -> case simplifyBinder b of
2✔
608
    _ :-> TruePred -> TruePred
2✔
609
    -- This is to get rid of exisentials like:
610
    -- `constrained $ \ x -> exists $ \ y -> [x ==. y, y + 2 <. 10]`
611
    x :-> p | Just t <- pinnedBy x p -> simplifyPred $ substitutePred x t p
2✔
612
    b' -> Exists k b'
2✔
613
  Monitor {} -> TruePred
2✔
614
  -- TODO: This is a bit questionable. On the one hand we could get rid of `Explain` here
615
  -- and just return `simplifyPred p` but doing so risks missing explanations when things
616
  -- do go wrong.
617
  Explain es p -> explanation es $ simplifyPred p
2✔
618

619
simplifyPreds :: [Pred] -> [Pred]
620
simplifyPreds = go [] . map simplifyPred
2✔
621
  where
622
    go acc [] = reverse acc
2✔
623
    go _ (FalsePred err : _) = [FalsePred err]
1✔
624
    go acc (TruePred : ps) = go acc ps
2✔
625
    go acc (p : ps) = go (p : acc) ps
2✔
626

627
simplifyBinder :: Binder a -> Binder a
628
simplifyBinder (x :-> p) = x :-> simplifyPred p
2✔
629

630
-- TODO: this can probably be cleaned up and generalized along with generalizing
631
-- to make sure we float lets in some missing cases.
632
letFloating :: Pred -> Pred
633
letFloating = fold . go []
2✔
634
  where
635
    goBlock ctx ps = goBlock' (freeVarNames ctx <> freeVarNames ps) ctx ps
2✔
636

637
    goBlock' :: Set Int -> [Pred] -> [Pred] -> [Pred]
638
    goBlock' _ ctx [] = ctx
2✔
639
    goBlock' fvs ctx (Let t (x :-> p) : ps) =
640
      -- We can do `goBlock'` here because we've already done let floating
641
      -- on the inner `p`
642
      [Let t (x' :-> fold (goBlock' (Set.insert (nameOf x') fvs) ctx (p' : ps)))]
2✔
643
      where
644
        (x', p') = freshen x p fvs
2✔
645
    goBlock' fvs ctx (And ps : ps') = goBlock' fvs ctx (ps ++ ps')
2✔
646
    goBlock' fvs ctx (p : ps) = goBlock' fvs (p : ctx) ps
2✔
647

648
    goExists ::
649
      HasSpec a =>
650
      [Pred] ->
651
      (Binder a -> Pred) ->
652
      Var a ->
653
      Pred ->
654
      [Pred]
655
    goExists ctx ex x (Let t (y :-> p))
2✔
656
      | not $ Name x `appearsIn` t =
1✔
657
          let (y', p') = freshen y p (Set.insert (nameOf x) $ freeVarNames p <> freeVarNames t)
2✔
658
           in go ctx (Let t (y' :-> ex (x :-> p')))
2✔
659
    goExists ctx ex x p = ex (x :-> p) : ctx
2✔
660

661
    pushExplain es (Let t (x :-> p)) = Let t (x :-> pushExplain es p)
1✔
662
    pushExplain es (And ps) = And (pushExplain es <$> ps)
2✔
663
    pushExplain es (Exists k (x :-> p)) =
664
      Exists (explainSemantics k) (x :-> pushExplain es p)
1✔
665
      where
666
        -- TODO: Unfortunately this is necessary on ghc 8.10.7
667
        explainSemantics ::
668
          forall a.
669
          ((forall b. Term b -> b) -> GE a) ->
670
          (forall b. Term b -> b) ->
671
          GE a
672
        explainSemantics k2 env = explainNE es $ k2 env
1✔
673
    -- TODO: possibly one wants to have a `Term` level explanation in case
674
    -- the `b` propagates to ErrorSpec for some reason?
675
    pushExplain es (When b p) = When b (pushExplain es p)
1✔
676
    pushExplain es p = explanation es p
2✔
677

678
    go ctx = \case
2✔
679
      ElemPred bool t xs -> ElemPred bool t xs : ctx
2✔
680
      And ps0 -> goBlock ctx (map letFloating ps0)
2✔
681
      Let t (x :-> p) -> goBlock ctx [Let t (x :-> letFloating p)]
2✔
682
      Exists k (x :-> p) -> goExists ctx (Exists k) x (letFloating p)
2✔
683
      Subst x t p -> go ctx (substitutePred x t p)
×
684
      Reifies t' t f -> Reifies t' t f : ctx
2✔
685
      Explain es p -> pushExplain es p : ctx
2✔
686
      -- TODO: float let through forall if possible
687
      ForAll t (x :-> p) -> ForAll t (x :-> letFloating p) : ctx
2✔
688
      -- TODO: float let through the cases if possible
689
      Case t bs -> Case t (mapList (mapWeighted (\(x :-> p) -> x :-> letFloating p)) bs) : ctx
2✔
690
      -- TODO: float let through if possible
691
      When b p -> When b (letFloating p) : ctx
2✔
692
      -- Boring cases
693
      Assert t -> Assert t : ctx
2✔
694
      GenHint h t -> GenHint h t : ctx
2✔
695
      DependsOn t t' -> DependsOn t t' : ctx
2✔
696
      TruePred -> TruePred : ctx
2✔
697
      FalsePred es -> FalsePred es : ctx
1✔
698
      Monitor m -> Monitor m : ctx
×
699

700
-- Common subexpression elimination but only on terms that are already let-bound.
701
letSubexpressionElimination :: Pred -> Pred
702
letSubexpressionElimination = go []
2✔
703
  where
704
    adjustSub :: HasSpec a => Var a -> Subst -> Subst
705
    adjustSub x sub =
2✔
706
      [ x' := t
2✔
707
      | x' := t <- sub
2✔
708
      , isNothing $ eqVar x x'
2✔
709
      , -- TODO: possibly freshen the binder where
710
      -- `x` appears instead?
711
      not $ Name x `appearsIn` t
1✔
712
      ]
713

714
    goBinder :: Subst -> Binder a -> Binder a
715
    goBinder sub (x :-> p) = x :-> go (adjustSub x sub) p
2✔
716

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

760
-- Turning Preds into Specifications. Here is where Propagation occurs ----
761

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

832
-- | Precondition: the `Pred fn` defines the `Var a`.
833
--   Runs in `GE` in order for us to have detailed context on failure.
834
computeSpec ::
835
  forall a. (HasSpec a, HasCallStack) => Var a -> Pred -> GE (Specification a)
836
computeSpec x p = computeSpecSimplified x (simplifyPred p)
2✔
837

838
computeSpecBinderSimplified :: Binder a -> GE (Specification a)
839
computeSpecBinderSimplified (x :-> p) = computeSpecSimplified x p
2✔
840

841
-- | Turn a list of branches into a SumSpec. If all the branches fail return an ErrorSpec.
842
--   Note the requirement of HasSpec(SumOver).
843
caseSpec ::
844
  forall as.
845
  HasSpec (SumOver as) =>
846
  Maybe String ->
847
  List (Weighted (Specification)) as ->
848
  Specification (SumOver as)
849
caseSpec tString ss
2✔
850
  | allBranchesFail ss =
2✔
851
      ErrorSpec
2✔
852
        ( NE.fromList
×
853
            [ "When simplifying SumSpec, all branches in a caseOn" ++ sumType tString ++ " simplify to False."
×
854
            , show spec
×
855
            ]
856
        )
857
  | True = spec
1✔
858
  where
859
    spec = loop tString ss
1✔
860

861
    allBranchesFail :: forall as2. List (Weighted Specification) as2 -> Bool
862
    allBranchesFail Nil = error "The impossible happened in allBranchesFail"
1✔
863
    allBranchesFail (Weighted _ s :> Nil) = isErrorLike s
2✔
864
    allBranchesFail (Weighted _ s :> ss2@(_ :> _)) = isErrorLike s && allBranchesFail ss2
2✔
865

866
    loop ::
867
      forall as3.
868
      HasSpec (SumOver as3) =>
869
      Maybe String ->
870
      List (Weighted Specification) as3 ->
871
      Specification (SumOver as3)
872
    loop _ Nil = error "The impossible happened in caseSpec"
1✔
873
    loop _ (s :> Nil) = thing s
2✔
874
    loop mTypeString (s :> ss1@(_ :> _))
875
      | Evidence <- prerequisites @(SumOver as3) =
2✔
876
          (typeSpec $ SumSpecRaw mTypeString theWeights (thing s) (loop Nothing ss1))
1✔
877
      where
878
        theWeights =
2✔
879
          case (weight s, totalWeight ss1) of
2✔
880
            (Nothing, Nothing) -> Nothing
2✔
881
            (a, b) -> Just (fromMaybe 1 a, fromMaybe (lengthList ss1) b)
1✔
882

883
------------------------------------------------------------------------
884
-- SumSpec et al
885
------------------------------------------------------------------------
886

887
-- | The Specification for Sums.
888
data SumSpec a b
889
  = SumSpecRaw
890
      (Maybe String) -- A String which is the type of arg in (caseOn arg branch1 .. branchN)
891
      (Maybe (Int, Int))
892
      (Specification a)
893
      (Specification b)
894

895
-- | The "normal" view of t`SumSpec` that doesn't take weights into account
896
pattern SumSpec ::
897
  (Maybe (Int, Int)) -> (Specification a) -> (Specification b) -> SumSpec a b
898
pattern SumSpec a b c <- SumSpecRaw _ a b c
2✔
899
  where
900
    SumSpec a b c = SumSpecRaw Nothing a b c
1✔
901

902
{-# COMPLETE SumSpec #-}
903

904
sumType :: Maybe String -> String
905
sumType Nothing = ""
×
906
sumType (Just x) = " type=" ++ x
×
907

908
totalWeight :: List (Weighted f) as -> Maybe Int
909
totalWeight = fmap getSum . foldMapList (fmap Semigroup.Sum . weight)
2✔
910

911
-- =================================
912
-- Operations on Stages and Plans
913

914
-- | Does nothing if the variable is not in the plan already.
915
mergeSolverStage :: SolverStage -> [SolverStage] -> [SolverStage]
916
mergeSolverStage (SolverStage x ps spec relevant) plan =
2✔
917
  [ case eqVar x y of
2✔
918
      Just Refl ->
919
        normalizeSolverStage $
2✔
920
          SolverStage
2✔
921
            y
2✔
922
            (ps ++ ps')
2✔
923
            (spec <> spec')
2✔
924
            (relevant <> relevant')
2✔
925
      Nothing -> stage
2✔
926
  | stage@(SolverStage y ps' spec' relevant') <- plan
2✔
927
  ]
928

929
isEmptyPlan :: SolverPlan -> Bool
930
isEmptyPlan (SolverPlan plan) = null plan
2✔
931

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

955
-- | Generate a satisfying `Env` for a `p : Pred fn`. The `Env` contains values for
956
-- all the free variables in `flattenPred p`.
957
genFromPreds :: forall m. MonadGenError m => Env -> Pred -> GenT m Env
958
-- TODO: remove this once optimisePred does a proper fixpoint computation
959
genFromPreds env0 (optimisePred . optimisePred -> preds) = do
2✔
960
  -- NOTE: this is just lazy enough that the work of flattening,
961
  -- computing dependencies, and linearizing is memoized in
962
  -- properties that use `genFromPreds`.
963
  origPlan <- runGE $ prepareLinearization preds
2✔
964
  let go :: Env -> SolverPlan -> GenT m Env
965
      go env plan | isEmptyPlan plan = pure env
2✔
966
      go env plan = do
2✔
967
        (env', plan') <- stepPlan origPlan env plan
2✔
968
        go env' plan'
2✔
969
  go env0 origPlan
2✔
970

971
-- | Push as much information we can backwards through the plan.
972
backPropagation :: Set Name -> SolverPlan -> SolverPlan
973
backPropagation relevant (SolverPlan initplan) = SolverPlan (go [] (reverse initplan))
2✔
974
  where
975
    go :: [SolverStage] -> [SolverStage] -> [SolverStage]
976
    go acc [] = acc
2✔
977
    go acc (s@(SolverStage (x :: Var a) ps spec _) : plan) = go (s : acc) plan'
2✔
978
      where
979
        newStages = concatMap newStage ps
2✔
980
        plan' = foldr mergeSolverStage plan newStages
2✔
981

982
        -- Note use of the Term Pattern Equal
983
        newStage (Assert (Equal tl tr))
2✔
984
          | [Name xl] <- Set.toList $ freeVarSet tl
2✔
985
          , [Name xr] <- Set.toList $ freeVarSet tr
2✔
986
          , Result ctxL <- toCtx xl tl
2✔
987
          , Result ctxR <- toCtx xr tr =
2✔
988
              case (eqVar x xl, eqVar x xr) of
2✔
989
                (Just Refl, _) ->
990
                  [ SolverStage
2✔
991
                      xr
2✔
992
                      []
2✔
993
                      (propagateSpec (forwardPropagateSpec spec ctxL) ctxR)
2✔
994
                      (Set.insert (Name x) relevant)
2✔
995
                  ]
996
                (_, Just Refl) ->
997
                  [ SolverStage
2✔
998
                      xl
2✔
999
                      []
2✔
1000
                      (propagateSpec (forwardPropagateSpec spec ctxR) ctxL)
2✔
1001
                      (Set.insert (Name x) relevant)
2✔
1002
                  ]
NEW
1003
                _ -> []
×
1004
        newStage (Case e bs)
1005
          | [Name xe] <- Set.toList $ freeVarSet e
2✔
1006
          , Nothing <- eqVar x xe =
2✔
1007
              [ SolverStage
2✔
1008
                  xe
2✔
1009
                  [Case e $ mapList mkBranch bs]
2✔
1010
                  TrueSpec
2✔
NEW
1011
                  (Set.insert (Name x) relevant) -- TODO: this is only true in the
×
1012
                  -- case where we actually rule some
1013
                  -- stuff out
1014
              ]
1015
          where
1016
            mkBranch :: Weighted Binder x -> Weighted Binder x
1017
            mkBranch (Weighted _ (xb :-> p))
2✔
1018
              | Result spec' <- computeSpec x p
2✔
1019
              , isErrorLike (spec <> spec') =
2✔
1020
                  Weighted Nothing $ xb :-> toPred False
1✔
1021
              | otherwise = Weighted Nothing (xb :-> TruePred)
1✔
1022
        newStage _ = []
2✔
1023

1024
-- | Function symbols for `(==.)`
1025
data EqW :: [Type] -> Type -> Type where
1026
  EqualW :: (Eq a, HasSpec a) => EqW '[a, a] Bool
1027

1028
deriving instance Eq (EqW dom rng)
×
1029

1030
instance Show (EqW d r) where
×
1031
  show EqualW = "==."
2✔
1032

1033
instance Syntax EqW where
2✔
1034
  isInfix EqualW = True
2✔
1035

1036
instance Semantics EqW where
1037
  semantics EqualW = (==)
2✔
1038

1039
instance Logic EqW where
×
1040
  propagate f ctxt (ExplainSpec es s) = explainSpec es $ propagate f ctxt s
2✔
1041
  propagate _ _ TrueSpec = TrueSpec
2✔
1042
  propagate _ _ (ErrorSpec msgs) = ErrorSpec msgs
1✔
1043
  propagate EqualW (HOLE :? Value x :> Nil) (SuspendedSpec v ps) =
1044
    constrained $ \v' -> Let (App EqualW (v' :> Lit x :> Nil)) (v :-> ps)
2✔
1045
  propagate EqualW (Value x :! Unary HOLE) (SuspendedSpec v ps) =
1046
    constrained $ \v' -> Let (App EqualW (Lit x :> v' :> Nil)) (v :-> ps)
2✔
1047
  propagate EqualW (HOLE :? Value s :> Nil) spec =
1048
    caseBoolSpec spec $ \case
2✔
1049
      True -> equalSpec s
2✔
1050
      False -> notEqualSpec s
2✔
1051
  propagate EqualW (Value s :! Unary HOLE) spec =
1052
    caseBoolSpec spec $ \case
2✔
1053
      True -> equalSpec s
2✔
1054
      False -> notEqualSpec s
2✔
1055

1056
  rewriteRules EqualW (t :> t' :> Nil) Evidence
2✔
1057
    | t == t' = Just $ lit True
2✔
1058
    | otherwise = Nothing
1✔
1059

1060
  saturate EqualW (FromGeneric (InjLeft _) :> t :> Nil) = [toPreds t (SumSpec Nothing TrueSpec (ErrorSpec (pure "saturatePred")))]
1✔
1061
  saturate EqualW (FromGeneric (InjRight _) :> t :> Nil) = [toPreds t (SumSpec Nothing (ErrorSpec (pure "saturatePred")) TrueSpec)]
1✔
1062
  saturate _ _ = []
2✔
1063

1064
infix 4 ==.
1065

1066
-- | Equality on the constraint-level
1067
(==.) :: HasSpec a => Term a -> Term a -> Term Bool
1068
(==.) = appTerm EqualW
2✔
1069

1070
-- | Pattern version of `(==.)` for rewrite rules
1071
pattern Equal ::
1072
  forall b.
1073
  () =>
1074
  forall a.
1075
  (b ~ Bool, Eq a, HasSpec a) =>
1076
  Term a ->
1077
  Term a ->
1078
  Term b
1079
pattern Equal x y <-
1080
  ( App
2✔
1081
      (getWitness -> Just EqualW)
1082
      (x :> y :> Nil)
1083
    )
1084

1085
-- | Like @if b then p else assert True@ in constraint-land
1086
whenTrue :: forall p. IsPred p => Term Bool -> p -> Pred
1087
whenTrue (Lit True) (toPred -> p) = p
2✔
1088
whenTrue (Lit False) _ = TruePred
2✔
1089
whenTrue b (toPred -> FalsePred {}) = assert (not_ b)
2✔
1090
whenTrue _ (toPred -> TruePred) = TruePred
2✔
1091
whenTrue b (toPred -> p) = When b p
2✔
1092

1093
-- | Is the variable x pinned to some free term in p? (free term
1094
-- meaning that all the variables in the term are free in p).
1095
--
1096
-- TODO: complete this with more cases!
1097
pinnedBy :: forall a. HasSpec a => Var a -> Pred -> Maybe (Term a)
1098
pinnedBy x (Assert (Equal t t'))
2✔
1099
  | V x' <- t, Just Refl <- eqVar x x' = Just t'
2✔
1100
  | V x' <- t', Just Refl <- eqVar x x' = Just t
2✔
1101
pinnedBy x (ElemPred True (V x') (xs NE.:| []))
1102
  | Just Refl <- eqVar x x' = Just (lit xs)
2✔
1103
pinnedBy x (And ps) = listToMaybe $ catMaybes $ map (pinnedBy x) ps
2✔
1104
pinnedBy x (Explain _ p) = pinnedBy x p
2✔
1105
pinnedBy _ _ = Nothing
2✔
1106

1107
-- ==================================================================================================
1108
-- TODO: generalize this to make it more flexible and extensible
1109
--
1110
-- The idea here is that we turn constraints into _extra_ constraints. C.f. the
1111
-- `mapIsJust` example in `Constrained.Examples.Map`:
1112

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

1117
-- Without this code the example wouldn't work because `y` is completely unconstrained during
1118
-- generation. With this code we _essentially_ rewrite occurences of `just_ A == B` to
1119
-- `[just_ A == B, case B of Nothing -> False; Just _ -> True]` to add extra information
1120
-- about the variables in `B`. Consequently, `y` in the example above is
1121
-- constrained to `MemberSpec [100 .. 102]` in the plan. This is implemented using the `saturate`
1122
-- function in the logic type class - in the example above for `==`.
1123
saturatePred :: Pred -> [Pred]
1124
saturatePred p =
2✔
1125
  -- [p]
1126
  --  + ---- if there is an Explain, it is still on 'p' here
1127
  --  |
1128
  --  v
1129
  p : case p of
2✔
1130
    Explain _es x -> saturatePred x -- Note that the Explain is still on the original 'p', so it is not lost
2✔
1131
    -- Note how the saturation is done by the 'saturate' method of the Logic class
1132
    Assert (App sym xs) -> saturate sym xs
2✔
1133
    _ -> []
2✔
1134

1135
-- ==================================================================
1136
-- HasSpec for Sums
1137
-- ==================================================================
1138

1139
guardSumSpec ::
1140
  forall a b.
1141
  (HasSpec a, HasSpec b, KnownNat (CountCases b)) =>
1142
  [String] ->
1143
  SumSpec a b ->
1144
  Specification (Sum a b)
1145
guardSumSpec msgs s@(SumSpecRaw tString _ sa sb)
2✔
1146
  | isErrorLike sa
2✔
1147
  , isErrorLike sb =
1✔
1148
      ErrorSpec $
×
1149
        NE.fromList $
×
1150
          msgs ++ ["All branches in a caseOn" ++ sumType tString ++ " simplify to False.", show s]
×
1151
  | otherwise = typeSpec s
1✔
1152

1153
instance (KnownNat (CountCases b), HasSpec a, HasSpec b) => Show (SumSpec a b) where
×
1154
  show sumspec@(SumSpecRaw tstring hint l r) = case alternateShow @(Sum a b) sumspec of
×
1155
    (BinaryShow _ ps) -> show $ parens (fromString ("SumSpec" ++ sumType tstring) /> vsep ps)
×
1156
    NonBinary ->
1157
      "(SumSpec"
×
1158
        ++ sumType tstring
×
1159
        ++ show (sumWeightL hint)
×
1160
        ++ " ("
×
1161
        ++ show l
×
1162
        ++ ") "
×
1163
        ++ show (sumWeightR hint)
×
1164
        ++ " ("
×
1165
        ++ show r
×
1166
        ++ "))"
×
1167

1168
combTypeName :: Maybe String -> Maybe String -> Maybe String
1169
combTypeName (Just x) (Just y) =
×
1170
  if x == y then Just x else Just ("(" ++ x ++ " | " ++ y ++ ")")
×
1171
combTypeName (Just x) Nothing = Just x
×
1172
combTypeName Nothing (Just x) = Just x
×
1173
combTypeName Nothing Nothing = Nothing
×
1174

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

1184
instance forall a b. (HasSpec a, HasSpec b, KnownNat (CountCases b)) => Monoid (SumSpec a b) where
×
1185
  mempty = SumSpec Nothing mempty mempty
2✔
1186

1187
-- | How many constructors are there in this type?
1188
type family CountCases a where
1189
  CountCases (Sum a b) = 1 + CountCases b
1190
  CountCases _ = 1
1191

1192
countCases :: forall a. KnownNat (CountCases a) => Int
1193
countCases = fromIntegral (natVal @(CountCases a) Proxy)
1✔
1194

1195
-- | The HasSpec Sum instance
1196
instance (HasSpec a, HasSpec b, KnownNat (CountCases b)) => HasSpec (Sum a b) where
1✔
1197
  type TypeSpec (Sum a b) = SumSpec a b
1198

1199
  type Prerequisites (Sum a b) = (HasSpec a, HasSpec b)
1200

1201
  emptySpec = mempty
2✔
1202

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

1205
  conformsTo (SumLeft a) (SumSpec _ sa _) = conformsToSpec a sa
2✔
1206
  conformsTo (SumRight b) (SumSpec _ _ sb) = conformsToSpec b sb
2✔
1207

1208
  genFromTypeSpec (SumSpec h sa sb)
2✔
1209
    | emptyA, emptyB = genError "genFromTypeSpec @SumSpec: empty"
1✔
1210
    | emptyA = SumRight <$> genFromSpecT sb
2✔
1211
    | emptyB = SumLeft <$> genFromSpecT sa
2✔
1212
    | fA == 0, fB == 0 = genError "All frequencies 0"
1✔
1213
    | otherwise =
×
1214
        frequencyT
2✔
1215
          [ (fA, SumLeft <$> genFromSpecT sa)
2✔
1216
          , (fB, SumRight <$> genFromSpecT sb)
2✔
1217
          ]
1218
    where
1219
      (max 0 -> fA, max 0 -> fB) = fromMaybe (1, countCases @b) h
2✔
1220
      emptyA = isErrorLike sa
2✔
1221
      emptyB = isErrorLike sb
2✔
1222

1223
  shrinkWithTypeSpec (SumSpec _ sa _) (SumLeft a) = SumLeft <$> shrinkWithSpec sa a
2✔
1224
  shrinkWithTypeSpec (SumSpec _ _ sb) (SumRight b) = SumRight <$> shrinkWithSpec sb b
2✔
1225

1226
  fixupWithTypeSpec (SumSpec _ sa _) (SumLeft a) = SumLeft <$> fixupWithSpec sa a
1✔
1227
  fixupWithTypeSpec (SumSpec _ _ sb) (SumRight b) = SumRight <$> fixupWithSpec sb b
2✔
1228

1229
  toPreds ct (SumSpec h sa sb) =
2✔
1230
    Case
2✔
1231
      ct
2✔
1232
      ( (Weighted (fst <$> h) $ bind $ \a -> satisfies a sa)
2✔
1233
          :> (Weighted (snd <$> h) $ bind $ \b -> satisfies b sb)
2✔
1234
          :> Nil
2✔
1235
      )
1236

1237
  cardinalTypeSpec (SumSpec _ leftspec rightspec) = addSpecInt (cardinality leftspec) (cardinality rightspec)
2✔
1238

1239
  typeSpecHasError (SumSpec _ x y) =
2✔
1240
    case (isErrorLike x, isErrorLike y) of
2✔
1241
      (True, True) -> Just $ (errorLikeMessage x <> errorLikeMessage y)
×
1242
      _ -> Nothing
2✔
1243

1244
  alternateShow (SumSpec h left right@(TypeSpec r [])) =
×
1245
    case alternateShow @b r of
×
1246
      (BinaryShow "SumSpec" ps) -> BinaryShow "SumSpec" ("|" <+> sumWeightL h <+> viaShow left : ps)
×
1247
      (BinaryShow "Cartesian" ps) ->
1248
        BinaryShow "SumSpec" ("|" <+> sumWeightL h <+> viaShow left : [parens ("Cartesian" /> vsep ps)])
×
1249
      _ ->
1250
        BinaryShow "SumSpec" ["|" <+> sumWeightL h <+> viaShow left, "|" <+> sumWeightR h <+> viaShow right]
×
1251
  alternateShow (SumSpec h left right) =
1252
    BinaryShow "SumSpec" ["|" <+> sumWeightL h <+> viaShow left, "|" <+> sumWeightR h <+> viaShow right]
×
1253

1254
-- ======================================
1255
-- Here are the Logic Instances for Sum
1256

1257
-- | Function symbols for `injLeft_` and `injRight_`
1258
data SumW dom rng where
1259
  InjLeftW :: SumW '[a] (Sum a b)
1260
  InjRightW :: SumW '[b] (Sum a b)
1261

1262
instance Show (SumW dom rng) where
×
1263
  show InjLeftW = "injLeft_"
2✔
1264
  show InjRightW = "injRight_"
2✔
1265

1266
deriving instance (Eq (SumW dom rng))
×
1267

1268
instance Syntax SumW
×
1269

1270
instance Semantics SumW where
1271
  semantics InjLeftW = SumLeft
2✔
1272
  semantics InjRightW = SumRight
2✔
1273

1274
instance Logic SumW where
1✔
1275
  propagateTypeSpec InjLeftW (Unary HOLE) (SumSpec _ sl _) cant = sl <> foldMap notEqualSpec [a | SumLeft a <- cant]
2✔
1276
  propagateTypeSpec InjRightW (Unary HOLE) (SumSpec _ _ sr) cant = sr <> foldMap notEqualSpec [a | SumRight a <- cant]
2✔
1277

1278
  propagateMemberSpec InjLeftW (Unary HOLE) es =
2✔
1279
    case [a | SumLeft a <- NE.toList es] of
2✔
1280
      (x : xs) -> MemberSpec (x :| xs)
2✔
1281
      [] ->
1282
        ErrorSpec $
2✔
1283
          pure $
×
1284
            "propMemberSpec (sumleft_ HOLE) on (MemberSpec es) with no SumLeft in es: " ++ show (NE.toList es)
×
1285
  propagateMemberSpec InjRightW (Unary HOLE) es =
1286
    case [a | SumRight a <- NE.toList es] of
2✔
1287
      (x : xs) -> MemberSpec (x :| xs)
2✔
1288
      [] ->
1289
        ErrorSpec $
2✔
1290
          pure $
×
1291
            "propagate(InjRight HOLE) on (MemberSpec es) with no SumLeft in es: " ++ show (NE.toList es)
×
1292

1293
  mapTypeSpec InjLeftW ts = typeSpec $ SumSpec Nothing (typeSpec ts) (ErrorSpec (pure "mapTypeSpec InjLeftW"))
1✔
1294
  mapTypeSpec InjRightW ts = typeSpec $ SumSpec Nothing (ErrorSpec (pure "mapTypeSpec InjRightW")) (typeSpec ts)
1✔
1295

1296
-- | Constructor for `Sum`
1297
injLeft_ :: (HasSpec a, HasSpec b, KnownNat (CountCases b)) => Term a -> Term (Sum a b)
1298
injLeft_ = appTerm InjLeftW
2✔
1299

1300
-- | Constructor for `Sum`
1301
injRight_ :: (HasSpec a, HasSpec b, KnownNat (CountCases b)) => Term b -> Term (Sum a b)
1302
injRight_ = appTerm InjRightW
2✔
1303

1304
-- | Pattern for building custom rewrite rules
1305
pattern InjRight ::
1306
  forall c.
1307
  () =>
1308
  forall a b.
1309
  ( c ~ Sum a b
1310
  , AppRequires SumW '[b] c
1311
  ) =>
1312
  Term b ->
1313
  Term c
1314
pattern InjRight x <- (App (getWitness -> Just InjRightW) (x :> Nil))
2✔
1315

1316
-- | Pattern for building custom rewrite rules
1317
pattern InjLeft ::
1318
  forall c.
1319
  () =>
1320
  forall a b.
1321
  ( c ~ Sum a b
1322
  , AppRequires SumW '[a] c
1323
  ) =>
1324
  Term a ->
1325
  Term c
1326
pattern InjLeft x <- App (getWitness -> Just InjLeftW) (x :> Nil)
2✔
1327

1328
sumWeightL, sumWeightR :: Maybe (Int, Int) -> Doc a
1329
sumWeightL Nothing = "1"
×
1330
sumWeightL (Just (x, _)) = fromString (show x)
×
1331
sumWeightR Nothing = "1"
×
1332
sumWeightR (Just (_, x)) = fromString (show x)
×
1333

1334
-- | Operations on Bool
1335
data BoolW (dom :: [Type]) (rng :: Type) where
1336
  NotW :: BoolW '[Bool] Bool
1337
  OrW :: BoolW '[Bool, Bool] Bool
1338

1339
deriving instance Eq (BoolW dom rng)
×
1340

1341
instance Show (BoolW dom rng) where
×
1342
  show NotW = "not_"
2✔
1343
  show OrW = "or_"
2✔
1344

1345
boolSem :: BoolW dom rng -> FunTy dom rng
1346
boolSem NotW = not
2✔
1347
boolSem OrW = (||)
2✔
1348

1349
instance Semantics BoolW where
1350
  semantics = boolSem
2✔
1351

1352
instance Syntax BoolW
×
1353

1354
-- ======= Logic instance BoolW
1355

1356
instance Logic BoolW where
1✔
1357
  propagate f ctxt (ExplainSpec [] s) = propagate f ctxt s
1✔
1358
  propagate f ctxt (ExplainSpec es s) = ExplainSpec es $ propagate f ctxt s
2✔
1359
  propagate _ _ TrueSpec = TrueSpec
2✔
1360
  propagate _ _ (ErrorSpec msgs) = ErrorSpec msgs
1✔
1361
  propagate NotW (Unary HOLE) (SuspendedSpec v ps) =
1362
    constrained $ \v' -> Let (App NotW (v' :> Nil)) (v :-> ps)
2✔
1363
  propagate NotW (Unary HOLE) spec =
1364
    caseBoolSpec spec (equalSpec . not)
2✔
1365
  propagate OrW (HOLE :<: x) (SuspendedSpec v ps) =
1366
    constrained $ \v' -> Let (App OrW (v' :> Lit x :> Nil)) (v :-> ps)
2✔
1367
  propagate OrW (x :>: HOLE) (SuspendedSpec v ps) =
1368
    constrained $ \v' -> Let (App OrW (Lit x :> v' :> Nil)) (v :-> ps)
2✔
1369
  propagate OrW (HOLE :<: s) spec =
1370
    caseBoolSpec spec (okOr s)
2✔
1371
  propagate OrW (s :>: HOLE) spec =
1372
    caseBoolSpec spec (okOr s)
2✔
1373

1374
  mapTypeSpec NotW () = typeSpec ()
×
1375

1376
-- | We have something like ('constant' ||. HOLE) must evaluate to 'need'.
1377
--   Return a (Specification Bool) for HOLE, that makes that True.
1378
okOr :: Bool -> Bool -> Specification Bool
1379
okOr constant need = case (constant, need) of
2✔
1380
  (True, True) -> TrueSpec
2✔
1381
  (True, False) ->
1382
    ErrorSpec
2✔
1383
      (pure ("(" ++ show constant ++ "||. HOLE) must equal False. That cannot be the case."))
×
1384
  (False, False) -> MemberSpec (pure False)
2✔
1385
  (False, True) -> MemberSpec (pure True)
2✔
1386

1387
-- | Disjunction on @`Term` `Bool`@, note that this will not cause backtracking during generation
1388
or_ :: Term Bool -> Term Bool -> Term Bool
1389
or_ = appTerm OrW
2✔
1390

1391
-- | Negation of booleans
1392
not_ :: Term Bool -> Term Bool
1393
not_ = appTerm NotW
2✔
1394

1395
-- ===============================================================================
1396
-- Syntax for Solving : stages and plans
1397

1398
data SolverStage where
1399
  SolverStage ::
1400
    HasSpec a =>
1401
    { stageVar :: Var a
×
1402
    , stagePreds :: [Pred]
×
1403
    , stageSpec :: Specification a
×
1404
    , relevantVariables :: Set Name
×
1405
    } ->
1406
    SolverStage
1407

1408
docVar :: Typeable a => Var a -> Doc h
1409
docVar (v :: Var a) = fromString (show v ++ " :: " ++ showType @a)
2✔
1410

1411
instance Pretty SolverStage where
×
1412
  pretty SolverStage {..} =
2✔
1413
    docVar stageVar
2✔
1414
      <+> "<-"
2✔
1415
        /> vsep'
2✔
1416
          ( [pretty stageSpec | not $ isTrueSpec stageSpec]
2✔
1417
              ++ ["---" | not $ null stagePreds, not $ isTrueSpec stageSpec]
2✔
1418
              ++ map pretty stagePreds
2✔
1419
              ++ ["_" | null stagePreds && isTrueSpec stageSpec]
1✔
1420
          )
1421

1422
newtype SolverPlan = SolverPlan {solverPlan :: [SolverStage]}
×
1423

1424
instance Pretty SolverPlan where
×
1425
  pretty SolverPlan {..} =
2✔
1426
    "SolverPlan" /> prettyLinear solverPlan
2✔
1427

1428
isTrueSpec :: Specification a -> Bool
1429
isTrueSpec TrueSpec = True
2✔
1430
isTrueSpec _ = False
2✔
1431

1432
prettyLinear :: [SolverStage] -> Doc ann
1433
prettyLinear = vsep' . map pretty
2✔
1434

1435
fromGESpec :: HasCallStack => GE (Specification a) -> Specification a
1436
fromGESpec ge = case ge of
2✔
1437
  Result s -> s
2✔
1438
  GenError xs -> ErrorSpec (catMessageList xs)
×
1439
  FatalError es -> error $ catMessages es
2✔
1440

1441
-- | Functor like property for Specification, but instead of a Haskell function (a -> b),
1442
--   it takes a function symbol (t '[a] b) from a to b.
1443
--   Note, in this context, a function symbol is some constructor of a witnesstype.
1444
--   Eg. ProdFstW, InjRightW, SingletonW, etc. NOT the lifted versions like fst_ singleton_,
1445
--   which construct Terms. We had to wait until here to define this because it
1446
--   depends on Semigroup property of Specification, and Asserting equality
1447
mapSpec ::
1448
  forall t a b.
1449
  AppRequires t '[a] b =>
1450
  t '[a] b ->
1451
  Specification a ->
1452
  Specification b
1453
mapSpec f (ExplainSpec es s) = explainSpec es (mapSpec f s)
2✔
1454
mapSpec f TrueSpec = mapTypeSpec f (emptySpec @a)
2✔
1455
mapSpec _ (ErrorSpec err) = ErrorSpec err
1✔
1456
mapSpec f (MemberSpec as) = MemberSpec $ NE.nub $ fmap (semantics f) as
2✔
1457
mapSpec f (SuspendedSpec x p) =
1458
  constrained $ \x' ->
2✔
1459
    Exists (\_ -> fatalError "mapSpec") (x :-> fold [Assert $ (x' ==. appTerm f (V x)), p])
1✔
1460
mapSpec f (TypeSpec ts cant) = mapTypeSpec f ts <> notMemberSpec (map (semantics f) cant)
2✔
1461

1462
-- TODO generalizeme!
1463
forwardPropagateSpec :: HasSpec a => Specification a -> Ctx a b -> Specification b
1464
forwardPropagateSpec s CtxHOLE = s
2✔
1465
forwardPropagateSpec s (CtxApp f (c :? Nil))
1466
  | Evidence <- ctxHasSpec c = mapSpec f (forwardPropagateSpec s c)
2✔
1467
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