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

input-output-hk / constrained-generators / 465

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

push

github

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

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

18 existing lines in 4 files now uncovered.

3974 of 5158 relevant lines covered (77.05%)

1.45 hits per line

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

74.27
/src/Constrained/Base.hs
1
{-# LANGUAGE AllowAmbiguousTypes #-}
2
{-# LANGUAGE ConstraintKinds #-}
3
{-# LANGUAGE DataKinds #-}
4
{-# LANGUAGE DefaultSignatures #-}
5
{-# LANGUAGE FlexibleContexts #-}
6
{-# LANGUAGE FlexibleInstances #-}
7
{-# LANGUAGE FunctionalDependencies #-}
8
{-# LANGUAGE GADTs #-}
9
{-# LANGUAGE LambdaCase #-}
10
{-# LANGUAGE OverloadedStrings #-}
11
{-# LANGUAGE PatternSynonyms #-}
12
{-# LANGUAGE ScopedTypeVariables #-}
13
{-# LANGUAGE StandaloneDeriving #-}
14
{-# LANGUAGE TypeApplications #-}
15
{-# LANGUAGE TypeFamilies #-}
16
{-# LANGUAGE TypeOperators #-}
17
{-# LANGUAGE UndecidableInstances #-}
18
{-# LANGUAGE UndecidableSuperClasses #-}
19
{-# LANGUAGE ViewPatterns #-}
20

21
-- | This module contains the most basic parts the implementation. Essentially
22
--   everything to define Specification, HasSpec, HasSimpleRep, Term, Pred, and the Syntax,
23
--   Semantics, and Logic class. It also has a few HasSpec, HasSimpleRep, and Logic
24
--   instances for basic types needed to define the default types and methods of HasSpec.
25
--   It also supplies Eq, Pretty, and Show instances on the syntax (Term, Pred, Binder etc.)
26
--   because many functions require these instances. It exports functions that define the
27
--   user interface to the domain embedded language (constrained, forall, exists etc.).
28
--   And, by design, nothing more.
29
module Constrained.Base (
30
  -- * Implementing logic propagation
31
  Logic (..),
32
  pattern (:<:),
33
  pattern (:>:),
34
  pattern Unary,
35
  Ctx (..),
36
  toCtx,
37
  flipCtx,
38
  fromListCtx,
39
  ctxHasSpec,
40

41
  -- * Useful function symbols and patterns for building custom rewrite rules
42
  fromGeneric_,
43
  toGeneric_,
44
  pattern ToGeneric,
45
  pattern FromGeneric,
46

47
  -- * Syntax for building specifications
48
  constrained,
49
  notMemberSpec,
50
  notEqualSpec,
51
  typeSpec,
52
  addToErrorSpec,
53
  memberSpec,
54
  fromSimpleRepSpec,
55
  toSimpleRepSpec,
56
  explainSpec,
57

58
  -- * Instantiated types and helper patterns
59
  Term,
60
  Specification,
61
  Pred,
62
  Binder,
63
  pattern TypeSpec,
64
  pattern GenHint,
65

66
  -- * Constraints and classes
67
  HasSpec (..),
68
  HasGenHint (..),
69
  Forallable,
70
  AppRequires,
71
  GenericallyInstantiated,
72
  GenericRequires,
73

74
  -- * Building `Pred`, `Specification`, `Term` etc.
75
  bind,
76
  name,
77

78
  -- * TODO: documentme
79
  propagateSpec,
80
  appFun,
81
  errorLikeMessage,
82
  isErrorLike,
83
  BinaryShow (..),
84
  toPred,
85
  forAllToList,
86
  IsPred,
87
  equalSpec,
88
  appTerm,
89
  HOLE (..),
90
  fromForAllSpec,
91
  Fun (..),
92
  BaseW (..),
93
  Deps,
94
) where
95

96
import Constrained.AbstractSyntax
97
import Constrained.Core
98
import Constrained.DependencyInjection
99
import Constrained.FunctionSymbol
100
import Constrained.GenT
101
import Constrained.Generic
102
import Constrained.List hiding (toList)
103
import Constrained.TypeErrors
104
import Data.Foldable (
105
  toList,
106
 )
107
import Data.Kind (Constraint, Type)
108
import qualified Data.List.NonEmpty as NE
109
import Data.Orphans ()
110
import Data.Semigroup (Max (..), getMax)
111
import Data.Typeable
112
import Data.List (nub)
113
import GHC.Stack
114
import Prettyprinter hiding (cat)
115
import Test.QuickCheck (arbitrary, shrink)
116

117
newtype TypeSpecF a = TypeSpecF (TypeSpec a)
118

119
instance Show (TypeSpec a) => Show (TypeSpecF a) where
1✔
120
  show (TypeSpecF ts) = show ts
2✔
121

122
newtype HintF a = HintF (Hint a)
123

124
instance Show (Hint a) => Show (HintF a) where
×
125
  show (HintF h) = show h
×
126

127
data Deps
128

129
instance Dependencies Deps where
130
  type HasSpecD Deps = HasSpec
131
  type TypeSpecD Deps = TypeSpecF
132
  type LogicD Deps = Logic
133
  type ForallableD Deps = Forallable
134
  type HasGenHintD Deps = HasGenHint
135
  type HintD Deps = HintF
136

137
-- | Binders instantiated with the correct `HasSpec` etc. classes
138
type Binder = BinderD Deps
139

140
-- | All the constraints needed for application in the first order term languge
141
type AppRequires t as b = AppRequiresD Deps t as b
142

143
-- | Predicates over `Term`s
144
type Pred = PredD Deps
145

146
-- | First-order language of variables, literals, and application
147
type Term = TermD Deps
148

149
-- | Specifications for generators instantiated with the `HasSpec` et al actual
150
-- classes
151
type Specification = SpecificationD Deps
152

153
-- | Pattern match out a `TypeSpec` and the can't-"set" - avoids some tedious
154
-- pitfalls related to the `Deps` and `Dependencies` trick
155
pattern TypeSpec :: () => HasSpec a => TypeSpec a -> [a] -> Specification a
156
pattern TypeSpec ts cant = TypeSpecD (TypeSpecF ts) cant
2✔
157

158
{-# COMPLETE ExplainSpec, MemberSpec, ErrorSpec, SuspendedSpec, TypeSpec, TrueSpec #-}
159

160
-- | Build a specifiation from just a `TypeSpec`, useful internal function when
161
-- writing `Logic` instances
162
typeSpec :: HasSpec a => TypeSpec a -> Specification a
163
typeSpec ts = TypeSpec ts mempty
2✔
164

165
-- | Pattern match out a `Hint` and the `Term` it applies to - avoids some
166
-- tedious pitfalls related to the `Deps` and `Dependencies` trick
167
pattern GenHint :: () => HasGenHint a => Hint a -> Term a -> Pred
168
pattern GenHint h t = GenHintD (HintF h) t
2✔
169

170
{-# COMPLETE
171
  ElemPred
172
  , Monitor
173
  , And
174
  , Exists
175
  , Subst
176
  , Let
177
  , Assert
178
  , Reifies
179
  , DependsOn
180
  , ForAll
181
  , Case
182
  , When
183
  , GenHint
184
  , TruePred
185
  , FalsePred
186
  , Explain
187
  #-}
188

189
-- ====================================================================
190

191
-- A First-order typed logic has 4 components
192
--     1. Terms        (Variables (x), Constants (5), and Applications (F x 5)
193
--        Applications, apply a function symbol to a list of arguments: (FunctionSymbol term1 .. termN)
194
--     2. Predicates   (Ordered, Odd, ...)
195
--     3. Connectives  (And, Or, Not, =>, ...)
196
--     4. Quantifiers  (Forall, Exists)
197
--
198
-- The Syntax, Semantics, and Logic classes implement new function symbols in
199
-- the first order logic. Note that a function symbol is first order
200
-- data, that uniquely identifies a higher order function. The three classes
201
-- supply varying levels of functionality, relating to the Syntax, Semantics, and
202
-- Logical operations of the function symbol.
203

204
-- | Logical operations are one that support reasoning about how a function symbol
205
--   relates to logical properties, that we call Specification's
206
class (Typeable t, Semantics t, Syntax t) => Logic t where
207
  {-# MINIMAL propagate | (propagateTypeSpec, propagateMemberSpec) #-}
208

209
  propagateTypeSpec ::
210
    (AppRequires t as b, HasSpec a) =>
211
    t as b ->
212
    ListCtx Value as (HOLE a) ->
213
    TypeSpec b ->
214
    [b] ->
215
    Specification a
216
  propagateTypeSpec f ctx ts cant = propagate f ctx (TypeSpec ts cant)
×
217

218
  propagateMemberSpec ::
219
    (AppRequires t as b, HasSpec a) =>
220
    t as b ->
221
    ListCtx Value as (HOLE a) ->
222
    NonEmpty b ->
223
    Specification a
224
  propagateMemberSpec f ctx xs = propagate f ctx (MemberSpec xs)
×
225

226
  propagate ::
227
    (AppRequires t as b, HasSpec a) =>
228
    t as b ->
229
    ListCtx Value as (HOLE a) ->
230
    Specification b ->
231
    Specification a
232
  propagate f ctx (ExplainSpec es s) = explainSpec es (propagate f ctx s)
2✔
233
  propagate _ _ TrueSpec = TrueSpec
2✔
234
  propagate _ _ (ErrorSpec es) = ErrorSpec es
1✔
235
  propagate f ctx (SuspendedSpec v ps) = constrained $ \v' -> Let (App f (fromListCtx ctx v')) (v :-> ps) :: Pred
2✔
236
  propagate f ctx (TypeSpec ts cant) = propagateTypeSpec f ctx ts cant
2✔
237
  propagate f ctx (MemberSpec xs) = propagateMemberSpec f ctx xs
2✔
238

239
  rewriteRules ::
240
    (TypeList dom, Typeable dom, HasSpec rng, All HasSpec dom) =>
241
    t dom rng ->
242
    List Term dom ->
243
    Evidence (AppRequires t dom rng) ->
244
    Maybe (Term rng)
245
  rewriteRules _ _ _ = Nothing
2✔
246

247
  mapTypeSpec ::
248
    forall a b.
249
    (HasSpec a, HasSpec b) =>
250
    t '[a] b ->
251
    TypeSpec a ->
252
    Specification b
253
  mapTypeSpec _ts _spec = TrueSpec
×
254

255
  saturate :: t dom Bool -> List Term dom -> [Pred]
256
  saturate _symbol _ = []
2✔
257

258
-- | This is where the logical properties of a function symbol are applied to transform one spec into another
259
-- Note if there is a bunch of functions nested together, like (sizeOf_ (elems_ (snd_ x)))
260
-- we propagate each of those nested function symbols over the current spec, one at a time.
261
-- The result of this propagation is then made the current spec in the recusive calls to 'propagateSpec'
262
propagateSpec ::
263
  forall v a.
264
  HasSpec v =>
265
  Specification a ->
266
  Ctx v a ->
267
  Specification v
268
propagateSpec spec = \case
2✔
269
  CtxHOLE -> spec
2✔
270
  CtxApp f (ListCtx pre c suf)
271
    | Evidence <- ctxHasSpec c -> propagateSpec (propagate f (ListCtx pre HOLE suf) spec) c
2✔
272

273
ctxHasSpec :: Ctx v a -> Evidence (HasSpec a)
274
ctxHasSpec CtxHOLE = Evidence
2✔
275
ctxHasSpec CtxApp {} = Evidence
2✔
276

277
-- | Contexts for Terms, basically a term with a _single_ HOLE
278
-- instead of a variable. This is used to traverse the defining
279
-- constraints for a variable and turn them into a spec. Each
280
-- subterm `f vs Ctx vs'` for lists of values `vs` and `vs'`
281
-- gets given to the `propagateSpecFun` for `f` as  `(f vs HOLE vs')`.
282
data Ctx v a where
283
  -- | A single hole of type `v`. Note ctxHOLE is a nullary constructor, where the `a` type index is the same as the `v` type index.
284
  CtxHOLE ::
285
    HasSpec v =>
286
    Ctx v v
287
  -- | The application `f vs Ctx vs'`
288
  CtxApp ::
289
    ( AppRequires fn as b
290
    , HasSpec b
291
    , TypeList as
292
    , Typeable as
293
    , All HasSpec as
294
    , Logic fn
295
    ) =>
296
    fn as b ->
297
    -- This is basically a `List` where
298
    -- everything is `Value` except for
299
    -- one entry which is `Ctx fn v`.
300
    ListCtx Value as (Ctx v) ->
301
    Ctx v b
302

303
-- | This is used together with `ListCtx` to form
304
-- just the arguments to `f vs Ctx vs'` - replacing
305
-- `Ctx` with `HOLE`, to get a `ListCtx Value as (HOLE a)` which then can be used as an input to `propagate`.
306
data HOLE a b where
307
  HOLE :: HOLE a a
308

309
-- | Try to convert a `Term` to a single-hole context - works only if the `Var`
310
-- is the _only_ variable in the term _and_ it appears only once in the `Term`.
311
toCtx ::
312
  forall m v a.
313
  ( Typeable v
314
  , Show v
315
  , MonadGenError m
316
  , HasCallStack
317
  ) =>
318
  Var v ->
319
  Term a ->
320
  m (Ctx v a)
321
toCtx v = go
2✔
322
  where
323
    go :: forall b. Term b -> m (Ctx v b)
324
    go (Lit i) =
2✔
325
      fatalErrorNE $
×
326
        NE.fromList
×
327
          [ "toCtx applied to literal: (Lit " ++ show i ++ ")"
×
328
          , "A context is always constructed from an (App f xs) term."
×
329
          ]
330
    go (App f as) = CtxApp f <$> toCtxList v as
2✔
331
    go (V v')
332
      | Just Refl <- eqVar v v' = pure $ CtxHOLE
2✔
333
      | otherwise =
×
334
          fatalErrorNE $
×
335
            NE.fromList
×
336
              [ "A context is always constructed from an (App f xs) term,"
×
337
              , "with a single occurence of the variable " ++ show v ++ "@(" ++ show (typeOf v) ++ ")"
×
338
              , "Instead we found an unknown variable " ++ show v' ++ "@(" ++ show (typeOf v') ++ ")"
×
339
              ]
340

341
-- | `toCtx` lifted to a `List` of `Term`s
342
toCtxList ::
343
  forall m v as.
344
  (Show v, Typeable v, MonadGenError m, HasCallStack) =>
345
  Var v ->
346
  List Term as ->
347
  m (ListCtx Value as (Ctx v))
348
toCtxList v xs = prefix xs
2✔
349
  where
350
    prefix :: forall as'. HasCallStack => List Term as' -> m (ListCtx Value as' (Ctx v))
351
    prefix Nil = fatalError ("toCtxList without hole, for variable " ++ show v)
1✔
352
    prefix (Lit l :> ts) = do
2✔
353
      ctx <- prefix ts
2✔
354
      pure $ Value l :! ctx
2✔
355
    prefix (t :> ts) = do
2✔
356
      hole <- toCtx v t
2✔
357
      suf <- suffix ts
2✔
358
      pure $ hole :? suf
2✔
359

360
    suffix :: forall as'. List Term as' -> m (List Value as')
361
    suffix Nil = pure Nil
2✔
362
    suffix (Lit l :> ts) = (Value l :>) <$> suffix ts
2✔
363
    suffix (_ :> _) = fatalErrorNE $ NE.fromList ["toCtxList with too many holes, for variable " ++ show v]
×
364

365
-- | A Convenient pattern for singleton contexts
366
pattern Unary :: HOLE a' a -> ListCtx f '[a] (HOLE a')
367
pattern Unary h = NilCtx h
2✔
368

369
{-# COMPLETE Unary #-}
370

371
-- | Convenient patterns for binary contexts (the arrow :<: points towards the hole)
372
pattern (:<:) :: (Typeable b, Show b) => HOLE c a -> b -> ListCtx Value '[a, b] (HOLE c)
373
pattern h :<: a = h :? Value a :> Nil
2✔
374

375
-- | Convenient patterns for binary contexts (the arrow :>: points towards the hole)
376
pattern (:>:) :: (Typeable a, Show a) => a -> HOLE c b -> ListCtx Value '[a, b] (HOLE c)
377
pattern a :>: h = Value a :! NilCtx h
1✔
378

379
{-# COMPLETE (:<:), (:>:) #-}
380

381
-- | Flip a binary context around
382
flipCtx ::
383
  (Typeable a, Show a, Typeable b, Show b) =>
384
  ListCtx Value '[a, b] (HOLE c) -> ListCtx Value '[b, a] (HOLE c)
385
flipCtx (HOLE :<: x) = x :>: HOLE
1✔
386
flipCtx (x :>: HOLE) = HOLE :<: x
2✔
387

388
-- | From a ListCtx, build a (List Term as), to which the function symbol can be applied.
389
fromListCtx :: All HasSpec as => ListCtx Value as (HOLE a) -> Term a -> List Term as
390
fromListCtx ctx t = fillListCtx (mapListCtxC @HasSpec (\(Value a) -> Lit a) ctx) (\HOLE -> t)
2✔
391

392
-- =================================================================
393
-- The class (HasSpec a) tells us what operations type 'a' must
394
-- support to add it to the constraint solver and generator
395
-- Writing HasSpec instances gives the system the power to grow
396
-- Don't be afraid of all the methods. Most have default implementations.
397
-- =================================================================
398

399
-- | A type where the `HasSpec` instance has been instantiated via the `SimpleRep` with
400
-- constraints that give good type errors
401
type GenericallyInstantiated a =
402
  ( AssertComputes
403
      (SimpleRep a)
404
      ( Text "Trying to use a generic instantiation of "
405
          :<>: ShowType a
406
          :<>: Text ", likely in a HasSpec instance."
407
          :$$: Text
408
                 "However, the type has no definition of SimpleRep, likely because of a missing instance of HasSimpleRep."
409
      )
410
  , HasSimpleRep a
411
  , HasSpec (SimpleRep a)
412
  , TypeSpec a ~ TypeSpec (SimpleRep a)
413
  )
414

415
-- | `Eq` and `Show` for `TypeSpec` with additional constraints to ensure good type errors
416
type TypeSpecEqShow a =
417
  ( AssertComputes
418
      (TypeSpec a)
419
      ( Text "Can't compute "
420
          :<>: ShowType (TypeSpec a)
421
          :$$: Text "Either because of a missing definition of TypeSpec or a missing instance of HasSimpleRep."
422
      )
423
  , Show (TypeSpec a)
424
  , Typeable (TypeSpec a)
425
  )
426

427
{- NOTE: type errors in constrained-generators
428
    It's easy to make a mistake like this:
429
      data Bad = Bad | Worse deriving (Eq, Show)
430
      instance HasSpec Bad
431
    Missing that this requires an instance of HasSimpleRep for Bad to work.
432
    The two `AssertComputes` uses above are here to give you better error messages when you make this mistake,
433
    e.g. giving you something like this:
434
      src/Constrained/Examples/Basic.hs:327:10: error: [GHC-64725]
435
          • Can't compute TypeSpec (SimpleRep Bad)
436
            Either because of a missing definition of TypeSpec or a missing instance of HasSimpleRep.
437
          • In the instance declaration for ‘HasSpec Bad’
438
          |
439
      327 | instance HasSpec Bad
440
          |          ^^^^^^^^^^^
441

442
      src/Constrained/Examples/Basic.hs:327:10: error: [GHC-64725]
443
          • Trying to use a generic instantiation of Bad, likely in a HasSpec instance.
444
            However, the type has no definition of SimpleRep, likely because of a missing instance of HasSimpleRep.
445
          • In the expression: Constrained.Base.$dmemptySpec @(Bad)
446
            In an equation for ‘emptySpec’:
447
                emptySpec = Constrained.Base.$dmemptySpec @(Bad)
448
            In the instance declaration for ‘HasSpec Bad’
449
          |
450
      327 | instance HasSpec Bad
451
          |          ^^^^^^^^^^^
452
-}
453

454
-- | Class for talking about types that we can write `Specification`s about
455
class
456
  ( Typeable a
457
  , Eq a
458
  , Show a
459
  , TypeSpecEqShow a
460
  ) =>
461
  HasSpec a
462
  where
463
  -- | The `TypeSpec a` is the type-specific `Specification a`.
464
  type TypeSpec a
465

466
  type TypeSpec a = TypeSpec (SimpleRep a)
467

468
  -- `TypeSpec` behaves sort-of like a monoid with a neutral
469
  -- element `emptySpec` and a `combineSpec` for combining
470
  -- two `TypeSpec a`. However, in order to provide flexibilty
471
  -- `combineSpec` takes two `TypeSpec` and constucts a `Specification`. This
472
  -- avoids e.g. having to have a separate implementation of `ErrorSpec`
473
  -- and `MemberSpec` in `TypeSpec`.
474

475
  -- | Trivial `TypeSpec` that admits anything
476
  emptySpec :: TypeSpec a
477

478
  -- | Conjunction of two `TypeSpec`s
479
  combineSpec :: TypeSpec a -> TypeSpec a -> Specification a
480

481
  -- | Generate a value that satisfies the `TypeSpec`.
482
  -- The key property for this generator is soundness:
483
  --  ∀ a ∈ genFromTypeSpec spec. a `conformsTo` spec
484
  genFromTypeSpec :: (HasCallStack, MonadGenError m) => TypeSpec a -> GenT m a
485

486
  -- | Check conformance to the spec.
487
  conformsTo :: HasCallStack => a -> TypeSpec a -> Bool
488

489
  -- | Shrink an `a` with the aide of a `TypeSpec`
490
  shrinkWithTypeSpec :: TypeSpec a -> a -> [a]
491

492
  -- | Try to make an `a` conform to `TypeSpec` with minimal changes. When
493
  -- `fixupWithSpec ts a` returns `Just a'`, it should be the case that
494
  -- `conformsTo a' ts`. There are no constraints in the `Nothing` case. A
495
  -- non-trivial implementation of this function is important for shrinking.
496
  fixupWithTypeSpec :: TypeSpec a -> a -> Maybe a
497

498
  -- | Convert a spec to predicates:
499
  -- The key property here is:
500
  --   ∀ a. a `conformsTo` spec == a `conformsTo` constrained (\t -> toPreds t spec)
501
  toPreds :: Term a -> TypeSpec a -> Pred
502

503
  -- | Compute an upper and lower bound on the number of solutions genFromTypeSpec might return
504
  cardinalTypeSpec :: TypeSpec a -> Specification Integer
505

506
  -- | A bound on the number of solutions `genFromTypeSpec TrueSpec` can produce.
507
  --   For a type with finite elements, we can get a much more accurate
508
  --   answer than TrueSpec
509
  cardinalTrueSpec :: Specification Integer
510
  cardinalTrueSpec = TrueSpec
2✔
511

512
  -- Each instance can decide if a TypeSpec has an Error, and what String
513
  -- to pass to ErrorSpec to create an ErrorSpec value. Particulary
514
  -- useful for type Sum and Prod. The default instance uses guardTypeSpec,
515
  -- which also has a default value, and if that defualt value is used, typeSpecHasError will
516
  -- return Nothing. Both 'typeSpecHasError' and 'guardTypeSpec' can be set individually.
517
  -- If you're only writing one of these non default values, give it to 'guardTypeSpec'
518
  typeSpecHasError :: TypeSpec a -> Maybe (NE.NonEmpty String)
519
  typeSpecHasError tspec = case guardTypeSpec @a [] tspec of
1✔
520
    ErrorSpec msgs -> Just msgs
1✔
521
    _ -> Nothing
2✔
522

523
  -- Some binary TypeSpecs, which nest to the right
524
  -- e.g. something like this (X a (TypeSpec (X b (TypeSpec (X c w))))))
525
  -- An would look better in Vertical mode as (X [a,b,c] m).
526
  -- This lets each HasSpec instance decide. Particulary useful for type Sum and Prod
527
  alternateShow :: TypeSpec a -> BinaryShow
528
  alternateShow _ = NonBinary
×
529

530
  -- | For some types (especially finite ones) there may be much better ways to construct
531
  --   a Specification than the default method of just adding a large 'bad' list to TypSpec. This
532
  --   function allows each HasSpec instance to decide.
533
  typeSpecOpt :: TypeSpec a -> [a] -> Specification a
534
  typeSpecOpt tySpec bad = TypeSpec tySpec bad
2✔
535

536
  -- | This can be used to detect self inconsistencies in a (TypeSpec t)
537
  --   Note this is similar to 'typeSpecHasError', and the default
538
  --   value for 'typeSpecHasError' is written in terms of 'guardTypeSpec'
539
  --   Both 'typeSpecHasError' and 'guardTypeSpec' can be set individually.
540
  guardTypeSpec :: [String] -> TypeSpec a -> Specification a
541
  guardTypeSpec _ ty = typeSpec ty
1✔
542

543
  -- | Prerequisites for the instance that are sometimes necessary
544
  -- when working with e.g. `Specification`s or functions in the universe.
545
  type Prerequisites a :: Constraint
546

547
  type Prerequisites a = ()
548

549
  -- | Materialize the `Prerequisites` dictionary. It should not be necessary to
550
  -- implement this function manually.
551
  prerequisites :: Evidence (Prerequisites a)
552
  default prerequisites :: Prerequisites a => Evidence (Prerequisites a)
553
  prerequisites = Evidence
2✔
554

555
  {- NOTE: Below follows default implementations for the functions in this
556
     class based on Generics.  They are meant to provide an implementation of
557
     `HasSpec a` when `HasSimpleRep a` and `HasSpec (SimpleRep a)`. For example,
558
     for a newtype wrapper like `newtype Foo = Foo Word64` we can define `SimpleRep
559
     Foo = Word64` with the requisite instance for `HasSimpleRep` (all of which
560
     is derived from `Generic Foo`) and the instance for `HasSpec Foo` is
561
     essentially the same as the instance for `Word64`. This is achieved by
562
     ensuring that `TypeSpec Foo = TypeSpec Word64` (c.f. the default
563
     implementation of `TypeSpec` above). To this end, the implementations
564
     below simply convert the relevant things between `SimpleRep a` and `a`.
565
     For example, in the implementation of `combineSpec s s'` we treat `s` and
566
     `s'` (which have type `TypeSpec a`) as `TypeSpec (SimpleRep a)`,
567
     combine them, and go from the resulting `Specification (SimpleRep a)` to `Specification
568
     a` using `fromSimpleRepSpec`.
569
   -}
570

571
  default emptySpec :: GenericallyInstantiated a => TypeSpec a
572
  emptySpec = emptySpec @(SimpleRep a)
2✔
573

574
  default combineSpec ::
575
    GenericallyInstantiated a =>
576
    TypeSpec a ->
577
    TypeSpec a ->
578
    Specification a
579
  combineSpec s s' = fromSimpleRepSpec $ combineSpec @(SimpleRep a) s s'
2✔
580

581
  default genFromTypeSpec ::
582
    (GenericallyInstantiated a, HasCallStack, MonadGenError m) =>
583
    TypeSpec a ->
584
    GenT m a
585
  genFromTypeSpec s = fromSimpleRep <$> genFromTypeSpec s
2✔
586

587
  default conformsTo ::
588
    (GenericallyInstantiated a, HasCallStack) =>
589
    a ->
590
    TypeSpec a ->
591
    Bool
592
  a `conformsTo` s = conformsTo (toSimpleRep a) s
2✔
593

594
  default toPreds ::
595
    GenericallyInstantiated a =>
596
    Term a ->
597
    TypeSpec a ->
598
    Pred
599
  toPreds v s = toPreds (toGeneric_ v) s
2✔
600

601
  default shrinkWithTypeSpec ::
602
    GenericallyInstantiated a =>
603
    TypeSpec a ->
604
    a ->
605
    [a]
606
  shrinkWithTypeSpec spec a = map fromSimpleRep $ shrinkWithTypeSpec spec (toSimpleRep a)
2✔
607

608
  default fixupWithTypeSpec ::
609
    GenericallyInstantiated a =>
610
    TypeSpec a ->
611
    a ->
612
    Maybe a
613
  fixupWithTypeSpec spec a = fromSimpleRep <$> fixupWithTypeSpec spec (toSimpleRep a)
2✔
614

615
  default cardinalTypeSpec ::
616
    GenericallyInstantiated a =>
617
    TypeSpec a ->
618
    Specification Integer
619
  cardinalTypeSpec = cardinalTypeSpec @(SimpleRep a)
2✔
620

621
------------------------------------------------------------------------
622
-- Some instances of HasSpec
623
------------------------------------------------------------------------
624

625
-- | NOTE: this instance means we have to use `ifElse`, `whenTrue`, and `whenFalse` instead
626
-- of `caseOn` for `Bool`
627
instance HasSpec Bool where
×
628
  type TypeSpec Bool = ()
629
  emptySpec = ()
×
630
  combineSpec _ _ = typeSpec ()
1✔
631
  genFromTypeSpec _ = pureGen arbitrary
2✔
632
  cardinalTypeSpec _ = equalSpec 2
2✔
633
  cardinalTrueSpec = equalSpec 2
2✔
634
  shrinkWithTypeSpec _ = shrink
2✔
635
  fixupWithTypeSpec _ = Just
×
636
  conformsTo _ _ = True
2✔
637
  toPreds _ _ = TruePred
2✔
638
  typeSpecOpt _ [] = TrueSpec
2✔
639
  typeSpecOpt _ (nub -> [b]) = equalSpec (not b)
2✔
NEW
640
  typeSpecOpt _ _ = ErrorSpec $ pure "inconsistent bool spec"
×
641

642
instance HasSpec () where
×
643
  type TypeSpec () = ()
644
  emptySpec = ()
×
645
  combineSpec _ _ = typeSpec ()
×
646
  _ `conformsTo` _ = True
2✔
647
  shrinkWithTypeSpec _ _ = []
2✔
648
  fixupWithTypeSpec _ _ = pure ()
×
649
  genFromTypeSpec _ = pure ()
2✔
650
  toPreds _ _ = TruePred
2✔
651
  cardinalTypeSpec _ = MemberSpec (pure 1)
×
652
  cardinalTrueSpec = equalSpec 1
1✔
653
  typeSpecOpt _ [] = TrueSpec
2✔
654
  typeSpecOpt _ (_ : _) = ErrorSpec (pure "Non null 'cant' set in typeSpecOpt @()")
×
655

656
-- ===================================================================
657
-- toGeneric and fromGeneric as Function Symbols
658
-- That means they can be used inside (Term a)
659
-- ===================================================================
660

661
-- | The things you need to know to work with the generics which translates things
662
-- into their SimpleRep, made of Sum and Prod
663
type GenericRequires a =
664
  ( HasSpec a -- This gives Show, Eq, and Typeable instances
665
  , GenericallyInstantiated a
666
  )
667

668
-- | The constructors of BaseW, are first order data (i.e Function Symbols) that describe functions.
669
-- The Base functions are just the functions neccessary to define Specification, and the classes
670
-- HasSimpleRep, HasSpec, Syntax, Semantics, and Logic. We call BaseW a 'witness type', and use
671
-- the convention that all witness types (and their constructors) have "W" as thrit last character.
672
data BaseW (dom :: [Type]) (rng :: Type) where
673
  ToGenericW :: GenericRequires a => BaseW '[a] (SimpleRep a)
674
  FromGenericW :: GenericRequires a => BaseW '[SimpleRep a] a
675

676
deriving instance Eq (BaseW dom rng)
×
677

678
instance Show (BaseW d r) where
×
679
  show ToGenericW = "toSimpleRep"
2✔
680
  show FromGenericW = "fromSimpleRep"
2✔
681

682
instance Syntax BaseW
×
683

684
instance Semantics BaseW where
685
  semantics FromGenericW = fromSimpleRep
2✔
686
  semantics ToGenericW = toSimpleRep
2✔
687

688
-- -- ============== ToGenericW Logic instance
689

690
instance Logic BaseW where
1✔
691
  propagateTypeSpec ToGenericW (Unary HOLE) s cant = TypeSpec s (fromSimpleRep <$> cant)
2✔
692
  propagateTypeSpec FromGenericW (Unary HOLE) s cant = TypeSpec s (toSimpleRep <$> cant)
2✔
693

694
  propagateMemberSpec ToGenericW (Unary HOLE) es = MemberSpec (fmap fromSimpleRep es)
2✔
695
  propagateMemberSpec FromGenericW (Unary HOLE) es = MemberSpec (fmap toSimpleRep es)
2✔
696

697
  mapTypeSpec ToGenericW ts = typeSpec ts
2✔
698
  mapTypeSpec FromGenericW ts = typeSpec ts
2✔
699

700
  rewriteRules ToGenericW (FromGeneric x :> Nil) Evidence = Just x
2✔
701
  rewriteRules (FromGenericW :: BaseW dom rng) (ToGeneric (x :: Term a) :> Nil) Evidence
702
    | Just Refl <- eqT @rng @a = Just x
2✔
703
  rewriteRules _ _ _ = Nothing
2✔
704

705
-- | Convert an @a@ to a @`SimpleRep` a@
706
toGeneric_ ::
707
  forall a.
708
  GenericRequires a =>
709
  Term a ->
710
  Term (SimpleRep a)
711
toGeneric_ = appTerm ToGenericW
2✔
712

713
-- | Convert an @`SimpleRep` a@ to an @a@
714
fromGeneric_ ::
715
  forall a.
716
  (GenericRequires a, AppRequires BaseW '[SimpleRep a] a) =>
717
  Term (SimpleRep a) ->
718
  Term a
719
fromGeneric_ = appTerm FromGenericW
2✔
720

721
-- ====================================================================
722
-- Generic Transformers
723
-- Using Generics to transform from ordinary (Specifications a) to
724
-- Specifications over 'a's SimpleRep (Specification (SimpleRep a))
725
-- ====================================================================
726

727
-- | Convert a `Specification` for a @`SimpleRep` a@ to one for @a@
728
fromSimpleRepSpec ::
729
  GenericRequires a =>
730
  Specification (SimpleRep a) ->
731
  Specification a
732
fromSimpleRepSpec = \case
2✔
733
  ExplainSpec es s -> explainSpec es (fromSimpleRepSpec s)
×
734
  TrueSpec -> TrueSpec
×
735
  ErrorSpec e -> ErrorSpec e
1✔
736
  TypeSpec s'' cant -> TypeSpec s'' $ map fromSimpleRep cant
1✔
737
  MemberSpec elems -> MemberSpec $ NE.nub (fmap fromSimpleRep elems)
×
738
  SuspendedSpec x p ->
739
    constrained $ \x' ->
×
740
      Let (toGeneric_ x') (x :-> p) :: Pred
×
741

742
-- | Convert a @`Specification` a@ to one for @`SimpleRep` a@
743
toSimpleRepSpec ::
744
  forall a.
745
  GenericRequires a =>
746
  Specification a ->
747
  Specification (SimpleRep a)
748
toSimpleRepSpec = \case
×
749
  ExplainSpec es s -> explainSpec es (toSimpleRepSpec s)
×
750
  TrueSpec -> TrueSpec
×
751
  ErrorSpec e -> ErrorSpec e
×
752
  TypeSpec s'' cant -> TypeSpec s'' $ map toSimpleRep cant
×
753
  MemberSpec elems -> MemberSpec $ NE.nub $ fmap toSimpleRep elems
×
754
  SuspendedSpec x p ->
755
    constrained $ \x' ->
×
756
      Let (fromGeneric_ x') (x :-> p) :: Pred
×
757

758
-- =====================================================================
759
-- Now the supporting operations and types.
760
-- =====================================================================
761

762
-- | Used to show binary operators like SumSpec and PairSpec
763
data BinaryShow where
764
  BinaryShow :: forall a. String -> [Doc a] -> BinaryShow
765
  NonBinary :: BinaryShow
766

767
-- =================================================
768
-- Term
769

770
-- | Like 'appSym' but builds functions over terms, rather that just one App term.
771
appTerm ::
772
  forall t ds r.
773
  AppRequires t ds r =>
774
  t ds r ->
775
  FunTy (MapList Term ds) (Term r)
776
appTerm sym = curryList @ds (App @Deps @t @ds @r sym)
2✔
777

778
-- | Give a `Term` a `String` name-hint _if_ the `Term` is a variable
779
name :: String -> Term a -> Term a
780
name nh (V (Var i _)) = V (Var i nh)
2✔
781
name _ _ = error "applying name to non-var thing! Shame on you!"
×
782

783
-- | Create a `Binder` with a fresh variable, used in e.g. `constrained`
784
bind :: (HasSpec a, IsPred p) => (Term a -> p) -> Binder a
785
bind bodyf = newv :-> bodyPred
2✔
786
  where
787
    bodyPred = toPred body
2✔
788
    newv = Var (nextVar bodyPred) "v"
2✔
789
    body = bodyf (V newv)
2✔
790

791
    nextVar q = 1 + bound q
2✔
792

793
    boundBinder :: Binder a -> Int
794
    boundBinder (x :-> p) = max (nameOf x) (bound p)
2✔
795

796
    bound (Explain _ p) = bound p
2✔
797
    bound (Subst x _ p) = max (nameOf x) (bound p)
2✔
798
    bound (And ps) = maximum $ (-1) : map bound ps -- (-1) as the default to get 0 as `nextVar p`
2✔
799
    bound (Exists _ b) = boundBinder b
2✔
800
    bound (Let _ b) = boundBinder b
2✔
801
    bound (ForAll _ b) = boundBinder b
2✔
802
    bound (Case _ cs) = getMax $ foldMapList (Max . boundBinder . thing) cs
2✔
803
    bound (When _ p) = bound p
2✔
804
    bound Reifies {} = -1
2✔
805
    bound GenHintD {} = -1
2✔
806
    bound Assert {} = -1
2✔
807
    bound DependsOn {} = -1
2✔
808
    bound TruePred = -1
2✔
809
    bound FalsePred {} = -1
2✔
810
    bound Monitor {} = -1
2✔
811
    bound ElemPred {} = -1
2✔
812

813
-- ==================================================
814
-- Pred
815

816
-- | A collection @t@ with elements of type @e@ where the `forAll` syntax will
817
-- work
818
class Forallable t e | t -> e where
819
  -- | Lift the `Specification` for the elements to the collection
820
  fromForAllSpec ::
821
    (HasSpec t, HasSpec e) => Specification e -> Specification t
822
  default fromForAllSpec ::
823
    ( HasSpec e
824
    , Forallable (SimpleRep t) e
825
    , GenericRequires t
826
    ) =>
827
    Specification e ->
828
    Specification t
829
  fromForAllSpec es = fromSimpleRepSpec $ fromForAllSpec @(SimpleRep t) @e es
2✔
830

831
  -- | Get the underlying items in the collection
832
  forAllToList :: t -> [e]
833
  default forAllToList ::
834
    ( HasSimpleRep t
835
    , Forallable (SimpleRep t) e
836
    ) =>
837
    t ->
838
    [e]
839
  forAllToList t = forAllToList (toSimpleRep t)
2✔
840

841
-- ===========================================
842
-- IsPred
843

844
-- | Something from which we can construct a `Pred`, useful for providing
845
-- flexible syntax for `constrained` and friends.
846
class Show p => IsPred p where
847
  -- | Convert to a `Pred`
848
  toPred :: p -> Pred
849

850
instance IsPred Pred where
851
  toPred (Assert (Lit False)) = FalsePred (pure "toPred(Lit False)")
1✔
852
  toPred (Assert (Lit True)) = TruePred
2✔
853
  toPred (Explain xs p) = Explain xs (toPred p)
2✔
854
  toPred (And ps) = And (map toPred ps)
2✔
855
  toPred x = x
2✔
856

857
instance IsPred p => IsPred [p] where
858
  toPred xs = And (map toPred xs)
2✔
859

860
instance IsPred Bool where
861
  toPred True = TruePred
2✔
862
  toPred False = FalsePred (pure "toPred False")
1✔
863

864
instance IsPred (Term Bool) where
865
  toPred (Lit b) = toPred b
1✔
866
  toPred term = Assert term
2✔
867

868
-- ============================================================
869
-- Simple Widely used operations on Specification
870

871
-- | return a MemberSpec or ans ErrorSpec depending on if 'xs' is null or not
872
memberSpec :: Foldable f => f a -> NE.NonEmpty String -> Specification a
873
memberSpec (toList -> xs) messages =
2✔
874
  case NE.nonEmpty xs of
2✔
875
    Nothing -> ErrorSpec messages
1✔
876
    Just ys -> MemberSpec ys
2✔
877

878
-- | Attach an explanation to a specification in order to track issues with satisfiability
879
explainSpec :: [String] -> Specification a -> Specification a
880
explainSpec [] x = x
1✔
881
explainSpec es (ExplainSpec es' spec) = ExplainSpec (es ++ es') spec
1✔
882
explainSpec es spec = ExplainSpec es spec
2✔
883

884
-- | A "discrete" specification satisfied by exactly one element
885
equalSpec :: a -> Specification a
886
equalSpec = MemberSpec . pure
2✔
887

888
-- | Anything but this
889
notEqualSpec :: forall a. HasSpec a => a -> Specification a
890
notEqualSpec = typeSpecOpt (emptySpec @a) . pure
2✔
891

892
-- | Anything but these
893
notMemberSpec :: forall a f. (HasSpec a, Foldable f) => f a -> Specification a
894
notMemberSpec = typeSpecOpt (emptySpec @a) . toList
2✔
895

896
-- | Build a `Specification` using predicates, e.g.
897
-- > constrained $ \ x -> assert $ x `elem_` lit [1..10 :: Int]
898
constrained ::
899
  forall a p.
900
  (IsPred p, HasSpec a) =>
901
  (Term a -> p) ->
902
  Specification a
903
constrained body =
2✔
904
  let x :-> p = bind body
2✔
905
   in SuspendedSpec x p
2✔
906

907
-- | Sound but not complete check for empty `Specification`s
908
isErrorLike :: forall a. Specification a -> Bool
909
isErrorLike (ExplainSpec _ s) = isErrorLike s
2✔
910
isErrorLike ErrorSpec {} = True
2✔
911
isErrorLike (TypeSpec x _) =
912
  case typeSpecHasError @a x of
2✔
913
    Nothing -> False
2✔
914
    Just _ -> True
2✔
915
isErrorLike _ = False
2✔
916

917
-- | Get the error message of an `isErrorLike` `Specification`
918
errorLikeMessage :: forall a. Specification a -> NE.NonEmpty String
919
errorLikeMessage (ErrorSpec es) = es
×
920
errorLikeMessage (TypeSpec x _) =
921
  case typeSpecHasError @a x of
×
922
    Nothing -> pure ("Bad call to errorLikeMessage case 1, not guarded by isErrorLike")
×
923
    Just xs -> xs
×
924
errorLikeMessage _ = pure ("Bad call to errorLikeMessage, case 2, not guarded by isErrorLike")
×
925

926
-- | Add the explanations, if it's an ErrorSpec, else drop them
927
addToErrorSpec :: NE.NonEmpty String -> Specification a -> Specification a
928
addToErrorSpec es (ExplainSpec [] x) = addToErrorSpec es x
1✔
929
addToErrorSpec es (ExplainSpec es2 x) = ExplainSpec es2 (addToErrorSpec es x)
×
930
addToErrorSpec es (ErrorSpec es') = ErrorSpec (es <> es')
1✔
931
addToErrorSpec _ s = s
2✔
932

933
------------------------------------------------------------------------
934
-- Pretty and Show instances
935
------------------------------------------------------------------------
936

937
-- | The Fun type encapuslates a Logic instance and symbol universe type to
938
-- hide everything but the domain and range. This is a way to pass around
939
-- functions without pain. Usefull in the ListFoldy implementaion that deals
940
-- with higher order functions.
941
data Fun dom rng where
942
  Fun ::
943
    forall t dom rng.
944
    AppRequires t dom rng =>
945
    t dom rng ->
946
    Fun dom rng
947

948
instance Show (Fun dom r) where
×
949
  show (Fun (f :: t dom rng)) = "(Fun " ++ show f ++ ")"
2✔
950

951
-- | Apply a single-argument `Fun` to a `Term`
952
appFun :: Fun '[x] b -> Term x -> Term b
953
appFun (Fun f) x = App f (x :> Nil)
2✔
954

955
sameFun :: Fun d1 r1 -> Fun d2 r2 -> Bool
956
sameFun (Fun f) (Fun g) = case cast f of
2✔
957
  Just f' -> f' == g
2✔
958
  Nothing -> False
×
959

960
instance Eq (Fun d r) where
×
961
  (==) = sameFun
2✔
962

963
-- | Pattern-match on an application of `fromGeneric_`, useful for writing
964
-- custom rewrite rules to help the solver
965
pattern FromGeneric ::
966
  forall rng.
967
  () =>
968
  forall a.
969
  (rng ~ a, GenericRequires a, HasSpec a, AppRequires BaseW '[SimpleRep a] rng) =>
970
  Term (SimpleRep a) ->
971
  Term rng
972
pattern FromGeneric x <-
973
  (App (getWitness -> Just FromGenericW) (x :> Nil))
2✔
974

975
-- | Pattern-match on an application of `toGeneric_`, useful for writing custom
976
-- rewrite rules to help the solver
977
pattern ToGeneric ::
978
  forall rng.
979
  () =>
980
  forall a.
981
  (rng ~ SimpleRep a, GenericRequires a, HasSpec a, AppRequires BaseW '[a] rng) =>
982
  Term a ->
983
  Term rng
984
pattern ToGeneric x <- (App (getWitness -> Just ToGenericW) (x :> Nil))
2✔
985

986
-- | Hints are things that only affect generation, and not validation. For instance, parameters to
987
--   control distribution of generated values.
988
class (HasSpec a, Show (Hint a)) => HasGenHint a where
989
  type Hint a
990
  giveHint :: Hint a -> Specification a
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