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

input-output-hk / constrained-generators / 338

22 Oct 2025 11:42AM UTC coverage: 77.014% (+0.2%) from 76.798%
338

push

github

web-flow
Resurrect shrinking and improve it (#51)

41 of 61 new or added lines in 9 files covered. (67.21%)

11 existing lines in 5 files now uncovered.

3890 of 5051 relevant lines covered (77.01%)

1.45 hits per line

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

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

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

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

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

43
  -- * Other syntax
44
  whenTrue,
45

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

54
  mapSpec,
55
  forwardPropagateSpec,
56
) where
57

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

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

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

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

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

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

151
-- | Shrink a value while preserving adherence to a `Specification`
152
shrinkWithSpec :: forall a. HasSpec a => Specification a -> a -> [a]
153
-- TODO: possibly allow for ignoring the `conformsToSpec` check in the `TypeSpec`
154
-- case when you know what you're doing
155
shrinkWithSpec (simplifySpec -> spec) a = filter (`conformsToSpec` spec) $ case spec of
2✔
156
  ExplainSpec _ s -> shrinkWithSpec s a
2✔
157
  -- TODO: filter on can't if we have a known to be sound shrinker
158
  TypeSpec s _ -> shrinkWithTypeSpec s a
2✔
159
  SuspendedSpec x p -> shrinkFromPreds p x a ++ shr a
2✔
160
  MemberSpec {} -> shr a
2✔
161
  TrueSpec -> shr a
2✔
162
  ErrorSpec {} -> []
×
163
  where
164
    shr = shrinkWithTypeSpec (emptySpec @a)
2✔
165

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

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

207
    -- Fix the rest of the plan given an environment `env` for the plan so far
208
    fixupPlan :: Env -> [SolverStage] -> Maybe Env
209
    fixupPlan env [] = pure env
2✔
210
    fixupPlan env ((unsafeSubstStage env -> SolverStage {..}) : plan) =
211
      case Env.lookup (env <> initialEnv) stageVar >>= fixupWithSpec stageSpec of
2✔
212
        Nothing -> Nothing
2✔
213
        Just a -> fixupPlan (Env.extend stageVar a env) plan
2✔
214

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

224
-- Debugging --------------------------------------------------------------
225

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

240
-- | Pretty-print the plan for a `Specifcation` in the terminal for debugging
241
printPlan :: HasSpec a => Specification a -> IO ()
242
printPlan = print . prettyPlan
×
243

244
-- | Plan pretty-printer for debugging
245
prettyPlan :: HasSpec a => Specification a -> Doc ann
246
prettyPlan (simplifySpec -> spec)
×
247
  | SuspendedSpec _ p <- spec
×
248
  , Result plan <- prepareLinearization p =
×
249
      vsep'
×
250
        [ "Simplified spec:" /> pretty spec
×
251
        , pretty plan
×
252
        ]
253
  | otherwise = "Simplfied spec:" /> pretty spec
×
254

255
-- ---------------------- Building a plan -----------------------------------
256

257
unsafeSubstStage :: Env -> SolverStage -> SolverStage
258
unsafeSubstStage env (SolverStage y ps spec relevant) =
2✔
259
  normalizeSolverStage $ SolverStage y (substPred env <$> ps) spec relevant
1✔
260

261
substStage :: HasSpec a => Set Name -> Var a -> a -> SolverStage -> SolverStage
262
substStage rel' x val (SolverStage y ps spec relevant) =
2✔
263
  normalizeSolverStage $ SolverStage y (substPred env <$> ps) spec relevant'
2✔
264
  where env = Env.singleton x val
2✔
265
        relevant' | Name x `appearsIn` ps = rel' <> relevant
2✔
266
                  | otherwise = relevant
1✔
267

268
normalizeSolverStage :: SolverStage -> SolverStage
269
normalizeSolverStage (SolverStage x ps spec relevant) = SolverStage x ps'' (spec <> spec') relevant
2✔
270
  where
271
    (ps', ps'') = partition ((1 ==) . Set.size . freeVarSet) ps
2✔
272
    spec' = fromGESpec $ computeSpec x (And ps')
2✔
273

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

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

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

318
    goBinder ::
319
      Set Int ->
320
      Binder a ->
321
      [Pred] ->
322
      (HasSpec a => Var a -> [Pred] -> [Pred]) ->
323
      [Pred]
324
    goBinder fvs (x :-> p) ps k = k x' $ go (Set.insert (nameOf x') fvs) (p' : ps)
2✔
325
      where
326
        (x', p') = freshen x p fvs
2✔
327

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

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

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

375
------------------------------------------------------------------------
376
-- Simplification of Specifications
377
------------------------------------------------------------------------
378

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

394
-- ------- Stages of simplifying -------------------------------
395

396
-- TODO: it might be necessary to run aggressiveInlining again after the let floating etc.
397
optimisePred :: Pred -> Pred
398
optimisePred p =
2✔
399
  simplifyPred
2✔
400
    . letSubexpressionElimination
2✔
401
    . letFloating
2✔
402
    . aggressiveInlining
2✔
403
    . simplifyPred
2✔
404
    $ p
2✔
405

406
aggressiveInlining :: Pred -> Pred
407
aggressiveInlining pred
2✔
408
  | inlined = aggressiveInlining pInlined
2✔
409
  | otherwise = pred
1✔
410
  where
411
    (pInlined, Any inlined) = runWriter $ go (freeVars pred) [] pred
2✔
412

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

415
    underBinderSub :: HasSpec a => Subst -> Var a -> Subst
416
    underBinderSub sub x =
2✔
417
      [ x' := t
2✔
418
      | x' := t <- sub
2✔
419
      , isNothing $ eqVar x x'
1✔
420
      ]
421

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

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

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

520
-- | Apply a substitution and simplify the resulting term if the
521
-- substitution changed the term.
522
substituteAndSimplifyTerm :: Subst -> Term a -> Term a
523
substituteAndSimplifyTerm sub t =
2✔
524
  case runWriter $ substituteTerm' sub t of
2✔
525
    (t', Any b)
526
      | b -> simplifyTerm t'
2✔
527
      | otherwise -> t'
1✔
528

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

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

597
simplifyPreds :: [Pred] -> [Pred]
598
simplifyPreds = go [] . map simplifyPred
2✔
599
  where
600
    go acc [] = reverse acc
2✔
601
    go _ (FalsePred err : _) = [FalsePred err]
1✔
602
    go acc (TruePred : ps) = go acc ps
2✔
603
    go acc (p : ps) = go (p : acc) ps
2✔
604

605
simplifyBinder :: Binder a -> Binder a
606
simplifyBinder (x :-> p) = x :-> simplifyPred p
2✔
607

608
-- TODO: this can probably be cleaned up and generalized along with generalizing
609
-- to make sure we float lets in some missing cases.
610
letFloating :: Pred -> Pred
611
letFloating = fold . go []
2✔
612
  where
613
    goBlock ctx ps = goBlock' (freeVarNames ctx <> freeVarNames ps) ctx ps
2✔
614

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

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

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

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

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

692
    goBinder :: Subst -> Binder a -> Binder a
693
    goBinder sub (x :-> p) = x :-> go (adjustSub x sub) p
2✔
694

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

738
-- Turning Preds into Specifications. Here is where Propagation occurs ----
739

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

810
-- | Precondition: the `Pred fn` defines the `Var a`.
811
--   Runs in `GE` in order for us to have detailed context on failure.
812
computeSpec ::
813
  forall a. (HasSpec a, HasCallStack) => Var a -> Pred -> GE (Specification a)
814
computeSpec x p = computeSpecSimplified x (simplifyPred p)
2✔
815

816
computeSpecBinderSimplified :: Binder a -> GE (Specification a)
817
computeSpecBinderSimplified (x :-> p) = computeSpecSimplified x p
2✔
818

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

839
    allBranchesFail :: forall as2. List (Weighted Specification) as2 -> Bool
840
    allBranchesFail Nil = error "The impossible happened in allBranchesFail"
1✔
841
    allBranchesFail (Weighted _ s :> Nil) = isErrorLike s
2✔
842
    allBranchesFail (Weighted _ s :> ss2@(_ :> _)) = isErrorLike s && allBranchesFail ss2
2✔
843

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

861
------------------------------------------------------------------------
862
-- SumSpec et al
863
------------------------------------------------------------------------
864

865
-- | The Specification for Sums.
866
data SumSpec a b
867
  = SumSpecRaw
868
      (Maybe String) -- A String which is the type of arg in (caseOn arg branch1 .. branchN)
869
      (Maybe (Int, Int))
870
      (Specification a)
871
      (Specification b)
872

873
-- | The "normal" view of t`SumSpec` that doesn't take weights into account
874
pattern SumSpec ::
875
  (Maybe (Int, Int)) -> (Specification a) -> (Specification b) -> SumSpec a b
876
pattern SumSpec a b c <- SumSpecRaw _ a b c
2✔
877
  where
878
    SumSpec a b c = SumSpecRaw Nothing a b c
1✔
879

880
{-# COMPLETE SumSpec #-}
881

882
sumType :: Maybe String -> String
883
sumType Nothing = ""
×
884
sumType (Just x) = " type=" ++ x
×
885

886
totalWeight :: List (Weighted f) as -> Maybe Int
887
totalWeight = fmap getSum . foldMapList (fmap Semigroup.Sum . weight)
2✔
888

889
-- =================================
890
-- Operations on Stages and Plans
891

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

906
isEmptyPlan :: SolverPlan -> Bool
907
isEmptyPlan (SolverPlan plan) = null plan
2✔
908

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

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

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

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

970
-- | Function symbols for `(==.)`
971
data EqW :: [Type] -> Type -> Type where
972
  EqualW :: (Eq a, HasSpec a) => EqW '[a, a] Bool
973

974
deriving instance Eq (EqW dom rng)
×
975

976
instance Show (EqW d r) where
×
977
  show EqualW = "==."
2✔
978

979
instance Syntax EqW where
2✔
980
  isInfix EqualW = True
2✔
981

982
instance Semantics EqW where
983
  semantics EqualW = (==)
2✔
984

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

1002
  rewriteRules EqualW (t :> t' :> Nil) Evidence
2✔
1003
    | t == t' = Just $ lit True
2✔
1004
    | otherwise = Nothing
1✔
1005

1006
  saturate EqualW (FromGeneric (InjLeft _) :> t :> Nil) = [toPreds t (SumSpec Nothing TrueSpec (ErrorSpec (pure "saturatePred")))]
1✔
1007
  saturate EqualW (FromGeneric (InjRight _) :> t :> Nil) = [toPreds t (SumSpec Nothing (ErrorSpec (pure "saturatePred")) TrueSpec)]
1✔
1008
  saturate _ _ = []
2✔
1009

1010
infix 4 ==.
1011

1012
-- | Equality on the constraint-level
1013
(==.) :: HasSpec a => Term a -> Term a -> Term Bool
1014
(==.) = appTerm EqualW
2✔
1015

1016
-- | Pattern version of `(==.)` for rewrite rules
1017
pattern Equal ::
1018
  forall b.
1019
  () =>
1020
  forall a.
1021
  (b ~ Bool, Eq a, HasSpec a) =>
1022
  Term a ->
1023
  Term a ->
1024
  Term b
1025
pattern Equal x y <-
1026
  ( App
2✔
1027
      (getWitness -> Just EqualW)
1028
      (x :> y :> Nil)
1029
    )
1030

1031
-- | Like @if b then p else assert True@ in constraint-land
1032
whenTrue :: forall p. IsPred p => Term Bool -> p -> Pred
1033
whenTrue (Lit True) (toPred -> p) = p
2✔
1034
whenTrue (Lit False) _ = TruePred
2✔
1035
whenTrue b (toPred -> FalsePred {}) = assert (not_ b)
2✔
1036
whenTrue _ (toPred -> TruePred) = TruePred
2✔
1037
whenTrue b (toPred -> p) = When b p
2✔
1038

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

1050
-- ==================================================================================================
1051
-- TODO: generalize this to make it more flexible and extensible
1052
--
1053
-- The idea here is that we turn constraints into _extra_ constraints. C.f. the
1054
-- `mapIsJust` example in `Constrained.Examples.Map`:
1055

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

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

1078
-- ==================================================================
1079
-- HasSpec for Sums
1080
-- ==================================================================
1081

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

1096
instance (KnownNat (CountCases b), HasSpec a, HasSpec b) => Show (SumSpec a b) where
×
1097
  show sumspec@(SumSpecRaw tstring hint l r) = case alternateShow @(Sum a b) sumspec of
×
1098
    (BinaryShow _ ps) -> show $ parens (fromString ("SumSpec" ++ sumType tstring) /> vsep ps)
×
1099
    NonBinary ->
1100
      "(SumSpec"
×
1101
        ++ sumType tstring
×
1102
        ++ show (sumWeightL hint)
×
1103
        ++ " ("
×
1104
        ++ show l
×
1105
        ++ ") "
×
1106
        ++ show (sumWeightR hint)
×
1107
        ++ " ("
×
1108
        ++ show r
×
1109
        ++ "))"
×
1110

1111
combTypeName :: Maybe String -> Maybe String -> Maybe String
1112
combTypeName (Just x) (Just y) =
×
1113
  if x == y then Just x else Just ("(" ++ x ++ " | " ++ y ++ ")")
×
1114
combTypeName (Just x) Nothing = Just x
×
1115
combTypeName Nothing (Just x) = Just x
×
1116
combTypeName Nothing Nothing = Nothing
×
1117

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

1127
instance forall a b. (HasSpec a, HasSpec b, KnownNat (CountCases b)) => Monoid (SumSpec a b) where
×
1128
  mempty = SumSpec Nothing mempty mempty
2✔
1129

1130
-- | How many constructors are there in this type?
1131
type family CountCases a where
1132
  CountCases (Sum a b) = 1 + CountCases b
1133
  CountCases _ = 1
1134

1135
countCases :: forall a. KnownNat (CountCases a) => Int
1136
countCases = fromIntegral (natVal @(CountCases a) Proxy)
1✔
1137

1138
-- | The HasSpec Sum instance
1139
instance (HasSpec a, HasSpec b, KnownNat (CountCases b)) => HasSpec (Sum a b) where
1✔
1140
  type TypeSpec (Sum a b) = SumSpec a b
1141

1142
  type Prerequisites (Sum a b) = (HasSpec a, HasSpec b)
1143

1144
  emptySpec = mempty
2✔
1145

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

1148
  conformsTo (SumLeft a) (SumSpec _ sa _) = conformsToSpec a sa
2✔
1149
  conformsTo (SumRight b) (SumSpec _ _ sb) = conformsToSpec b sb
2✔
1150

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

1166
  shrinkWithTypeSpec (SumSpec _ sa _) (SumLeft a) = SumLeft <$> shrinkWithSpec sa a
2✔
1167
  shrinkWithTypeSpec (SumSpec _ _ sb) (SumRight b) = SumRight <$> shrinkWithSpec sb b
2✔
1168

1169
  fixupWithTypeSpec (SumSpec _ sa _) (SumLeft a) = SumLeft <$> fixupWithSpec sa a
1✔
1170
  fixupWithTypeSpec (SumSpec _ _ sb) (SumRight b) = SumRight <$> fixupWithSpec sb b
2✔
1171

1172
  toPreds ct (SumSpec h sa sb) =
2✔
1173
    Case
2✔
1174
      ct
2✔
1175
      ( (Weighted (fst <$> h) $ bind $ \a -> satisfies a sa)
2✔
1176
          :> (Weighted (snd <$> h) $ bind $ \b -> satisfies b sb)
2✔
1177
          :> Nil
2✔
1178
      )
1179

1180
  cardinalTypeSpec (SumSpec _ leftspec rightspec) = addSpecInt (cardinality leftspec) (cardinality rightspec)
2✔
1181

1182
  typeSpecHasError (SumSpec _ x y) =
2✔
1183
    case (isErrorLike x, isErrorLike y) of
2✔
1184
      (True, True) -> Just $ (errorLikeMessage x <> errorLikeMessage y)
×
1185
      _ -> Nothing
2✔
1186

1187
  alternateShow (SumSpec h left right@(TypeSpec r [])) =
×
1188
    case alternateShow @b r of
×
1189
      (BinaryShow "SumSpec" ps) -> BinaryShow "SumSpec" ("|" <+> sumWeightL h <+> viaShow left : ps)
×
1190
      (BinaryShow "Cartesian" ps) ->
1191
        BinaryShow "SumSpec" ("|" <+> sumWeightL h <+> viaShow left : [parens ("Cartesian" /> vsep ps)])
×
1192
      _ ->
1193
        BinaryShow "SumSpec" ["|" <+> sumWeightL h <+> viaShow left, "|" <+> sumWeightR h <+> viaShow right]
×
1194
  alternateShow (SumSpec h left right) =
1195
    BinaryShow "SumSpec" ["|" <+> sumWeightL h <+> viaShow left, "|" <+> sumWeightR h <+> viaShow right]
×
1196

1197
-- ======================================
1198
-- Here are the Logic Instances for Sum
1199

1200
-- | Function symbols for `injLeft_` and `injRight_`
1201
data SumW dom rng where
1202
  InjLeftW :: SumW '[a] (Sum a b)
1203
  InjRightW :: SumW '[b] (Sum a b)
1204

1205
instance Show (SumW dom rng) where
×
1206
  show InjLeftW = "injLeft_"
2✔
1207
  show InjRightW = "injRight_"
2✔
1208

1209
deriving instance (Eq (SumW dom rng))
×
1210

1211
instance Syntax SumW
×
1212

1213
instance Semantics SumW where
1214
  semantics InjLeftW = SumLeft
2✔
1215
  semantics InjRightW = SumRight
2✔
1216

1217
instance Logic SumW where
1✔
1218
  propagateTypeSpec InjLeftW (Unary HOLE) (SumSpec _ sl _) cant = sl <> foldMap notEqualSpec [a | SumLeft a <- cant]
2✔
1219
  propagateTypeSpec InjRightW (Unary HOLE) (SumSpec _ _ sr) cant = sr <> foldMap notEqualSpec [a | SumRight a <- cant]
2✔
1220

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

1236
  mapTypeSpec InjLeftW ts = typeSpec $ SumSpec Nothing (typeSpec ts) (ErrorSpec (pure "mapTypeSpec InjLeftW"))
1✔
1237
  mapTypeSpec InjRightW ts = typeSpec $ SumSpec Nothing (ErrorSpec (pure "mapTypeSpec InjRightW")) (typeSpec ts)
1✔
1238

1239
-- | Constructor for `Sum`
1240
injLeft_ :: (HasSpec a, HasSpec b, KnownNat (CountCases b)) => Term a -> Term (Sum a b)
1241
injLeft_ = appTerm InjLeftW
2✔
1242

1243
-- | Constructor for `Sum`
1244
injRight_ :: (HasSpec a, HasSpec b, KnownNat (CountCases b)) => Term b -> Term (Sum a b)
1245
injRight_ = appTerm InjRightW
2✔
1246

1247
-- | Pattern for building custom rewrite rules
1248
pattern InjRight ::
1249
  forall c.
1250
  () =>
1251
  forall a b.
1252
  ( c ~ Sum a b
1253
  , AppRequires SumW '[b] c
1254
  ) =>
1255
  Term b ->
1256
  Term c
1257
pattern InjRight x <- (App (getWitness -> Just InjRightW) (x :> Nil))
2✔
1258

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

1271
sumWeightL, sumWeightR :: Maybe (Int, Int) -> Doc a
1272
sumWeightL Nothing = "1"
×
1273
sumWeightL (Just (x, _)) = fromString (show x)
×
1274
sumWeightR Nothing = "1"
×
1275
sumWeightR (Just (_, x)) = fromString (show x)
×
1276

1277
-- | Operations on Bool
1278
data BoolW (dom :: [Type]) (rng :: Type) where
1279
  NotW :: BoolW '[Bool] Bool
1280
  OrW :: BoolW '[Bool, Bool] Bool
1281

1282
deriving instance Eq (BoolW dom rng)
×
1283

1284
instance Show (BoolW dom rng) where
×
1285
  show NotW = "not_"
2✔
1286
  show OrW = "or_"
2✔
1287

1288
boolSem :: BoolW dom rng -> FunTy dom rng
1289
boolSem NotW = not
2✔
1290
boolSem OrW = (||)
2✔
1291

1292
instance Semantics BoolW where
1293
  semantics = boolSem
2✔
1294

1295
instance Syntax BoolW
×
1296

1297
-- ======= Logic instance BoolW
1298

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

1317
  mapTypeSpec NotW () = typeSpec ()
×
1318

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

1330
-- | Disjunction on @`Term` `Bool`@, note that this will not cause backtracking during generation
1331
or_ :: Term Bool -> Term Bool -> Term Bool
1332
or_ = appTerm OrW
2✔
1333

1334
-- | Negation of booleans
1335
not_ :: Term Bool -> Term Bool
1336
not_ = appTerm NotW
2✔
1337

1338
-- ===============================================================================
1339
-- Syntax for Solving : stages and plans
1340

1341
data SolverStage where
1342
  SolverStage ::
1343
    HasSpec a =>
1344
    { stageVar :: Var a
×
1345
    , stagePreds :: [Pred]
×
1346
    , stageSpec :: Specification a
×
1347
    , relevantVariables :: Set Name
×
1348
    } ->
1349
    SolverStage
1350

1351
docVar :: Typeable a => Var a -> Doc h
1352
docVar (v :: Var a) = fromString (show v ++ " :: " ++ showType @a)
2✔
1353

1354
instance Pretty SolverStage where
×
1355
  pretty SolverStage {..} =
2✔
1356
    docVar stageVar
2✔
1357
      <+> "<-"
2✔
1358
        /> vsep'
2✔
1359
          ( [pretty stageSpec | not $ isTrueSpec stageSpec]
2✔
1360
              ++ ["---" | not $ null stagePreds, not $ isTrueSpec stageSpec]
2✔
1361
              ++ map pretty stagePreds
2✔
1362
              ++ ["_" | null stagePreds && isTrueSpec stageSpec]
2✔
1363
          )
1364

1365
newtype SolverPlan = SolverPlan { solverPlan :: [SolverStage] }
×
1366

1367
instance Pretty SolverPlan where
×
1368
  pretty SolverPlan {..} =
2✔
1369
    "SolverPlan" /> prettyLinear solverPlan
2✔
1370

1371
isTrueSpec :: Specification a -> Bool
1372
isTrueSpec TrueSpec = True
2✔
1373
isTrueSpec _ = False
2✔
1374

1375
prettyLinear :: [SolverStage] -> Doc ann
1376
prettyLinear = vsep' . map pretty
2✔
1377

1378
fromGESpec :: HasCallStack => GE (Specification a) -> Specification a
1379
fromGESpec ge = case ge of
2✔
1380
  Result s -> s
2✔
1381
  GenError xs -> ErrorSpec (catMessageList xs)
×
1382
  FatalError es -> error $ catMessages es
2✔
1383

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

1405
-- TODO generalizeme!
1406
forwardPropagateSpec :: HasSpec a => Specification a -> Ctx a b -> Specification b
1407
forwardPropagateSpec s CtxHOLE = s
2✔
1408
forwardPropagateSpec s (CtxApp f (c :? Nil))
1409
  | Evidence <- ctxHasSpec c = mapSpec f (forwardPropagateSpec s c)
2✔
1410
forwardPropagateSpec _ _ = TrueSpec
2✔
1411

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