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

input-output-hk / constrained-generators / 465

12 Dec 2025 02:24PM UTC coverage: 77.045% (-0.4%) from 77.401%
465

push

github

web-flow
Move strict optimization with ElemPred later (#72)

14 of 15 new or added lines in 2 files covered. (93.33%)

18 existing lines in 4 files now uncovered.

3974 of 5158 relevant lines covered (77.05%)

1.45 hits per line

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

77.79
/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✔
UNCOV
581
  DependsOn _ Lit {} -> TruePred
×
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
          , Name x `elem` [Name xl, Name xr]
1✔
987
          , Result ctxL <- toCtx xl tl
2✔
988
          , Result ctxR <- toCtx xr tr =
2✔
989
              case (eqVar x xl, eqVar x xr) of
2✔
990
                (Just Refl, _) ->
991
                  [ SolverStage
2✔
992
                      xr
2✔
993
                      []
2✔
994
                      (propagateSpec (forwardPropagateSpec spec ctxL) ctxR)
2✔
995
                      (Set.insert (Name x) relevant)
2✔
996
                  ]
997
                (_, Just Refl) ->
998
                  [ SolverStage
2✔
999
                      xl
2✔
1000
                      []
2✔
1001
                      (propagateSpec (forwardPropagateSpec spec ctxR) ctxL)
2✔
1002
                      (Set.insert (Name x) relevant)
2✔
1003
                  ]
1004
                _ -> error "The impossible happened"
×
1005
        newStage _ = []
2✔
1006

1007
-- | Function symbols for `(==.)`
1008
data EqW :: [Type] -> Type -> Type where
1009
  EqualW :: (Eq a, HasSpec a) => EqW '[a, a] Bool
1010

1011
deriving instance Eq (EqW dom rng)
×
1012

1013
instance Show (EqW d r) where
×
1014
  show EqualW = "==."
2✔
1015

1016
instance Syntax EqW where
2✔
1017
  isInfix EqualW = True
2✔
1018

1019
instance Semantics EqW where
1020
  semantics EqualW = (==)
2✔
1021

1022
instance Logic EqW where
×
1023
  propagate f ctxt (ExplainSpec es s) = explainSpec es $ propagate f ctxt s
2✔
1024
  propagate _ _ TrueSpec = TrueSpec
2✔
1025
  propagate _ _ (ErrorSpec msgs) = ErrorSpec msgs
1✔
1026
  propagate EqualW (HOLE :? Value x :> Nil) (SuspendedSpec v ps) =
1027
    constrained $ \v' -> Let (App EqualW (v' :> Lit x :> Nil)) (v :-> ps)
2✔
1028
  propagate EqualW (Value x :! Unary HOLE) (SuspendedSpec v ps) =
1029
    constrained $ \v' -> Let (App EqualW (Lit x :> v' :> Nil)) (v :-> ps)
2✔
1030
  propagate EqualW (HOLE :? Value s :> Nil) spec =
1031
    caseBoolSpec spec $ \case
2✔
1032
      True -> equalSpec s
2✔
1033
      False -> notEqualSpec s
2✔
1034
  propagate EqualW (Value s :! Unary HOLE) spec =
1035
    caseBoolSpec spec $ \case
2✔
1036
      True -> equalSpec s
2✔
1037
      False -> notEqualSpec s
2✔
1038

1039
  rewriteRules EqualW (t :> t' :> Nil) Evidence
2✔
1040
    | t == t' = Just $ lit True
2✔
1041
    | otherwise = Nothing
1✔
1042

1043
  saturate EqualW (FromGeneric (InjLeft _) :> t :> Nil) = [toPreds t (SumSpec Nothing TrueSpec (ErrorSpec (pure "saturatePred")))]
1✔
1044
  saturate EqualW (FromGeneric (InjRight _) :> t :> Nil) = [toPreds t (SumSpec Nothing (ErrorSpec (pure "saturatePred")) TrueSpec)]
1✔
1045
  saturate _ _ = []
2✔
1046

1047
infix 4 ==.
1048

1049
-- | Equality on the constraint-level
1050
(==.) :: HasSpec a => Term a -> Term a -> Term Bool
1051
(==.) = appTerm EqualW
2✔
1052

1053
-- | Pattern version of `(==.)` for rewrite rules
1054
pattern Equal ::
1055
  forall b.
1056
  () =>
1057
  forall a.
1058
  (b ~ Bool, Eq a, HasSpec a) =>
1059
  Term a ->
1060
  Term a ->
1061
  Term b
1062
pattern Equal x y <-
1063
  ( App
2✔
1064
      (getWitness -> Just EqualW)
1065
      (x :> y :> Nil)
1066
    )
1067

1068
-- | Like @if b then p else assert True@ in constraint-land
1069
whenTrue :: forall p. IsPred p => Term Bool -> p -> Pred
1070
whenTrue (Lit True) (toPred -> p) = p
2✔
1071
whenTrue (Lit False) _ = TruePred
2✔
1072
whenTrue b (toPred -> FalsePred {}) = assert (not_ b)
2✔
1073
whenTrue _ (toPred -> TruePred) = TruePred
2✔
1074
whenTrue b (toPred -> p) = When b p
2✔
1075

1076
-- | Is the variable x pinned to some free term in p? (free term
1077
-- meaning that all the variables in the term are free in p).
1078
--
1079
-- TODO: complete this with more cases!
1080
pinnedBy :: forall a. HasSpec a => Var a -> Pred -> Maybe (Term a)
1081
pinnedBy x (Assert (Equal t t'))
2✔
1082
  | V x' <- t, Just Refl <- eqVar x x' = Just t'
2✔
1083
  | V x' <- t', Just Refl <- eqVar x x' = Just t
2✔
1084
pinnedBy x (ElemPred True (V x') (xs NE.:| []))
1085
  | Just Refl <- eqVar x x' = Just (lit xs)
2✔
1086
pinnedBy x (And ps) = listToMaybe $ catMaybes $ map (pinnedBy x) ps
2✔
1087
pinnedBy x (Explain _ p) = pinnedBy x p
2✔
1088
pinnedBy _ _ = Nothing
2✔
1089

1090
-- ==================================================================================================
1091
-- TODO: generalize this to make it more flexible and extensible
1092
--
1093
-- The idea here is that we turn constraints into _extra_ constraints. C.f. the
1094
-- `mapIsJust` example in `Constrained.Examples.Map`:
1095

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

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

1118
-- ==================================================================
1119
-- HasSpec for Sums
1120
-- ==================================================================
1121

1122
guardSumSpec ::
1123
  forall a b.
1124
  (HasSpec a, HasSpec b, KnownNat (CountCases b)) =>
1125
  [String] ->
1126
  SumSpec a b ->
1127
  Specification (Sum a b)
1128
guardSumSpec msgs s@(SumSpecRaw tString _ sa sb)
2✔
1129
  | isErrorLike sa
2✔
1130
  , isErrorLike sb =
1✔
1131
      ErrorSpec $
×
1132
        NE.fromList $
×
1133
          msgs ++ ["All branches in a caseOn" ++ sumType tString ++ " simplify to False.", show s]
×
1134
  | otherwise = typeSpec s
1✔
1135

1136
instance (KnownNat (CountCases b), HasSpec a, HasSpec b) => Show (SumSpec a b) where
×
1137
  show sumspec@(SumSpecRaw tstring hint l r) = case alternateShow @(Sum a b) sumspec of
×
1138
    (BinaryShow _ ps) -> show $ parens (fromString ("SumSpec" ++ sumType tstring) /> vsep ps)
×
1139
    NonBinary ->
1140
      "(SumSpec"
×
1141
        ++ sumType tstring
×
1142
        ++ show (sumWeightL hint)
×
1143
        ++ " ("
×
1144
        ++ show l
×
1145
        ++ ") "
×
1146
        ++ show (sumWeightR hint)
×
1147
        ++ " ("
×
1148
        ++ show r
×
1149
        ++ "))"
×
1150

1151
combTypeName :: Maybe String -> Maybe String -> Maybe String
1152
combTypeName (Just x) (Just y) =
×
1153
  if x == y then Just x else Just ("(" ++ x ++ " | " ++ y ++ ")")
×
1154
combTypeName (Just x) Nothing = Just x
×
1155
combTypeName Nothing (Just x) = Just x
×
1156
combTypeName Nothing Nothing = Nothing
×
1157

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

1167
instance forall a b. (HasSpec a, HasSpec b, KnownNat (CountCases b)) => Monoid (SumSpec a b) where
×
1168
  mempty = SumSpec Nothing mempty mempty
2✔
1169

1170
-- | How many constructors are there in this type?
1171
type family CountCases a where
1172
  CountCases (Sum a b) = 1 + CountCases b
1173
  CountCases _ = 1
1174

1175
countCases :: forall a. KnownNat (CountCases a) => Int
1176
countCases = fromIntegral (natVal @(CountCases a) Proxy)
1✔
1177

1178
-- | The HasSpec Sum instance
1179
instance (HasSpec a, HasSpec b, KnownNat (CountCases b)) => HasSpec (Sum a b) where
1✔
1180
  type TypeSpec (Sum a b) = SumSpec a b
1181

1182
  type Prerequisites (Sum a b) = (HasSpec a, HasSpec b)
1183

1184
  emptySpec = mempty
2✔
1185

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

1188
  conformsTo (SumLeft a) (SumSpec _ sa _) = conformsToSpec a sa
2✔
1189
  conformsTo (SumRight b) (SumSpec _ _ sb) = conformsToSpec b sb
2✔
1190

1191
  genFromTypeSpec (SumSpec h sa sb)
2✔
1192
    | emptyA, emptyB = genError "genFromTypeSpec @SumSpec: empty"
1✔
1193
    | emptyA = SumRight <$> genFromSpecT sb
2✔
1194
    | emptyB = SumLeft <$> genFromSpecT sa
2✔
1195
    | fA == 0, fB == 0 = genError "All frequencies 0"
1✔
1196
    | otherwise =
×
1197
        frequencyT
2✔
1198
          [ (fA, SumLeft <$> genFromSpecT sa)
2✔
1199
          , (fB, SumRight <$> genFromSpecT sb)
2✔
1200
          ]
1201
    where
1202
      (max 0 -> fA, max 0 -> fB) = fromMaybe (1, countCases @b) h
2✔
1203
      emptyA = isErrorLike sa
2✔
1204
      emptyB = isErrorLike sb
2✔
1205

1206
  shrinkWithTypeSpec (SumSpec _ sa _) (SumLeft a) = SumLeft <$> shrinkWithSpec sa a
2✔
1207
  shrinkWithTypeSpec (SumSpec _ _ sb) (SumRight b) = SumRight <$> shrinkWithSpec sb b
2✔
1208

1209
  fixupWithTypeSpec (SumSpec _ sa _) (SumLeft a) = SumLeft <$> fixupWithSpec sa a
1✔
1210
  fixupWithTypeSpec (SumSpec _ _ sb) (SumRight b) = SumRight <$> fixupWithSpec sb b
2✔
1211

1212
  toPreds ct (SumSpec h sa sb) =
2✔
1213
    Case
2✔
1214
      ct
2✔
1215
      ( (Weighted (fst <$> h) $ bind $ \a -> satisfies a sa)
2✔
1216
          :> (Weighted (snd <$> h) $ bind $ \b -> satisfies b sb)
2✔
1217
          :> Nil
2✔
1218
      )
1219

1220
  cardinalTypeSpec (SumSpec _ leftspec rightspec) = addSpecInt (cardinality leftspec) (cardinality rightspec)
2✔
1221

1222
  typeSpecHasError (SumSpec _ x y) =
2✔
1223
    case (isErrorLike x, isErrorLike y) of
2✔
1224
      (True, True) -> Just $ (errorLikeMessage x <> errorLikeMessage y)
×
1225
      _ -> Nothing
2✔
1226

1227
  alternateShow (SumSpec h left right@(TypeSpec r [])) =
×
1228
    case alternateShow @b r of
×
1229
      (BinaryShow "SumSpec" ps) -> BinaryShow "SumSpec" ("|" <+> sumWeightL h <+> viaShow left : ps)
×
1230
      (BinaryShow "Cartesian" ps) ->
1231
        BinaryShow "SumSpec" ("|" <+> sumWeightL h <+> viaShow left : [parens ("Cartesian" /> vsep ps)])
×
1232
      _ ->
1233
        BinaryShow "SumSpec" ["|" <+> sumWeightL h <+> viaShow left, "|" <+> sumWeightR h <+> viaShow right]
×
1234
  alternateShow (SumSpec h left right) =
1235
    BinaryShow "SumSpec" ["|" <+> sumWeightL h <+> viaShow left, "|" <+> sumWeightR h <+> viaShow right]
×
1236

1237
-- ======================================
1238
-- Here are the Logic Instances for Sum
1239

1240
-- | Function symbols for `injLeft_` and `injRight_`
1241
data SumW dom rng where
1242
  InjLeftW :: SumW '[a] (Sum a b)
1243
  InjRightW :: SumW '[b] (Sum a b)
1244

1245
instance Show (SumW dom rng) where
×
1246
  show InjLeftW = "injLeft_"
2✔
1247
  show InjRightW = "injRight_"
2✔
1248

1249
deriving instance (Eq (SumW dom rng))
×
1250

1251
instance Syntax SumW
×
1252

1253
instance Semantics SumW where
1254
  semantics InjLeftW = SumLeft
2✔
1255
  semantics InjRightW = SumRight
2✔
1256

1257
instance Logic SumW where
1✔
1258
  propagateTypeSpec InjLeftW (Unary HOLE) (SumSpec _ sl _) cant = sl <> foldMap notEqualSpec [a | SumLeft a <- cant]
2✔
1259
  propagateTypeSpec InjRightW (Unary HOLE) (SumSpec _ _ sr) cant = sr <> foldMap notEqualSpec [a | SumRight a <- cant]
2✔
1260

1261
  propagateMemberSpec InjLeftW (Unary HOLE) es =
2✔
1262
    case [a | SumLeft a <- NE.toList es] of
2✔
1263
      (x : xs) -> MemberSpec (x :| xs)
2✔
1264
      [] ->
1265
        ErrorSpec $
2✔
1266
          pure $
×
1267
            "propMemberSpec (sumleft_ HOLE) on (MemberSpec es) with no SumLeft in es: " ++ show (NE.toList es)
×
1268
  propagateMemberSpec InjRightW (Unary HOLE) es =
1269
    case [a | SumRight a <- NE.toList es] of
2✔
1270
      (x : xs) -> MemberSpec (x :| xs)
2✔
1271
      [] ->
1272
        ErrorSpec $
2✔
1273
          pure $
×
1274
            "propagate(InjRight HOLE) on (MemberSpec es) with no SumLeft in es: " ++ show (NE.toList es)
×
1275

1276
  mapTypeSpec InjLeftW ts = typeSpec $ SumSpec Nothing (typeSpec ts) (ErrorSpec (pure "mapTypeSpec InjLeftW"))
1✔
1277
  mapTypeSpec InjRightW ts = typeSpec $ SumSpec Nothing (ErrorSpec (pure "mapTypeSpec InjRightW")) (typeSpec ts)
1✔
1278

1279
-- | Constructor for `Sum`
1280
injLeft_ :: (HasSpec a, HasSpec b, KnownNat (CountCases b)) => Term a -> Term (Sum a b)
1281
injLeft_ = appTerm InjLeftW
2✔
1282

1283
-- | Constructor for `Sum`
1284
injRight_ :: (HasSpec a, HasSpec b, KnownNat (CountCases b)) => Term b -> Term (Sum a b)
1285
injRight_ = appTerm InjRightW
2✔
1286

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

1299
-- | Pattern for building custom rewrite rules
1300
pattern InjLeft ::
1301
  forall c.
1302
  () =>
1303
  forall a b.
1304
  ( c ~ Sum a b
1305
  , AppRequires SumW '[a] c
1306
  ) =>
1307
  Term a ->
1308
  Term c
1309
pattern InjLeft x <- App (getWitness -> Just InjLeftW) (x :> Nil)
2✔
1310

1311
sumWeightL, sumWeightR :: Maybe (Int, Int) -> Doc a
1312
sumWeightL Nothing = "1"
×
1313
sumWeightL (Just (x, _)) = fromString (show x)
×
1314
sumWeightR Nothing = "1"
×
1315
sumWeightR (Just (_, x)) = fromString (show x)
×
1316

1317
-- | Operations on Bool
1318
data BoolW (dom :: [Type]) (rng :: Type) where
1319
  NotW :: BoolW '[Bool] Bool
1320
  OrW :: BoolW '[Bool, Bool] Bool
1321

1322
deriving instance Eq (BoolW dom rng)
×
1323

1324
instance Show (BoolW dom rng) where
×
1325
  show NotW = "not_"
2✔
1326
  show OrW = "or_"
2✔
1327

1328
boolSem :: BoolW dom rng -> FunTy dom rng
1329
boolSem NotW = not
2✔
1330
boolSem OrW = (||)
2✔
1331

1332
instance Semantics BoolW where
1333
  semantics = boolSem
2✔
1334

1335
instance Syntax BoolW
×
1336

1337
-- ======= Logic instance BoolW
1338

1339
instance Logic BoolW where
1✔
1340
  propagate f ctxt (ExplainSpec [] s) = propagate f ctxt s
1✔
1341
  propagate f ctxt (ExplainSpec es s) = ExplainSpec es $ propagate f ctxt s
2✔
1342
  propagate _ _ TrueSpec = TrueSpec
2✔
1343
  propagate _ _ (ErrorSpec msgs) = ErrorSpec msgs
1✔
1344
  propagate NotW (Unary HOLE) (SuspendedSpec v ps) =
1345
    constrained $ \v' -> Let (App NotW (v' :> Nil)) (v :-> ps)
2✔
1346
  propagate NotW (Unary HOLE) spec =
1347
    caseBoolSpec spec (equalSpec . not)
2✔
1348
  propagate OrW (HOLE :<: x) (SuspendedSpec v ps) =
1349
    constrained $ \v' -> Let (App OrW (v' :> Lit x :> Nil)) (v :-> ps)
2✔
1350
  propagate OrW (x :>: HOLE) (SuspendedSpec v ps) =
1351
    constrained $ \v' -> Let (App OrW (Lit x :> v' :> Nil)) (v :-> ps)
2✔
1352
  propagate OrW (HOLE :<: s) spec =
1353
    caseBoolSpec spec (okOr s)
2✔
1354
  propagate OrW (s :>: HOLE) spec =
1355
    caseBoolSpec spec (okOr s)
2✔
1356

1357
  mapTypeSpec NotW () = typeSpec ()
×
1358

1359
-- | We have something like ('constant' ||. HOLE) must evaluate to 'need'.
1360
--   Return a (Specification Bool) for HOLE, that makes that True.
1361
okOr :: Bool -> Bool -> Specification Bool
1362
okOr constant need = case (constant, need) of
2✔
1363
  (True, True) -> TrueSpec
2✔
1364
  (True, False) ->
1365
    ErrorSpec
2✔
1366
      (pure ("(" ++ show constant ++ "||. HOLE) must equal False. That cannot be the case."))
×
1367
  (False, False) -> MemberSpec (pure False)
2✔
1368
  (False, True) -> MemberSpec (pure True)
2✔
1369

1370
-- | Disjunction on @`Term` `Bool`@, note that this will not cause backtracking during generation
1371
or_ :: Term Bool -> Term Bool -> Term Bool
1372
or_ = appTerm OrW
2✔
1373

1374
-- | Negation of booleans
1375
not_ :: Term Bool -> Term Bool
1376
not_ = appTerm NotW
2✔
1377

1378
-- ===============================================================================
1379
-- Syntax for Solving : stages and plans
1380

1381
data SolverStage where
1382
  SolverStage ::
1383
    HasSpec a =>
1384
    { stageVar :: Var a
×
1385
    , stagePreds :: [Pred]
×
1386
    , stageSpec :: Specification a
×
1387
    , relevantVariables :: Set Name
×
1388
    } ->
1389
    SolverStage
1390

1391
docVar :: Typeable a => Var a -> Doc h
1392
docVar (v :: Var a) = fromString (show v ++ " :: " ++ showType @a)
2✔
1393

1394
instance Pretty SolverStage where
×
1395
  pretty SolverStage {..} =
2✔
1396
    docVar stageVar
2✔
1397
      <+> "<-"
2✔
1398
        /> vsep'
2✔
1399
          ( [pretty stageSpec | not $ isTrueSpec stageSpec]
2✔
1400
              ++ ["---" | not $ null stagePreds, not $ isTrueSpec stageSpec]
2✔
1401
              ++ map pretty stagePreds
2✔
1402
              ++ ["_" | null stagePreds && isTrueSpec stageSpec]
1✔
1403
          )
1404

1405
newtype SolverPlan = SolverPlan {solverPlan :: [SolverStage]}
×
1406

1407
instance Pretty SolverPlan where
×
1408
  pretty SolverPlan {..} =
2✔
1409
    "SolverPlan" /> prettyLinear solverPlan
2✔
1410

1411
isTrueSpec :: Specification a -> Bool
1412
isTrueSpec TrueSpec = True
2✔
1413
isTrueSpec _ = False
2✔
1414

1415
prettyLinear :: [SolverStage] -> Doc ann
1416
prettyLinear = vsep' . map pretty
2✔
1417

1418
fromGESpec :: HasCallStack => GE (Specification a) -> Specification a
1419
fromGESpec ge = case ge of
2✔
1420
  Result s -> s
2✔
1421
  GenError xs -> ErrorSpec (catMessageList xs)
×
1422
  FatalError es -> error $ catMessages es
2✔
1423

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

1445
-- TODO generalizeme!
1446
forwardPropagateSpec :: HasSpec a => Specification a -> Ctx a b -> Specification b
1447
forwardPropagateSpec s CtxHOLE = s
2✔
1448
forwardPropagateSpec s (CtxApp f (c :? Nil))
1449
  | Evidence <- ctxHasSpec c = mapSpec f (forwardPropagateSpec s c)
2✔
1450
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