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

input-output-hk / constrained-generators / 314

17 Oct 2025 06:03AM UTC coverage: 74.661% (+0.2%) from 74.422%
314

push

github

web-flow
Coveralls and stack support (#44)

* Implement coveralls support

12 of 20 new or added lines in 2 files covered. (60.0%)

53 existing lines in 2 files now uncovered.

3745 of 5016 relevant lines covered (74.66%)

1.41 hits per line

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

75.04
/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
  simplifySpec,
29

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

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

42
  -- * Other syntax
43
  whenTrue,
44

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

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

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

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

148
-- ----------------------- Shrinking -------------------------------
149

150
-- | Shrink a value while preserving adherence to a `Specification`
151
shrinkWithSpec :: forall a. HasSpec a => Specification a -> a -> [a]
152
-- TODO: possibly allow for ignoring the `conformsToSpec` check in the `TypeSpec`
153
-- case when you know what you're doing
154
shrinkWithSpec (simplifySpec -> spec) a = filter (`conformsToSpec` spec) $ case spec of
×
155
  ExplainSpec _ s -> shrinkWithSpec s a
×
156
  -- TODO: filter on can't if we have a known to be sound shrinker
157
  TypeSpec s _ -> shrinkWithTypeSpec s a
×
158
  -- TODO: The better way of doing this is to compute the dependency graph,
159
  -- shrink one variable at a time, and fixup the rest of the variables
160
  SuspendedSpec {} -> shr a
×
161
  MemberSpec {} -> shr a
×
162
  TrueSpec -> shr a
×
163
  ErrorSpec {} -> []
×
164
  where
165
    shr = shrinkWithTypeSpec (emptySpec @a)
×
166

167
-- Debugging --------------------------------------------------------------
168

169
-- | A version of `genFromSpecT` that runs in the IO monad. Good for debugging.
170
debugSpec :: forall a. HasSpec a => Specification a -> IO ()
171
debugSpec spec = do
×
172
  ans <- generate $ genFromGenT $ inspect (genFromSpecT spec)
×
173
  let f x = putStrLn (unlines (NE.toList x))
×
174
      ok x =
×
175
        if conformsToSpec x spec
×
176
          then putStrLn "True"
×
177
          else putStrLn "False, perhaps there is an unsafeExists in the spec?"
×
178
  case ans of
×
179
    FatalError xs -> mapM_ f xs
×
180
    GenError xs -> mapM_ f xs
×
181
    Result x -> print spec >> print x >> ok x
×
182

183
-- | Pretty-print the plan for a `Specifcation` in the terminal for debugging
184
printPlan :: HasSpec a => Specification a -> IO ()
185
printPlan = print . prettyPlan
×
186

187
-- | Plan pretty-printer for debugging
188
prettyPlan :: HasSpec a => Specification a -> Doc ann
189
prettyPlan (simplifySpec -> spec)
×
190
  | SuspendedSpec _ p <- spec
×
191
  , Result plan <- prepareLinearization p =
×
192
      vsep'
×
193
        [ "Simplified spec:" /> pretty spec
×
194
        , pretty plan
×
195
        ]
196
  | otherwise = "Simplfied spec:" /> pretty spec
×
197

198
-- ---------------------- Building a plan -----------------------------------
199

200
substStage :: HasSpec a => Set Name -> Var a -> a -> SolverStage -> SolverStage
201
substStage rel' x val (SolverStage y ps spec relevant) =
2✔
202
  normalizeSolverStage $ SolverStage y (substPred env <$> ps) spec relevant'
2✔
203
  where env = Env.singleton x val
2✔
204
        relevant' | Name x `appearsIn` ps = rel' <> relevant
2✔
205
                  | otherwise = relevant
1✔
206

207
normalizeSolverStage :: SolverStage -> SolverStage
208
normalizeSolverStage (SolverStage x ps spec relevant) = SolverStage x ps'' (spec <> spec') relevant
2✔
209
  where
210
    (ps', ps'') = partition ((1 ==) . Set.size . freeVarSet) ps
2✔
211
    spec' = fromGESpec $ computeSpec x (And ps')
2✔
212

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

219
-- | Linearize a predicate, turning it into a list of variables to solve and
220
-- their defining constraints such that each variable can be solved independently.
221
prepareLinearization :: Pred -> GE SolverPlan
222
prepareLinearization p = do
2✔
223
  let preds = concatMap saturatePred $ flattenPred p
2✔
224
      hints = computeHints preds
2✔
225
      graph = transitiveClosure $ hints <> respecting hints (foldMap computeDependencies preds)
2✔
226
  plan <-
227
    explainNE
2✔
228
      ( NE.fromList
×
229
          [ "Linearizing"
×
230
          , show $
×
231
              "  preds: "
×
232
                <> pretty (take 3 preds)
×
233
                <> (if length preds > 3 then fromString (" ... " ++ show (length preds - 3) ++ " more.") else "")
×
234
          , show $ "  graph: " <> pretty graph
×
235
          ]
236
      )
237
      $ linearize preds graph
2✔
238
  pure $ backPropagation mempty $ SolverPlan plan
2✔
239

240
-- | Flatten nested `Let`, `Exists`, and `And` in a `Pred fn`. `Let` and
241
-- `Exists` bound variables become free in the result.
242
flattenPred :: Pred -> [Pred]
243
flattenPred pIn = go (freeVarNames pIn) [pIn]
2✔
244
  where
245
    go _ [] = []
2✔
246
    go fvs (p : ps) = case p of
2✔
247
      And ps' -> go fvs (ps' ++ ps)
2✔
248
      -- NOTE: the order of the arguments to `==.` here are important.
249
      -- The whole point of `Let` is that it allows us to solve all of `t`
250
      -- before we solve the variables in `t`.
251
      Let t b -> goBinder fvs b ps (\x -> (assert (t ==. (V x)) :))
2✔
252
      Exists _ b -> goBinder fvs b ps (const id)
2✔
253
      When b pp -> map (When b) (go fvs [pp]) ++ go fvs ps
2✔
254
      Explain es pp -> map (explanation es) (go fvs [pp]) ++ go fvs ps
2✔
255
      _ -> p : go fvs ps
2✔
256

257
    goBinder ::
258
      Set Int ->
259
      Binder a ->
260
      [Pred] ->
261
      (HasSpec a => Var a -> [Pred] -> [Pred]) ->
262
      [Pred]
263
    goBinder fvs (x :-> p) ps k = k x' $ go (Set.insert (nameOf x') fvs) (p' : ps)
2✔
264
      where
265
        (x', p') = freshen x p fvs
2✔
266

267
-- Consider: A + B = C + D
268
-- We want to fail if A and B are independent.
269
-- Consider: A + B = A + C, A <- B
270
-- Here we want to consider this constraint defining for A
271
linearize ::
272
  MonadGenError m => [Pred] -> DependGraph -> m [SolverStage]
273
linearize preds graph = do
2✔
274
  sorted <- case topsort graph of
2✔
275
    Left cycle ->
276
      fatalError
×
277
        ( show $
×
278
            "linearize: Dependency cycle in graph:"
×
279
              /> vsep'
×
280
                [ "cycle:" /> pretty cycle
×
281
                , "graph:" /> pretty graph
×
282
                ]
283
        )
284
    Right sorted -> pure sorted
2✔
285
  go sorted [(freeVarSet ps, ps) | ps <- filter isRelevantPred preds]
2✔
286
  where
287
    isRelevantPred TruePred = False
1✔
288
    isRelevantPred DependsOn {} = False
2✔
289
    isRelevantPred (Assert (Lit True)) = False
2✔
290
    isRelevantPred _ = True
2✔
291

292
    go [] [] = pure []
2✔
293
    go [] ps
294
      | null $ foldMap fst ps =
1✔
295
          case checkPredsE (pure "Linearizing fails") mempty (map snd ps) of
1✔
296
            Nothing -> pure []
2✔
297
            Just msgs -> genErrorNE msgs
1✔
298
      | otherwise =
×
299
          fatalErrorNE $
×
300
            NE.fromList
×
301
              [ "Dependency error in `linearize`: "
×
302
              , show $ indent 2 $ "graph: " /> pretty graph
×
303
              , show $
×
304
                  indent 2 $
×
305
                    "the following left-over constraints are not defining constraints for a unique variable:"
×
306
                      /> vsep' (map (pretty . snd) ps)
×
307
              ]
308
    go (n@(Name x) : ns) ps = do
2✔
309
      let (nps, ops) = partition (isLastVariable n . fst) ps
2✔
310
      (normalizeSolverStage (SolverStage x (map snd nps) mempty mempty):) <$> go ns ops
2✔
311

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

314
------------------------------------------------------------------------
315
-- Simplification of Specifications
316
------------------------------------------------------------------------
317

318
-- | Spec simplification, use with care and don't modify the spec after using this!
319
simplifySpec :: HasSpec a => Specification a -> Specification a
320
simplifySpec spec = case applyNameHints spec of
2✔
321
  SuspendedSpec x p ->
322
    let optP = optimisePred p
2✔
323
     in fromGESpec $
2✔
324
          explain
2✔
325
            ("\nWhile calling simplifySpec on var " ++ show x ++ "\noptP=\n" ++ show optP ++ "\n")
2✔
326
            (computeSpecSimplified x optP)
2✔
327
  MemberSpec xs -> MemberSpec xs
2✔
328
  ErrorSpec es -> ErrorSpec es
1✔
329
  TypeSpec ts cant -> TypeSpec ts cant
2✔
330
  TrueSpec -> TrueSpec
2✔
331
  ExplainSpec es s -> explainSpec es (simplifySpec s)
2✔
332

333
-- ------- Stages of simplifying -------------------------------
334

335
-- TODO: it might be necessary to run aggressiveInlining again after the let floating etc.
336
optimisePred :: Pred -> Pred
337
optimisePred p =
2✔
338
  simplifyPred
2✔
339
    . letSubexpressionElimination
2✔
340
    . letFloating
2✔
341
    . aggressiveInlining
2✔
342
    . simplifyPred
2✔
343
    $ p
2✔
344

345
aggressiveInlining :: Pred -> Pred
346
aggressiveInlining pred
2✔
347
  | inlined = aggressiveInlining pInlined
2✔
348
  | otherwise = pred
1✔
349
  where
350
    (pInlined, Any inlined) = runWriter $ go (freeVars pred) [] pred
2✔
351

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

354
    underBinderSub :: HasSpec a => Subst -> Var a -> Subst
355
    underBinderSub sub x =
2✔
356
      [ x' := t
2✔
357
      | x' := t <- sub
2✔
358
      , isNothing $ eqVar x x'
1✔
359
      ]
360

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

366
    -- Check that the name `n` is only ever used as the only variable
367
    -- in the expressions where it appears. This ensures that it doesn't
368
    -- interact with anything.
369
    onlyUsedUniquely n p = case p of
2✔
370
      Assert t
371
        | n `appearsIn` t -> Set.size (freeVarSet t) == 1
2✔
372
        | otherwise -> True
1✔
373
      And ps -> all (onlyUsedUniquely n) ps
2✔
374
      -- TODO: we can (and should) probably add a bunch of cases to this.
375
      _ -> False
2✔
376

377
    go fvs sub pred2 = case pred2 of
2✔
378
      ElemPred bool t xs
379
        | not (isLit t)
1✔
380
        , Lit a <- substituteAndSimplifyTerm sub t -> do
1✔
381
            tell $ Any True
×
382
            pure $ ElemPred bool (Lit a) xs
×
383
        | otherwise -> pure $ ElemPred bool t xs
1✔
384
      Subst x t p -> go fvs sub (substitutePred x t p)
×
385
      Reifies t' t f
386
        | not (isLit t)
1✔
387
        , Lit a <- substituteAndSimplifyTerm sub t -> do
1✔
388
            tell $ Any True
×
389
            pure $ Reifies t' (Lit a) f
×
390
        | otherwise -> pure $ Reifies t' t f
1✔
391
      ForAll set b
392
        | not (isLit set)
1✔
393
        , Lit a <- substituteAndSimplifyTerm sub set -> do
2✔
394
            tell $ Any True
2✔
395
            pure $ foldMap (`unBind` b) (forAllToList a)
2✔
396
        | otherwise -> ForAll set <$> goBinder fvs sub b
1✔
397
      Case t bs
398
        | not (isLit t)
1✔
399
        , Lit a <- substituteAndSimplifyTerm sub t -> do
1✔
400
            tell $ Any True
×
401
            pure $ runCaseOn a (mapList thing bs) $ \x v p -> substPred (Env.singleton x v) p
×
402
        | (Weighted w (x :-> p) :> Nil) <- bs -> do
1✔
403
            let t' = substituteAndSimplifyTerm sub t
×
404
            p' <- go (underBinder fvs x p) (x := t' : sub) p
×
405
            pure $ Case t (Weighted w (x :-> p') :> Nil)
×
406
        | otherwise -> Case t <$> mapMList (traverseWeighted $ goBinder fvs sub) bs
1✔
407
      When b tp
408
        | not (isLit b)
1✔
409
        , Lit a <- substituteAndSimplifyTerm sub b -> do
1✔
410
            tell $ Any True
×
411
            pure $ if a then tp else TruePred
×
412
        | otherwise -> whenTrue b <$> go fvs sub tp
1✔
413
      Let t (x :-> p)
414
        | all (\n -> count n fvs <= 1) (freeVarSet t) -> do
2✔
415
            tell $ Any True
2✔
416
            pure $ substitutePred x t p
2✔
417
        | onlyUsedUniquely (Name x) p -> do
2✔
418
            tell $ Any True
2✔
419
            pure $ substitutePred x t p
2✔
420
        | not $ Name x `appearsIn` p -> do
2✔
421
            tell $ Any True
2✔
422
            pure p
2✔
423
        | not (isLit t)
1✔
424
        , Lit a <- substituteAndSimplifyTerm sub t -> do
1✔
425
            tell $ Any True
×
426
            pure $ unBind a (x :-> p)
×
427
        | otherwise -> Let t . (x :->) <$> go (underBinder fvs x p) (x := t : sub) p
1✔
428
      Exists k b -> Exists k <$> goBinder fvs sub b
2✔
429
      And ps -> fold <$> mapM (go fvs sub) ps
2✔
430
      Assert t
431
        | not (isLit t)
2✔
432
        , Lit b <- substituteAndSimplifyTerm sub t -> do
1✔
433
            tell $ Any True
×
434
            pure $ toPred b
×
435
        | otherwise -> pure pred2
1✔
436
      -- If the term turns into a literal, there is no more generation to do here
437
      -- so we can ignore the `GenHint`
438
      GenHint _ t
439
        | not (isLit t)
1✔
440
        , Lit {} <- substituteAndSimplifyTerm sub t -> do
1✔
441
            tell $ Any True
×
442
            pure TruePred
×
443
        | otherwise -> pure pred2
1✔
444
      DependsOn t t'
445
        | not (isLit t)
1✔
446
        , Lit {} <- substituteAndSimplifyTerm sub t -> do
1✔
447
            tell $ Any True
×
448
            pure $ TruePred
×
449
        | not (isLit t')
1✔
450
        , Lit {} <- substituteAndSimplifyTerm sub t' -> do
1✔
451
            tell $ Any True
×
452
            pure $ TruePred
×
453
        | otherwise -> pure pred2
1✔
454
      TruePred -> pure pred2
2✔
455
      FalsePred {} -> pure pred2
1✔
456
      Monitor {} -> pure pred2
×
457
      Explain es p -> Explain es <$> go fvs sub p
1✔
458

459
-- | Apply a substitution and simplify the resulting term if the
460
-- substitution changed the term.
461
substituteAndSimplifyTerm :: Subst -> Term a -> Term a
462
substituteAndSimplifyTerm sub t =
2✔
463
  case runWriter $ substituteTerm' sub t of
2✔
464
    (t', Any b)
465
      | b -> simplifyTerm t'
2✔
466
      | otherwise -> t'
1✔
467

468
-- | Simplify a Term, if the Term is an 'App', apply the rewrite rules
469
--   chosen by the (Logic sym t bs a) instance attached
470
--   to the function witness 'f'
471
simplifyTerm :: forall a. Term a -> Term a
472
simplifyTerm = \case
2✔
473
  V v -> V v
2✔
474
  Lit l -> Lit l
2✔
475
  App (f :: t bs a) (mapList simplifyTerm -> ts)
476
    | Just vs <- fromLits ts -> Lit $ uncurryList_ unValue (semantics f) vs
2✔
477
    | Just t <- rewriteRules f ts (Evidence @(AppRequires t bs a)) -> simplifyTerm t
2✔
478
    | otherwise -> App f ts
1✔
479

480
simplifyPred :: Pred -> Pred
481
simplifyPred = \case
2✔
482
  -- If the term simplifies away to a literal, that means there is no
483
  -- more generation to do so we can get rid of `GenHint`
484
  GenHint h t -> case simplifyTerm t of
2✔
485
    Lit {} -> TruePred
×
486
    t' -> GenHint h t'
2✔
487
  p@(ElemPred bool t xs) -> case simplifyTerm t of
2✔
488
    Lit x -> case (elem x xs, bool) of
2✔
489
      (True, True) -> TruePred
2✔
490
      (True, False) -> FalsePred ("notElemPred reduces to True" :| [show p])
1✔
491
      (False, True) -> FalsePred ("elemPred reduces to False" :| [show p])
1✔
492
      (False, False) -> TruePred
2✔
493
    t' -> ElemPred bool t' xs
2✔
494
  Subst x t p -> simplifyPred $ substitutePred x t p
2✔
495
  Assert t -> Assert $ simplifyTerm t
2✔
496
  Reifies t' t f -> case simplifyTerm t of
2✔
497
    Lit a ->
498
      -- Assert $ simplifyTerm t' ==. Lit (f a)
499
      ElemPred True (simplifyTerm t') (pure (f a))
2✔
500
    t'' -> Reifies (simplifyTerm t') t'' f
2✔
501
  ForAll (ts :: Term t) (b :: Binder a) -> case simplifyTerm ts of
2✔
502
    Lit as -> foldMap (`unBind` b) (forAllToList as)
2✔
503
    -- (App (extractW (UnionW @t) -> Just Refl) xs) -> error "MADE IT"
504
    {- Has to wait until we have HasSpec(Set a) instance
505
    UnionPat (xs :: Term (Set a)) ys ->
506
       let b' = simplifyBinder b
507
       in mkForAll xs b' <> mkForAll ys b' -}
508
    set' -> case simplifyBinder b of
2✔
509
      _ :-> TruePred -> TruePred
2✔
510
      b' -> ForAll set' b'
2✔
UNCOV
511
  DependsOn _ Lit {} -> TruePred
×
512
  DependsOn Lit {} _ -> TruePred
×
513
  DependsOn x y -> DependsOn x y
2✔
514
  -- Here is where we need the SumSpec instance
515
  Case t bs -> mkCase (simplifyTerm t) (mapList (mapWeighted simplifyBinder) bs)
2✔
516
  When b p -> whenTrue (simplifyTerm b) (simplifyPred p)
2✔
517
  TruePred -> TruePred
2✔
518
  FalsePred es -> FalsePred es
1✔
519
  And ps -> fold (simplifyPreds ps)
2✔
520
  Let t b -> case simplifyTerm t of
2✔
521
    t'@App {} -> Let t' (simplifyBinder b)
2✔
522
    -- Variable or literal
523
    t' | x :-> p <- b -> simplifyPred $ substitutePred x t' p
2✔
524
  Exists k b -> case simplifyBinder b of
2✔
525
    _ :-> TruePred -> TruePred
2✔
526
    -- This is to get rid of exisentials like:
527
    -- `constrained $ \ x -> exists $ \ y -> [x ==. y, y + 2 <. 10]`
528
    x :-> p | Just t <- pinnedBy x p -> simplifyPred $ substitutePred x t p
2✔
529
    b' -> Exists k b'
2✔
530
  Monitor {} -> TruePred
2✔
531
  -- TODO: This is a bit questionable. On the one hand we could get rid of `Explain` here
532
  -- and just return `simplifyPred p` but doing so risks missing explanations when things
533
  -- do go wrong.
534
  Explain es p -> explanation es $ simplifyPred p
2✔
535

536
simplifyPreds :: [Pred] -> [Pred]
537
simplifyPreds = go [] . map simplifyPred
2✔
538
  where
539
    go acc [] = reverse acc
2✔
540
    go _ (FalsePred err : _) = [FalsePred err]
1✔
541
    go acc (TruePred : ps) = go acc ps
2✔
542
    go acc (p : ps) = go (p : acc) ps
2✔
543

544
simplifyBinder :: Binder a -> Binder a
545
simplifyBinder (x :-> p) = x :-> simplifyPred p
2✔
546

547
-- TODO: this can probably be cleaned up and generalized along with generalizing
548
-- to make sure we float lets in some missing cases.
549
letFloating :: Pred -> Pred
550
letFloating = fold . go []
2✔
551
  where
552
    goBlock ctx ps = goBlock' (freeVarNames ctx <> freeVarNames ps) ctx ps
2✔
553

554
    goBlock' :: Set Int -> [Pred] -> [Pred] -> [Pred]
555
    goBlock' _ ctx [] = ctx
2✔
556
    goBlock' fvs ctx (Let t (x :-> p) : ps) =
557
      -- We can do `goBlock'` here because we've already done let floating
558
      -- on the inner `p`
559
      [Let t (x' :-> fold (goBlock' (Set.insert (nameOf x') fvs) ctx (p' : ps)))]
2✔
560
      where
561
        (x', p') = freshen x p fvs
2✔
562
    goBlock' fvs ctx (And ps : ps') = goBlock' fvs ctx (ps ++ ps')
2✔
563
    goBlock' fvs ctx (p : ps) = goBlock' fvs (p : ctx) ps
2✔
564

565
    goExists ::
566
      HasSpec a =>
567
      [Pred] ->
568
      (Binder a -> Pred) ->
569
      Var a ->
570
      Pred ->
571
      [Pred]
572
    goExists ctx ex x (Let t (y :-> p))
2✔
573
      | not $ Name x `appearsIn` t =
1✔
574
          let (y', p') = freshen y p (Set.insert (nameOf x) $ freeVarNames p <> freeVarNames t)
2✔
575
           in go ctx (Let t (y' :-> ex (x :-> p')))
2✔
576
    goExists ctx ex x p = ex (x :-> p) : ctx
2✔
577

578
    pushExplain es (Let t (x :-> p)) = Let t (x :-> pushExplain es p)
1✔
579
    pushExplain es (And ps) = And (pushExplain es <$> ps)
2✔
580
    pushExplain es (Exists k (x :-> p)) =
581
      Exists (explainSemantics k) (x :-> pushExplain es p)
2✔
582
      where
583
        -- TODO: Unfortunately this is necessary on ghc 8.10.7
584
        explainSemantics ::
585
          forall a.
586
          ((forall b. Term b -> b) -> GE a) ->
587
          (forall b. Term b -> b) ->
588
          GE a
589
        explainSemantics k2 env = explainNE es $ k2 env
1✔
590
    -- TODO: possibly one wants to have a `Term` level explanation in case
591
    -- the `b` propagates to ErrorSpec for some reason?
592
    pushExplain es (When b p) = When b (pushExplain es p)
1✔
593
    pushExplain es p = explanation es p
2✔
594

595
    go ctx = \case
2✔
596
      ElemPred bool t xs -> ElemPred bool t xs : ctx
2✔
597
      And ps0 -> goBlock ctx (map letFloating ps0)
2✔
598
      Let t (x :-> p) -> goBlock ctx [Let t (x :-> letFloating p)]
2✔
599
      Exists k (x :-> p) -> goExists ctx (Exists k) x (letFloating p)
2✔
600
      Subst x t p -> go ctx (substitutePred x t p)
×
601
      Reifies t' t f -> Reifies t' t f : ctx
2✔
602
      Explain es p -> pushExplain es p : ctx
2✔
603
      -- TODO: float let through forall if possible
604
      ForAll t (x :-> p) -> ForAll t (x :-> letFloating p) : ctx
2✔
605
      -- TODO: float let through the cases if possible
606
      Case t bs -> Case t (mapList (mapWeighted (\(x :-> p) -> x :-> letFloating p)) bs) : ctx
2✔
607
      -- TODO: float let through if possible
608
      When b p -> When b (letFloating p) : ctx
2✔
609
      -- Boring cases
610
      Assert t -> Assert t : ctx
2✔
611
      GenHint h t -> GenHint h t : ctx
2✔
612
      DependsOn t t' -> DependsOn t t' : ctx
2✔
613
      TruePred -> TruePred : ctx
2✔
614
      FalsePred es -> FalsePred es : ctx
1✔
615
      Monitor m -> Monitor m : ctx
×
616

617
-- Common subexpression elimination but only on terms that are already let-bound.
618
letSubexpressionElimination :: Pred -> Pred
619
letSubexpressionElimination = go []
2✔
620
  where
621
    adjustSub :: HasSpec a => Var a -> Subst -> Subst
622
    adjustSub x sub =
2✔
623
      [ x' := t
2✔
624
      | x' := t <- sub
2✔
625
      , isNothing $ eqVar x x'
2✔
626
      , -- TODO: possibly freshen the binder where
627
      -- `x` appears instead?
628
      not $ Name x `appearsIn` t
1✔
629
      ]
630

631
    goBinder :: Subst -> Binder a -> Binder a
632
    goBinder sub (x :-> p) = x :-> go (adjustSub x sub) p
2✔
633

634
    go sub = \case
2✔
635
      ElemPred bool t xs -> ElemPred bool (backwardsSubstitution sub t) xs
2✔
636
      GenHint h t -> GenHint h (backwardsSubstitution sub t)
2✔
637
      And ps -> And (go sub <$> ps)
2✔
638
      Let t (x :-> p) -> Let t' (x :-> go (x := t' : sub') p)
2✔
639
        where
640
          t' = backwardsSubstitution sub t
2✔
641
          sub' = adjustSub x sub
2✔
642
      Exists k b -> Exists k (goBinder sub b)
2✔
643
      Subst x t p -> go sub (substitutePred x t p)
×
644
      Assert t -> Assert (backwardsSubstitution sub t)
2✔
645
      Reifies t' t f -> Reifies (backwardsSubstitution sub t') (backwardsSubstitution sub t) f
2✔
646
      -- NOTE: this is a tricky case. One possible thing to do here is to keep the old `DependsOn t t'`
647
      -- and have the new DependsOn if `backwardsSubstitution` changed something. With this semantics you
648
      -- risk running into unintuitive behaviour if you have something like:
649
      -- ```
650
      -- let x = y + z in
651
      --  {y + z `dependsOn` w
652
      --   assert $ w <. y + 2
653
      --   ...}
654
      -- ```
655
      -- This will be rewritten as:
656
      -- ```
657
      -- let x = y + z in
658
      --  {z `dependsOn` w
659
      --   assert $ w <. y + 2
660
      --   ...}
661
      -- ```
662
      -- which changes the dependency order of `w` and `y`. However, fixing
663
      -- this behaviour in turn makes it more difficult to detect when
664
      -- variables are no longer used after being substituted away - which
665
      -- blocks some other optimizations. As we strongly encourage users not to
666
      -- use `letBind` in their own code most users will never encounter this issue
667
      -- so the tradeoff is probably worth it.
668
      DependsOn t t' -> DependsOn (backwardsSubstitution sub t) (backwardsSubstitution sub t')
2✔
669
      ForAll t b -> ForAll (backwardsSubstitution sub t) (goBinder sub b)
2✔
670
      Case t bs -> Case (backwardsSubstitution sub t) (mapList (mapWeighted $ goBinder sub) bs)
2✔
671
      When b p -> When (backwardsSubstitution sub b) (go sub p)
2✔
672
      TruePred -> TruePred
2✔
673
      FalsePred es -> FalsePred es
1✔
674
      Monitor m -> Monitor m
×
675
      Explain es p -> Explain es $ go sub p
2✔
676

677
-- Turning Preds into Specifications. Here is where Propagation occurs ----
678

679
-- | Precondition: the `Pred` defines the `Var a`
680
-- Runs in `GE` in order for us to have detailed context on failure.
681
computeSpecSimplified ::
682
  forall a. (HasSpec a, HasCallStack) => Var a -> Pred -> GE (Specification a)
683
computeSpecSimplified x pred3 = localGESpec $ case simplifyPred pred3 of
2✔
684
  ElemPred True t xs -> propagateSpec (MemberSpec xs) <$> toCtx x t
2✔
685
  ElemPred False (t :: Term b) xs -> propagateSpec (TypeSpec @b (emptySpec @b) (NE.toList xs)) <$> toCtx x t
2✔
686
  Monitor {} -> pure mempty
×
687
  GenHint h t -> propagateSpec (giveHint h) <$> toCtx x t
2✔
688
  Subst x' t p' -> computeSpec x (substitutePred x' t p') -- NOTE: this is impossible as it should have gone away already
×
689
  TruePred -> pure mempty
2✔
690
  FalsePred es -> genErrorNE es
1✔
691
  And ps -> do
2✔
692
    spec <- fold <$> mapM (computeSpecSimplified x) ps
2✔
693
    case spec of
2✔
694
      ExplainSpec es (SuspendedSpec y ps') -> pure $ explainSpec es (SuspendedSpec y $ simplifyPred ps')
×
695
      SuspendedSpec y ps' -> pure $ SuspendedSpec y $ simplifyPred ps'
2✔
696
      s -> pure s
2✔
697
  Let t b -> pure $ SuspendedSpec x (Let t b)
2✔
698
  Exists k b -> pure $ SuspendedSpec x (Exists k b)
2✔
699
  Assert (Lit True) -> pure mempty
2✔
700
  Assert (Lit False) -> genError (show pred3)
1✔
701
  Assert t -> propagateSpec (equalSpec True) <$> toCtx x t
2✔
702
  ForAll (Lit s) b -> fold <$> mapM (\val -> computeSpec x $ unBind val b) (forAllToList s)
×
703
  ForAll t b -> do
2✔
704
    bSpec <- computeSpecBinderSimplified b
2✔
705
    propagateSpec (fromForAllSpec bSpec) <$> toCtx x t
2✔
706
  Case (Lit val) bs -> runCaseOn val (mapList thing bs) $ \va vaVal psa -> computeSpec x (substPred (Env.singleton va vaVal) psa)
×
707
  Case t branches -> do
2✔
708
    branchSpecs <- mapMList (traverseWeighted computeSpecBinderSimplified) branches
2✔
709
    propagateSpec (caseSpec (Just (showType @a)) branchSpecs) <$> toCtx x t
1✔
710
  When (Lit b) tp -> if b then computeSpecSimplified x tp else pure TrueSpec
×
711
  -- This shouldn't happen a lot of the time because when the body is trivial we mostly get rid of the `When` entirely
712
  When {} -> pure $ SuspendedSpec x pred3
2✔
713
  Reifies (Lit a) (Lit val) f
714
    | f val == a -> pure TrueSpec
×
715
    | otherwise ->
×
716
        pure $
×
717
          ErrorSpec (NE.fromList ["Value does not reify to literal: " ++ show val ++ " -/> " ++ show a])
×
718
  Reifies t' (Lit val) f ->
719
    propagateSpec (equalSpec (f val)) <$> toCtx x t'
×
720
  Reifies Lit {} _ _ ->
721
    fatalErrorNE $ NE.fromList ["Dependency error in computeSpec: Reifies", "  " ++ show pred3]
2✔
722
  Explain es p -> do
2✔
723
    -- In case things crash in here we want the explanation
724
    s <- pushGE (NE.toList es) (computeSpecSimplified x p)
2✔
725
    -- This is because while we do want to propagate `explanation`s into `SuspendedSpec`
726
    -- we probably don't want to propagate the full "currently simplifying xyz" explanation.
727
    case s of
2✔
728
      SuspendedSpec x2 p2 -> pure $ SuspendedSpec x2 (explanation es p2)
1✔
729
      _ -> pure $ addToErrorSpec es s
1✔
730
  -- Impossible cases that should be ruled out by the dependency analysis and linearizer
731
  DependsOn {} ->
732
    fatalErrorNE $
×
733
      NE.fromList
×
734
        [ "The impossible happened in computeSpec: DependsOn"
×
735
        , "  " ++ show x
×
736
        , show $ indent 2 (pretty pred3)
×
737
        ]
738
  Reifies {} ->
739
    fatalErrorNE $
×
740
      NE.fromList
×
741
        ["The impossible happened in computeSpec: Reifies", "  " ++ show x, show $ indent 2 (pretty pred3)]
×
742
  where
743
    -- We want `genError` to turn into `ErrorSpec` and we want `FatalError` to turn into `FatalError`
744
    localGESpec ge = case ge of
2✔
745
      (GenError xs) -> Result $ ErrorSpec (catMessageList xs)
1✔
746
      (FatalError es) -> FatalError es
2✔
747
      (Result v) -> Result v
2✔
748

749
-- | Precondition: the `Pred fn` defines the `Var a`.
750
--   Runs in `GE` in order for us to have detailed context on failure.
751
computeSpec ::
752
  forall a. (HasSpec a, HasCallStack) => Var a -> Pred -> GE (Specification a)
753
computeSpec x p = computeSpecSimplified x (simplifyPred p)
2✔
754

755
computeSpecBinderSimplified :: Binder a -> GE (Specification a)
756
computeSpecBinderSimplified (x :-> p) = computeSpecSimplified x p
2✔
757

758
-- | Turn a list of branches into a SumSpec. If all the branches fail return an ErrorSpec.
759
--   Note the requirement of HasSpec(SumOver).
760
caseSpec ::
761
  forall as.
762
  HasSpec (SumOver as) =>
763
  Maybe String ->
764
  List (Weighted (Specification)) as ->
765
  Specification (SumOver as)
766
caseSpec tString ss
2✔
767
  | allBranchesFail ss =
2✔
768
      ErrorSpec
2✔
769
        ( NE.fromList
×
770
            [ "When simplifying SumSpec, all branches in a caseOn" ++ sumType tString ++ " simplify to False."
×
771
            , show spec
×
772
            ]
773
        )
774
  | True = spec
1✔
775
  where
776
    spec = loop tString ss
1✔
777

778
    allBranchesFail :: forall as2. List (Weighted Specification) as2 -> Bool
779
    allBranchesFail Nil = error "The impossible happened in allBranchesFail"
1✔
780
    allBranchesFail (Weighted _ s :> Nil) = isErrorLike s
2✔
781
    allBranchesFail (Weighted _ s :> ss2@(_ :> _)) = isErrorLike s && allBranchesFail ss2
2✔
782

783
    loop ::
784
      forall as3.
785
      HasSpec (SumOver as3) =>
786
      Maybe String ->
787
      List (Weighted Specification) as3 ->
788
      Specification (SumOver as3)
789
    loop _ Nil = error "The impossible happened in caseSpec"
1✔
790
    loop _ (s :> Nil) = thing s
2✔
791
    loop mTypeString (s :> ss1@(_ :> _))
792
      | Evidence <- prerequisites @(SumOver as3) =
2✔
793
          (typeSpec $ SumSpecRaw mTypeString theWeights (thing s) (loop Nothing ss1))
1✔
794
      where
795
        theWeights =
2✔
796
          case (weight s, totalWeight ss1) of
2✔
797
            (Nothing, Nothing) -> Nothing
2✔
798
            (a, b) -> Just (fromMaybe 1 a, fromMaybe (lengthList ss1) b)
1✔
799

800
------------------------------------------------------------------------
801
-- SumSpec et al
802
------------------------------------------------------------------------
803

804
-- | The Specification for Sums.
805
data SumSpec a b
806
  = SumSpecRaw
807
      (Maybe String) -- A String which is the type of arg in (caseOn arg branch1 .. branchN)
808
      (Maybe (Int, Int))
809
      (Specification a)
810
      (Specification b)
811

812
-- | The "normal" view of t`SumSpec` that doesn't take weights into account
813
pattern SumSpec ::
814
  (Maybe (Int, Int)) -> (Specification a) -> (Specification b) -> SumSpec a b
815
pattern SumSpec a b c <- SumSpecRaw _ a b c
2✔
816
  where
817
    SumSpec a b c = SumSpecRaw Nothing a b c
1✔
818

819
{-# COMPLETE SumSpec #-}
820

821
sumType :: Maybe String -> String
822
sumType Nothing = ""
×
823
sumType (Just x) = " type=" ++ x
×
824

825
totalWeight :: List (Weighted f) as -> Maybe Int
826
totalWeight = fmap getSum . foldMapList (fmap Semigroup.Sum . weight)
2✔
827

828
-- =================================
829
-- Operations on Stages and Plans
830

831
-- | Does nothing if the variable is not in the plan already.
832
mergeSolverStage :: SolverStage -> [SolverStage] -> [SolverStage]
833
mergeSolverStage (SolverStage x ps spec relevant) plan =
2✔
834
  [ case eqVar x y of
2✔
835
      Just Refl ->
836
        normalizeSolverStage $ SolverStage
2✔
837
            y
2✔
838
            (ps ++ ps')
2✔
839
            (spec <> spec')
2✔
840
            (relevant <> relevant')
2✔
841
      Nothing -> stage
2✔
842
  | stage@(SolverStage y ps' spec' relevant') <- plan
2✔
843
  ]
844

845
isEmptyPlan :: SolverPlan -> Bool
846
isEmptyPlan (SolverPlan plan) = null plan
2✔
847

848
stepPlan :: MonadGenError m => SolverPlan -> Env -> SolverPlan -> GenT m (Env, SolverPlan)
849
stepPlan _ env plan@(SolverPlan []) = pure (env, plan)
1✔
850
stepPlan (SolverPlan origStages) env (SolverPlan (stage@(SolverStage (x :: Var a) ps spec relevant) : pl)) = do
2✔
851
  let errorMessage = "Failed to step the plan" />
2✔
852
                        vsep [ "Relevant parts of the original plan:" //> pretty narrowedOrigPlan
2✔
853
                             , "Already generated variables:" //> pretty narrowedEnv
2✔
854
                             , "Current stage:" //> pretty stage
2✔
855
                             ]
856
      relevant' = Set.insert (Name x) relevant
2✔
857
      narrowedOrigPlan = SolverPlan $ [ st | st@(SolverStage v _ _ _) <- origStages, Name v `Set.member` relevant' ]
2✔
858
      narrowedEnv = Env.filterKeys env (\v -> nameOf v `Set.member` (Set.map (\ (Name n) -> nameOf n) relevant'))
2✔
859
  explain (show errorMessage) $ do
2✔
860
    when (isErrorLike spec) $
2✔
861
      genError "The specification in the current stage is unsatisfiable, giving up."
2✔
862
    when (not $ null ps) $
2✔
863
      fatalError "Something went wrong and not all predicates have been discharged. Report this as a bug in Constrained.Generation"
×
864
    val <- genFromSpecT spec
2✔
865
    let env1 = Env.extend x val env
2✔
866
    pure (env1, backPropagation relevant' $ SolverPlan (substStage relevant' x val <$> pl) )
2✔
867

868
-- | Generate a satisfying `Env` for a `p : Pred fn`. The `Env` contains values for
869
-- all the free variables in `flattenPred p`.
870
genFromPreds :: forall m. MonadGenError m => Env -> Pred -> GenT m Env
871
-- TODO: remove this once optimisePred does a proper fixpoint computation
872
genFromPreds env0 (optimisePred . optimisePred -> preds) = do
2✔
873
    -- NOTE: this is just lazy enough that the work of flattening,
874
    -- computing dependencies, and linearizing is memoized in
875
    -- properties that use `genFromPreds`.
876
    origPlan <- runGE $ prepareLinearization preds
2✔
877
    let go :: Env -> SolverPlan -> GenT m Env
878
        go env plan | isEmptyPlan plan = pure env
2✔
879
        go env plan = do
2✔
880
          (env', plan') <- stepPlan origPlan env plan
2✔
881
          go env' plan'
2✔
882
    go env0 origPlan
2✔
883
  where
884

885
-- | Push as much information we can backwards through the plan.
886
backPropagation :: Set Name -> SolverPlan -> SolverPlan
887
backPropagation relevant (SolverPlan initplan) = SolverPlan (go [] (reverse initplan))
2✔
888
  where
889
    go :: [SolverStage] -> [SolverStage] -> [SolverStage]
890
    go acc [] = acc
2✔
891
    go acc (s@(SolverStage (x :: Var a) ps spec _) : plan) = go (s : acc) plan'
2✔
892
      where
893
        newStages = concatMap newStage ps
2✔
894
        plan' = foldr mergeSolverStage plan newStages
2✔
895

896
        -- Note use of the Term Pattern Equal
897
        newStage (Assert (Equal tl tr))
2✔
898
          | [Name xl] <- Set.toList $ freeVarSet tl
2✔
899
          , [Name xr] <- Set.toList $ freeVarSet tr
2✔
900
          , Name x `elem` [Name xl, Name xr]
1✔
901
          , Result ctxL <- toCtx xl tl
2✔
902
          , Result ctxR <- toCtx xr tr
2✔
903
          = case (eqVar x xl, eqVar x xr) of
2✔
904
              (Just Refl, _) -> [SolverStage xr [] (propagateSpec (forwardPropagateSpec spec ctxL) ctxR) (Set.insert (Name x) relevant)]
2✔
905
              (_, Just Refl) -> [SolverStage xl [] (propagateSpec (forwardPropagateSpec spec ctxR) ctxL) (Set.insert (Name x) relevant)]
2✔
906
              _ -> error "The impossible happened"
×
907
        newStage _ = []
2✔
908

909
-- | Function symbols for `(==.)`
910
data EqW :: [Type] -> Type -> Type where
911
  EqualW :: (Eq a, HasSpec a) => EqW '[a, a] Bool
912

913
deriving instance Eq (EqW dom rng)
×
914

915
instance Show (EqW d r) where
×
916
  show EqualW = "==."
2✔
917

918
instance Syntax EqW where
2✔
919
  isInfix EqualW = True
2✔
920

921
instance Semantics EqW where
922
  semantics EqualW = (==)
2✔
923

924
instance Logic EqW where
×
925
  propagate f ctxt (ExplainSpec es s) = explainSpec es $ propagate f ctxt s
2✔
926
  propagate _ _ TrueSpec = TrueSpec
2✔
927
  propagate _ _ (ErrorSpec msgs) = ErrorSpec msgs
1✔
928
  propagate EqualW (HOLE :? Value x :> Nil) (SuspendedSpec v ps) =
929
    constrained $ \v' -> Let (App EqualW (v' :> Lit x :> Nil)) (v :-> ps)
2✔
930
  propagate EqualW (Value x :! Unary HOLE) (SuspendedSpec v ps) =
931
    constrained $ \v' -> Let (App EqualW (Lit x :> v' :> Nil)) (v :-> ps)
2✔
932
  propagate EqualW (HOLE :? Value s :> Nil) spec =
933
    caseBoolSpec spec $ \case
2✔
934
      True -> equalSpec s
2✔
935
      False -> notEqualSpec s
2✔
936
  propagate EqualW (Value s :! Unary HOLE) spec =
937
    caseBoolSpec spec $ \case
2✔
938
      True -> equalSpec s
2✔
939
      False -> notEqualSpec s
2✔
940

941
  rewriteRules EqualW (t :> t' :> Nil) Evidence
2✔
942
    | t == t' = Just $ lit True
2✔
943
    | otherwise = Nothing
1✔
944

945
  saturate EqualW (FromGeneric (InjLeft _) :> t :> Nil) = [toPreds t (SumSpec Nothing TrueSpec (ErrorSpec (pure "saturatePred")))]
1✔
946
  saturate EqualW (FromGeneric (InjRight _) :> t :> Nil) = [toPreds t (SumSpec Nothing (ErrorSpec (pure "saturatePred")) TrueSpec)]
1✔
947
  saturate _ _ = []
2✔
948

949
infix 4 ==.
950

951
-- | Equality on the constraint-level
952
(==.) :: HasSpec a => Term a -> Term a -> Term Bool
953
(==.) = appTerm EqualW
2✔
954

955
-- | Pattern version of `(==.)` for rewrite rules
956
pattern Equal ::
957
  forall b.
958
  () =>
959
  forall a.
960
  (b ~ Bool, Eq a, HasSpec a) =>
961
  Term a ->
962
  Term a ->
963
  Term b
964
pattern Equal x y <-
965
  ( App
2✔
966
      (getWitness -> Just EqualW)
967
      (x :> y :> Nil)
968
    )
969

970
-- | Like @if b then p else assert True@ in constraint-land
971
whenTrue :: forall p. IsPred p => Term Bool -> p -> Pred
972
whenTrue (Lit True) (toPred -> p) = p
2✔
973
whenTrue (Lit False) _ = TruePred
2✔
974
whenTrue b (toPred -> FalsePred {}) = assert (not_ b)
2✔
975
whenTrue _ (toPred -> TruePred) = TruePred
2✔
976
whenTrue b (toPred -> p) = When b p
2✔
977

978
-- | Is the variable x pinned to some free term in p? (free term
979
-- meaning that all the variables in the term are free in p).
980
--
981
-- TODO: complete this with more cases!
982
pinnedBy :: forall a. HasSpec a => Var a -> Pred -> Maybe (Term a)
983
pinnedBy x (Assert (Equal t t'))
2✔
984
  | V x' <- t, Just Refl <- eqVar x x' = Just t'
2✔
985
  | V x' <- t', Just Refl <- eqVar x x' = Just t
2✔
986
pinnedBy x (And ps) = listToMaybe $ catMaybes $ map (pinnedBy x) ps
2✔
987
pinnedBy _ _ = Nothing
2✔
988

989
-- ==================================================================================================
990
-- TODO: generalize this to make it more flexible and extensible
991
--
992
-- The idea here is that we turn constraints into _extra_ constraints. C.f. the
993
-- `mapIsJust` example in `Constrained.Examples.Map`:
994

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

999
-- Without this code the example wouldn't work because `y` is completely unconstrained during
1000
-- generation. With this code we _essentially_ rewrite occurences of `just_ A == B` to
1001
-- `[just_ A == B, case B of Nothing -> False; Just _ -> True]` to add extra information
1002
-- about the variables in `B`. Consequently, `y` in the example above is
1003
-- constrained to `MemberSpec [100 .. 102]` in the plan. This is implemented using the `saturate`
1004
-- function in the logic type class - in the example above for `==`.
1005
saturatePred :: Pred -> [Pred]
1006
saturatePred p =
2✔
1007
  -- [p]
1008
  --  + ---- if there is an Explain, it is still on 'p' here
1009
  --  |
1010
  --  v
1011
  p : case p of
2✔
1012
    Explain _es x -> saturatePred x -- Note that the Explain is still on the original 'p', so it is not lost
2✔
1013
    -- Note how the saturation is done by the 'saturate' method of the Logic class
1014
    Assert (App sym xs) -> saturate sym xs
2✔
1015
    _ -> []
2✔
1016

1017
-- ==================================================================
1018
-- HasSpec for Sums
1019
-- ==================================================================
1020

1021
guardSumSpec ::
1022
  forall a b.
1023
  (HasSpec a, HasSpec b, KnownNat (CountCases b)) =>
1024
  [String] ->
1025
  SumSpec a b ->
1026
  Specification (Sum a b)
1027
guardSumSpec msgs s@(SumSpecRaw tString _ sa sb)
2✔
1028
  | isErrorLike sa
2✔
1029
  , isErrorLike sb =
1✔
1030
      ErrorSpec $
×
1031
        NE.fromList $
×
1032
          msgs ++ ["All branches in a caseOn" ++ sumType tString ++ " simplify to False.", show s]
×
1033
  | otherwise = typeSpec s
1✔
1034

1035
instance (KnownNat (CountCases b), HasSpec a, HasSpec b) => Show (SumSpec a b) where
×
1036
  show sumspec@(SumSpecRaw tstring hint l r) = case alternateShow @(Sum a b) sumspec of
×
1037
    (BinaryShow _ ps) -> show $ parens (fromString ("SumSpec" ++ sumType tstring) /> vsep ps)
×
1038
    NonBinary ->
1039
      "(SumSpec"
×
1040
        ++ sumType tstring
×
1041
        ++ show (sumWeightL hint)
×
1042
        ++ " ("
×
1043
        ++ show l
×
1044
        ++ ") "
×
1045
        ++ show (sumWeightR hint)
×
1046
        ++ " ("
×
1047
        ++ show r
×
1048
        ++ "))"
×
1049

1050
combTypeName :: Maybe String -> Maybe String -> Maybe String
1051
combTypeName (Just x) (Just y) =
×
1052
  if x == y then Just x else Just ("(" ++ x ++ " | " ++ y ++ ")")
×
1053
combTypeName (Just x) Nothing = Just x
×
1054
combTypeName Nothing (Just x) = Just x
×
1055
combTypeName Nothing Nothing = Nothing
×
1056

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

1066
instance forall a b. (HasSpec a, HasSpec b, KnownNat (CountCases b)) => Monoid (SumSpec a b) where
×
1067
  mempty = SumSpec Nothing mempty mempty
2✔
1068

1069
-- | How many constructors are there in this type?
1070
type family CountCases a where
1071
  CountCases (Sum a b) = 1 + CountCases b
1072
  CountCases _ = 1
1073

1074
countCases :: forall a. KnownNat (CountCases a) => Int
1075
countCases = fromIntegral (natVal @(CountCases a) Proxy)
1✔
1076

1077
-- | The HasSpec Sum instance
1078
instance (HasSpec a, HasSpec b, KnownNat (CountCases b)) => HasSpec (Sum a b) where
1✔
1079
  type TypeSpec (Sum a b) = SumSpec a b
1080

1081
  type Prerequisites (Sum a b) = (HasSpec a, HasSpec b)
1082

1083
  emptySpec = mempty
2✔
1084

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

1087
  conformsTo (SumLeft a) (SumSpec _ sa _) = conformsToSpec a sa
2✔
1088
  conformsTo (SumRight b) (SumSpec _ _ sb) = conformsToSpec b sb
2✔
1089

1090
  genFromTypeSpec (SumSpec h sa sb)
2✔
1091
    | emptyA, emptyB = genError "genFromTypeSpec @SumSpec: empty"
1✔
1092
    | emptyA = SumRight <$> genFromSpecT sb
2✔
1093
    | emptyB = SumLeft <$> genFromSpecT sa
2✔
1094
    | fA == 0, fB == 0 = genError "All frequencies 0"
1✔
1095
    | otherwise =
×
1096
        frequencyT
2✔
1097
          [ (fA, SumLeft <$> genFromSpecT sa)
2✔
1098
          , (fB, SumRight <$> genFromSpecT sb)
2✔
1099
          ]
1100
    where
1101
      (max 0 -> fA, max 0 -> fB) = fromMaybe (1, countCases @b) h
2✔
1102
      emptyA = isErrorLike sa
2✔
1103
      emptyB = isErrorLike sb
2✔
1104

1105
  shrinkWithTypeSpec (SumSpec _ sa _) (SumLeft a) = SumLeft <$> shrinkWithSpec sa a
×
1106
  shrinkWithTypeSpec (SumSpec _ _ sb) (SumRight b) = SumRight <$> shrinkWithSpec sb b
×
1107

1108
  toPreds ct (SumSpec h sa sb) =
2✔
1109
    Case
2✔
1110
      ct
2✔
1111
      ( (Weighted (fst <$> h) $ bind $ \a -> satisfies a sa)
2✔
1112
          :> (Weighted (snd <$> h) $ bind $ \b -> satisfies b sb)
2✔
1113
          :> Nil
2✔
1114
      )
1115

1116
  cardinalTypeSpec (SumSpec _ leftspec rightspec) = addSpecInt (cardinality leftspec) (cardinality rightspec)
2✔
1117

1118
  typeSpecHasError (SumSpec _ x y) =
2✔
1119
    case (isErrorLike x, isErrorLike y) of
2✔
1120
      (True, True) -> Just $ (errorLikeMessage x <> errorLikeMessage y)
×
1121
      _ -> Nothing
2✔
1122

1123
  alternateShow (SumSpec h left right@(TypeSpec r [])) =
×
1124
    case alternateShow @b r of
×
1125
      (BinaryShow "SumSpec" ps) -> BinaryShow "SumSpec" ("|" <+> sumWeightL h <+> viaShow left : ps)
×
1126
      (BinaryShow "Cartesian" ps) ->
1127
        BinaryShow "SumSpec" ("|" <+> sumWeightL h <+> viaShow left : [parens ("Cartesian" /> vsep ps)])
×
1128
      _ ->
1129
        BinaryShow "SumSpec" ["|" <+> sumWeightL h <+> viaShow left, "|" <+> sumWeightR h <+> viaShow right]
×
1130
  alternateShow (SumSpec h left right) =
1131
    BinaryShow "SumSpec" ["|" <+> sumWeightL h <+> viaShow left, "|" <+> sumWeightR h <+> viaShow right]
×
1132

1133
-- ======================================
1134
-- Here are the Logic Instances for Sum
1135

1136
-- | Function symbols for `injLeft_` and `injRight_`
1137
data SumW dom rng where
1138
  InjLeftW :: SumW '[a] (Sum a b)
1139
  InjRightW :: SumW '[b] (Sum a b)
1140

1141
instance Show (SumW dom rng) where
×
1142
  show InjLeftW = "injLeft_"
2✔
1143
  show InjRightW = "injRight_"
2✔
1144

1145
deriving instance (Eq (SumW dom rng))
×
1146

1147
instance Syntax SumW
×
1148

1149
instance Semantics SumW where
1150
  semantics InjLeftW = SumLeft
2✔
1151
  semantics InjRightW = SumRight
2✔
1152

1153
instance Logic SumW where
1✔
1154
  propagateTypeSpec InjLeftW (Unary HOLE) (SumSpec _ sl _) cant = sl <> foldMap notEqualSpec [a | SumLeft a <- cant]
2✔
1155
  propagateTypeSpec InjRightW (Unary HOLE) (SumSpec _ _ sr) cant = sr <> foldMap notEqualSpec [a | SumRight a <- cant]
2✔
1156

1157
  propagateMemberSpec InjLeftW (Unary HOLE) es =
2✔
1158
    case [a | SumLeft a <- NE.toList es] of
2✔
1159
      (x : xs) -> MemberSpec (x :| xs)
2✔
1160
      [] ->
1161
        ErrorSpec $
2✔
1162
          pure $
×
1163
            "propMemberSpec (sumleft_ HOLE) on (MemberSpec es) with no SumLeft in es: " ++ show (NE.toList es)
×
1164
  propagateMemberSpec InjRightW (Unary HOLE) es =
1165
    case [a | SumRight a <- NE.toList es] of
2✔
1166
      (x : xs) -> MemberSpec (x :| xs)
2✔
1167
      [] ->
1168
        ErrorSpec $
2✔
1169
          pure $
×
1170
            "propagate(InjRight HOLE) on (MemberSpec es) with no SumLeft in es: " ++ show (NE.toList es)
×
1171

1172
  mapTypeSpec InjLeftW ts = typeSpec $ SumSpec Nothing (typeSpec ts) (ErrorSpec (pure "mapTypeSpec InjLeftW"))
1✔
1173
  mapTypeSpec InjRightW ts = typeSpec $ SumSpec Nothing (ErrorSpec (pure "mapTypeSpec InjRightW")) (typeSpec ts)
1✔
1174

1175
-- | Constructor for `Sum`
1176
injLeft_ :: (HasSpec a, HasSpec b, KnownNat (CountCases b)) => Term a -> Term (Sum a b)
1177
injLeft_ = appTerm InjLeftW
2✔
1178

1179
-- | Constructor for `Sum`
1180
injRight_ :: (HasSpec a, HasSpec b, KnownNat (CountCases b)) => Term b -> Term (Sum a b)
1181
injRight_ = appTerm InjRightW
2✔
1182

1183
-- | Pattern for building custom rewrite rules
1184
pattern InjRight ::
1185
  forall c.
1186
  () =>
1187
  forall a b.
1188
  ( c ~ Sum a b
1189
  , AppRequires SumW '[b] c
1190
  ) =>
1191
  Term b ->
1192
  Term c
1193
pattern InjRight x <- (App (getWitness -> Just InjRightW) (x :> Nil))
2✔
1194

1195
-- | Pattern for building custom rewrite rules
1196
pattern InjLeft ::
1197
  forall c.
1198
  () =>
1199
  forall a b.
1200
  ( c ~ Sum a b
1201
  , AppRequires SumW '[a] c
1202
  ) =>
1203
  Term a ->
1204
  Term c
1205
pattern InjLeft x <- App (getWitness -> Just InjLeftW) (x :> Nil)
2✔
1206

1207
sumWeightL, sumWeightR :: Maybe (Int, Int) -> Doc a
1208
sumWeightL Nothing = "1"
×
1209
sumWeightL (Just (x, _)) = fromString (show x)
×
1210
sumWeightR Nothing = "1"
×
1211
sumWeightR (Just (_, x)) = fromString (show x)
×
1212

1213
-- | Operations on Bool
1214
data BoolW (dom :: [Type]) (rng :: Type) where
1215
  NotW :: BoolW '[Bool] Bool
1216
  OrW :: BoolW '[Bool, Bool] Bool
1217

1218
deriving instance Eq (BoolW dom rng)
×
1219

1220
instance Show (BoolW dom rng) where
×
1221
  show NotW = "not_"
2✔
1222
  show OrW = "or_"
2✔
1223

1224
boolSem :: BoolW dom rng -> FunTy dom rng
1225
boolSem NotW = not
2✔
1226
boolSem OrW = (||)
2✔
1227

1228
instance Semantics BoolW where
1229
  semantics = boolSem
2✔
1230

1231
instance Syntax BoolW
×
1232

1233
-- ======= Logic instance BoolW
1234

1235
instance Logic BoolW where
1✔
1236
  propagate f ctxt (ExplainSpec [] s) = propagate f ctxt s
1✔
1237
  propagate f ctxt (ExplainSpec es s) = ExplainSpec es $ propagate f ctxt s
2✔
1238
  propagate _ _ TrueSpec = TrueSpec
2✔
1239
  propagate _ _ (ErrorSpec msgs) = ErrorSpec msgs
1✔
1240
  propagate NotW (Unary HOLE) (SuspendedSpec v ps) =
1241
    constrained $ \v' -> Let (App NotW (v' :> Nil)) (v :-> ps)
2✔
1242
  propagate NotW (Unary HOLE) spec =
1243
    caseBoolSpec spec (equalSpec . not)
2✔
1244
  propagate OrW (HOLE :<: x) (SuspendedSpec v ps) =
1245
    constrained $ \v' -> Let (App OrW (v' :> Lit x :> Nil)) (v :-> ps)
2✔
1246
  propagate OrW (x :>: HOLE) (SuspendedSpec v ps) =
1247
    constrained $ \v' -> Let (App OrW (Lit x :> v' :> Nil)) (v :-> ps)
2✔
1248
  propagate OrW (HOLE :<: s) spec =
1249
    caseBoolSpec spec (okOr s)
2✔
1250
  propagate OrW (s :>: HOLE) spec =
1251
    caseBoolSpec spec (okOr s)
2✔
1252

1253
  mapTypeSpec NotW () = typeSpec ()
×
1254

1255
-- | We have something like ('constant' ||. HOLE) must evaluate to 'need'.
1256
--   Return a (Specification Bool) for HOLE, that makes that True.
1257
okOr :: Bool -> Bool -> Specification Bool
1258
okOr constant need = case (constant, need) of
2✔
1259
  (True, True) -> TrueSpec
2✔
1260
  (True, False) ->
1261
    ErrorSpec
2✔
1262
      (pure ("(" ++ show constant ++ "||. HOLE) must equal False. That cannot be the case."))
×
1263
  (False, False) -> MemberSpec (pure False)
2✔
1264
  (False, True) -> MemberSpec (pure True)
2✔
1265

1266
-- | Disjunction on @`Term` `Bool`@, note that this will not cause backtracking during generation
1267
or_ :: Term Bool -> Term Bool -> Term Bool
1268
or_ = appTerm OrW
2✔
1269

1270
-- | Negation of booleans
1271
not_ :: Term Bool -> Term Bool
1272
not_ = appTerm NotW
2✔
1273

1274
-- ===============================================================================
1275
-- Syntax for Solving : stages and plans
1276

1277
data SolverStage where
1278
  SolverStage ::
1279
    HasSpec a =>
1280
    { stageVar :: Var a
×
1281
    , stagePreds :: [Pred]
×
1282
    , stageSpec :: Specification a
×
1283
    , relevantVariables :: Set Name
×
1284
    } ->
1285
    SolverStage
1286

1287
docVar :: Typeable a => Var a -> Doc h
1288
docVar (v :: Var a) = fromString (show v ++ " :: " ++ showType @a)
2✔
1289

1290
instance Pretty SolverStage where
×
1291
  pretty SolverStage {..} =
2✔
1292
    docVar stageVar
2✔
1293
      <+> "<-"
2✔
1294
        /> vsep'
2✔
1295
          ( [pretty stageSpec | not $ isTrueSpec stageSpec]
2✔
1296
              ++ ["---" | not $ null stagePreds, not $ isTrueSpec stageSpec]
2✔
1297
              ++ map pretty stagePreds
2✔
1298
              ++ ["_" | null stagePreds && isTrueSpec stageSpec]
2✔
1299
          )
1300

1301
newtype SolverPlan = SolverPlan { solverPlan :: [SolverStage] }
×
1302

1303
instance Pretty SolverPlan where
×
1304
  pretty SolverPlan {..} =
2✔
1305
    "SolverPlan" /> prettyLinear solverPlan
2✔
1306

1307
isTrueSpec :: Specification a -> Bool
1308
isTrueSpec TrueSpec = True
2✔
1309
isTrueSpec _ = False
2✔
1310

1311
prettyLinear :: [SolverStage] -> Doc ann
1312
prettyLinear = vsep' . map pretty
2✔
1313

1314
fromGESpec :: HasCallStack => GE (Specification a) -> Specification a
1315
fromGESpec ge = case ge of
2✔
1316
  Result s -> s
2✔
1317
  GenError xs -> ErrorSpec (catMessageList xs)
×
1318
  FatalError es -> error $ catMessages es
2✔
1319

1320
-- | Functor like property for Specification, but instead of a Haskell function (a -> b),
1321
--   it takes a function symbol (t '[a] b) from a to b.
1322
--   Note, in this context, a function symbol is some constructor of a witnesstype.
1323
--   Eg. ProdFstW, InjRightW, SingletonW, etc. NOT the lifted versions like fst_ singleton_,
1324
--   which construct Terms. We had to wait until here to define this because it
1325
--   depends on Semigroup property of Specification, and Asserting equality
1326
mapSpec ::
1327
  forall t a b.
1328
  AppRequires t '[a] b =>
1329
  t '[a] b ->
1330
  Specification a ->
1331
  Specification b
1332
mapSpec f (ExplainSpec es s) = explainSpec es (mapSpec f s)
2✔
1333
mapSpec f TrueSpec = mapTypeSpec f (emptySpec @a)
2✔
1334
mapSpec _ (ErrorSpec err) = ErrorSpec err
1✔
1335
mapSpec f (MemberSpec as) = MemberSpec $ NE.nub $ fmap (semantics f) as
2✔
1336
mapSpec f (SuspendedSpec x p) =
1337
  constrained $ \x' ->
2✔
1338
    Exists (\_ -> fatalError "mapSpec") (x :-> fold [Assert $ (x' ==. appTerm f (V x)), p])
1✔
1339
mapSpec f (TypeSpec ts cant) = mapTypeSpec f ts <> notMemberSpec (map (semantics f) cant)
2✔
1340

1341
-- TODO generalizeme!
1342
forwardPropagateSpec :: HasSpec a => Specification a -> Ctx a b -> Specification b
1343
forwardPropagateSpec s CtxHOLE = s
2✔
1344
forwardPropagateSpec s (CtxApp f (c :? Nil))
1345
  | Evidence <- ctxHasSpec c = mapSpec f (forwardPropagateSpec s c)
2✔
1346
forwardPropagateSpec _ _ = TrueSpec
2✔
1347

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