Skip to content

Commit 524d715

Browse files
constrained-generators: Make type errors clearer using the csongor
trick
1 parent 2e2d20c commit 524d715

File tree

12 files changed

+159
-54
lines changed

12 files changed

+159
-54
lines changed

libs/cardano-ledger-test/src/Test/Cardano/Ledger/Constrained/Conway/Instances/Ledger.hs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,7 @@ instance
393393
err = error $ "toCompact @" ++ show (typeOf x) ++ " " ++ show x
394394
instance
395395
( Compactible a
396+
, GenericallyInstantiated (CompactForm a)
396397
, Typeable (TypeSpec (SimpleRep a))
397398
, Show (TypeSpec (SimpleRep a))
398399
, HasSpec a
@@ -873,7 +874,13 @@ instance Typeable a => HasSimpleRep (THKD tag Identity a) where
873874
type SimpleRep (THKD tag Identity a) = a
874875
fromSimpleRep = THKD
875876
toSimpleRep (THKD a) = a
876-
instance (IsNormalType a, Typeable tag, HasSpec a) => HasSpec (THKD tag Identity a)
877+
instance
878+
( IsNormalType a
879+
, Typeable tag
880+
, HasSpec a
881+
, GenericallyInstantiated (THKD tag Identity a)
882+
) =>
883+
HasSpec (THKD tag Identity a)
877884

878885
instance HasSimpleRep GovActionPurpose
879886
instance HasSpec GovActionPurpose

libs/constrained-generators/constrained-generators.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ library
4545
Constrained.SumList
4646
Constrained.Syntax
4747
Constrained.TheKnot
48+
Constrained.TypeErrors
4849

4950
hs-source-dirs: src
5051
default-language: Haskell2010

libs/constrained-generators/src/Constrained/API.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
{-# LANGUAGE ViewPatterns #-}
66

77
module Constrained.API (
8+
GenericallyInstantiated,
89
Logic (..),
910
Semantics (..),
1011
Syntax (..),
@@ -144,6 +145,7 @@ where
144145

145146
import Constrained.Base (
146147
Fun (..),
148+
GenericallyInstantiated,
147149
HasSpec (..),
148150
Logic (..),
149151
Pred (..),

libs/constrained-generators/src/Constrained/Base.hs

Lines changed: 69 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
{-# LANGUAGE UndecidableSuperClasses #-}
2525
{-# LANGUAGE ViewPatterns #-}
2626
-- Show Evidence
27-
{-# OPTIONS_GHC -Wno-orphans #-}
27+
{-# OPTIONS_GHC -Wno-orphans -Wno-unticked-promoted-constructors #-}
2828

2929
-- | This module contains the most basic parts the implementation. Essentially
3030
-- everything to define Specification, HasSpec, HasSimpleRep, Term, Pred, and the Syntax,
@@ -71,6 +71,7 @@ import Constrained.List (
7171
pattern ListCtx,
7272
pattern NilCtx,
7373
)
74+
import Constrained.TypeErrors
7475

7576
import Control.Monad.Writer (
7677
Writer,
@@ -383,7 +384,66 @@ typeSpec ts = TypeSpec ts mempty
383384
-- Don't be afraid of all the methods. Most have default implementations.
384385
-- =================================================================
385386

386-
class (Typeable a, Eq a, Show a, Show (TypeSpec a), Typeable (TypeSpec a)) => HasSpec a where
387+
type GenericallyInstantiated a =
388+
( AssertComputes
389+
(SimpleRep a)
390+
( Text "Trying to use a generic instantiation of "
391+
:<>: ShowType a
392+
:<>: Text ", likely in a HasSpec instance."
393+
:$$: Text
394+
"However, the type has no definition of SimpleRep, likely because of a missing instance of HasSimpleRep."
395+
)
396+
, HasSimpleRep a
397+
, HasSpec (SimpleRep a)
398+
, TypeSpec a ~ TypeSpec (SimpleRep a)
399+
)
400+
401+
type TypeSpecEqShow a =
402+
( AssertComputes
403+
(TypeSpec a)
404+
( Text "Can't compute "
405+
:<>: ShowType (TypeSpec a)
406+
:$$: Text "Either because of a missing definition of TypeSpec or a missing instance of HasSimpleRep."
407+
)
408+
, Show (TypeSpec a)
409+
, Typeable (TypeSpec a)
410+
)
411+
412+
{- NOTE: type errors in constrained-generators
413+
It's easy to make a mistake like this:
414+
data Bad = Bad | Worse deriving (Eq, Show)
415+
instance HasSpec Bad
416+
Missing that this requires an instance of HasSimpleRep for Bad to work.
417+
The two `AssertComputes` uses above are here to give you better error messages when you make this mistake,
418+
e.g. giving you something like this:
419+
src/Constrained/Examples/Basic.hs:327:10: error: [GHC-64725]
420+
• Can't compute TypeSpec (SimpleRep Bad)
421+
Either because of a missing definition of TypeSpec or a missing instance of HasSimpleRep.
422+
• In the instance declaration for ‘HasSpec Bad’
423+
|
424+
327 | instance HasSpec Bad
425+
| ^^^^^^^^^^^
426+
427+
src/Constrained/Examples/Basic.hs:327:10: error: [GHC-64725]
428+
• Trying to use a generic instantiation of Bad, likely in a HasSpec instance.
429+
However, the type has no definition of SimpleRep, likely because of a missing instance of HasSimpleRep.
430+
• In the expression: Constrained.Base.$dmemptySpec @(Bad)
431+
In an equation for ‘emptySpec’:
432+
emptySpec = Constrained.Base.$dmemptySpec @(Bad)
433+
In the instance declaration for ‘HasSpec Bad’
434+
|
435+
327 | instance HasSpec Bad
436+
| ^^^^^^^^^^^
437+
-}
438+
439+
class
440+
( Typeable a
441+
, Eq a
442+
, Show a
443+
, TypeSpecEqShow a
444+
) =>
445+
HasSpec a
446+
where
387447
-- | The `TypeSpec a` is the type-specific `Specification a`.
388448
type TypeSpec a
389449

@@ -489,63 +549,45 @@ class (Typeable a, Eq a, Show a, Show (TypeSpec a), Typeable (TypeSpec a)) => Ha
489549
a` using `fromSimpleRepSpec`.
490550
-}
491551

492-
default emptySpec ::
493-
(HasSpec (SimpleRep a), TypeSpec a ~ TypeSpec (SimpleRep a)) => TypeSpec a
552+
default emptySpec :: GenericallyInstantiated a => TypeSpec a
494553
emptySpec = emptySpec @(SimpleRep a)
495554

496555
default combineSpec ::
497-
( HasSimpleRep a
498-
, HasSpec (SimpleRep a)
499-
, TypeSpec a ~ TypeSpec (SimpleRep a)
500-
) =>
556+
GenericallyInstantiated a =>
501557
TypeSpec a ->
502558
TypeSpec a ->
503559
Specification a
504560
combineSpec s s' = fromSimpleRepSpec $ combineSpec @(SimpleRep a) s s'
505561

506562
default genFromTypeSpec ::
507-
( HasSimpleRep a
508-
, HasSpec (SimpleRep a)
509-
, TypeSpec a ~ TypeSpec (SimpleRep a)
510-
) =>
511-
(HasCallStack, MonadGenError m) =>
563+
(GenericallyInstantiated a, HasCallStack, MonadGenError m) =>
512564
TypeSpec a ->
513565
GenT m a
514566
genFromTypeSpec s = fromSimpleRep <$> genFromTypeSpec s
515567

516568
default conformsTo ::
517-
( HasSimpleRep a
518-
, HasSpec (SimpleRep a)
519-
, TypeSpec a ~ TypeSpec (SimpleRep a)
520-
) =>
521-
HasCallStack =>
569+
(GenericallyInstantiated a, HasCallStack) =>
522570
a ->
523571
TypeSpec a ->
524572
Bool
525573
a `conformsTo` s = conformsTo (toSimpleRep a) s
526574

527575
default toPreds ::
528-
( HasSpec (SimpleRep a)
529-
, TypeSpec a ~ TypeSpec (SimpleRep a)
530-
, HasSimpleRep a
531-
) =>
576+
GenericallyInstantiated a =>
532577
Term a ->
533578
TypeSpec a ->
534579
Pred
535580
toPreds v s = toPreds (toGeneric_ v) s
536581

537582
default shrinkWithTypeSpec ::
538-
( HasSpec (SimpleRep a)
539-
, TypeSpec a ~ TypeSpec (SimpleRep a)
540-
, HasSimpleRep a
541-
) =>
583+
GenericallyInstantiated a =>
542584
TypeSpec a ->
543585
a ->
544586
[a]
545587
shrinkWithTypeSpec spec a = map fromSimpleRep $ shrinkWithTypeSpec spec (toSimpleRep a)
546588

547589
default cardinalTypeSpec ::
548-
(HasSpec (SimpleRep a), TypeSpec a ~ TypeSpec (SimpleRep a)) =>
590+
GenericallyInstantiated a =>
549591
TypeSpec a ->
550592
Specification Integer
551593
cardinalTypeSpec = cardinalTypeSpec @(SimpleRep a)

libs/constrained-generators/src/Constrained/NumSpec.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
{-# LANGUAGE TypeFamilies #-}
1818
{-# LANGUAGE TypeOperators #-}
1919
{-# LANGUAGE UndecidableInstances #-}
20+
{-# LANGUAGE UndecidableSuperClasses #-}
2021
{-# LANGUAGE ViewPatterns #-}
2122
-- Random Natural, Arbitrary Natural, Uniform Natural
2223
{-# OPTIONS_GHC -Wno-orphans #-}

libs/constrained-generators/src/Constrained/Properties.hs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,9 @@ prop_conformEmpty ::
162162
prop_conformEmpty a = QC.property $ conformsTo a (emptySpec @a)
163163

164164
prop_univSound :: TestableFn -> QC.Property
165-
prop_univSound (TestableFn fn) =
165+
prop_univSound (TestableFn (fn :: t as b)) =
166166
QC.label (show fn) $
167-
QC.forAllShrinkBlind QC.arbitrary QC.shrink $ \tc@(TestableCtx ctx) ->
167+
QC.forAllShrinkBlind @QC.Property (QC.arbitrary @(TestableCtx as)) QC.shrink $ \tc@(TestableCtx ctx) ->
168168
QC.forAllShrinkBlind QC.arbitrary QC.shrink $ \spec ->
169169
QC.counterexample ("\nfn ctx = " ++ showCtxWith fn tc) $
170170
QC.counterexample (show $ "\nspec =" <+> pretty spec) $
@@ -221,7 +221,7 @@ showCtxWith fn (TestableCtx ctx) = show tm
221221
tm :: Term b
222222
tm =
223223
uncurryList (appTerm fn) $
224-
fillListCtx (mapListCtxC @HasSpec (lit @_ . unValue) ctx) (\HOLE -> V $ Var 0 "v")
224+
fillListCtx (mapListCtxC @HasSpec @_ @Value @Term (lit @_ . unValue) ctx) (\HOLE -> V $ Var 0 "v")
225225

226226
data TestableFn where
227227
TestableFn ::

libs/constrained-generators/src/Constrained/Spec/SumProd.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
{-# LANGUAGE TypeFamilies #-}
1919
{-# LANGUAGE TypeOperators #-}
2020
{-# LANGUAGE UndecidableInstances #-}
21+
{-# LANGUAGE UndecidableSuperClasses #-}
2122
{-# LANGUAGE ViewPatterns #-}
2223
{-# OPTIONS_GHC -Wno-orphans #-}
2324
{-# OPTIONS_GHC -Wno-redundant-constraints #-}

libs/constrained-generators/src/Constrained/SumList.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
{-# LANGUAGE ScopedTypeVariables #-}
1010
{-# LANGUAGE TypeApplications #-}
1111
{-# LANGUAGE TypeOperators #-}
12+
{-# LANGUAGE UndecidableSuperClasses #-}
1213
{-# LANGUAGE ViewPatterns #-}
1314

1415
-- | Operations for generating random elements of Num like types, that sum to a particular total.

libs/constrained-generators/src/Constrained/Syntax.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,7 @@ runCaseOn s ((x :-> ps) :> bs@(_ :> _)) f = case s of
601601
letSubexpressionElimination :: HasSpec Bool => Pred -> Pred
602602
letSubexpressionElimination = go []
603603
where
604+
adjustSub :: HasSpec a => Var a -> Subst -> Subst
604605
adjustSub x sub =
605606
[ x' := t
606607
| x' := t <- sub
@@ -663,6 +664,7 @@ letFloating = fold . go []
663664
where
664665
goBlock ctx ps = goBlock' (freeVarNames ctx <> freeVarNames ps) ctx ps
665666

667+
goBlock' :: Set Int -> [Pred] -> [Pred] -> [Pred]
666668
goBlock' _ ctx [] = ctx
667669
goBlock' fvs ctx (Let t (x :-> p) : ps) =
668670
-- We can do `goBlock'` here because we've already done let floating

libs/constrained-generators/src/Constrained/TheKnot.hs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
{-# LANGUAGE TypeFamilies #-}
2828
{-# LANGUAGE TypeOperators #-}
2929
{-# LANGUAGE UndecidableInstances #-}
30+
{-# LANGUAGE UndecidableSuperClasses #-}
3031
{-# LANGUAGE ViewPatterns #-}
3132
{-# OPTIONS_GHC -Wno-orphans -Wno-name-shadowing #-}
3233

@@ -779,6 +780,7 @@ aggressiveInlining pred
779780

780781
underBinder fvs x p = fvs `without` [Name x] <> singleton (Name x) (countOf (Name x) p)
781782

783+
underBinderSub :: HasSpec a => Subst -> Var a -> Subst
782784
underBinderSub sub x =
783785
[ x' := t
784786
| x' := t <- sub
@@ -1294,6 +1296,7 @@ shrinkFromPreds p
12941296
shrinkEnvFromPlan :: Env -> SolverPlan -> [Env]
12951297
shrinkEnvFromPlan initialEnv SolverPlan {..} = go mempty solverPlan
12961298
where
1299+
go :: Env -> [SolverStage] -> [Env]
12971300
go _ [] = [] -- In this case we decided to keep every variable the same so nothing to return
12981301
go env ((substStage env -> SolverStage {..}) : plan) = do
12991302
Just a <- [lookupEnv initialEnv stageVar]
@@ -1308,6 +1311,7 @@ shrinkEnvFromPlan initialEnv SolverPlan {..} = go mempty solverPlan
13081311
++ go (extendEnv stageVar a env) plan
13091312

13101313
-- Fix the rest of the plan given an environment `env` for the plan so far
1314+
fixupPlan :: Env -> [SolverStage] -> Maybe Env
13111315
fixupPlan env [] = pure env
13121316
fixupPlan env ((substStage env -> SolverStage {..}) : plan) =
13131317
case lookupEnv initialEnv stageVar >>= fixupWithSpec stageSpec of
@@ -1534,6 +1538,7 @@ backPropagation :: SolverPlan -> SolverPlan
15341538
-- backPropagation (SolverPlan _plan _graph) =
15351539
backPropagation (SolverPlan initplan graph) = SolverPlan (go [] (reverse initplan)) graph
15361540
where
1541+
go :: [SolverStage] -> [SolverStage] -> [SolverStage]
15371542
go acc [] = acc
15381543
go acc (s@(SolverStage (x :: Var a) ps spec) : plan) = go (s : acc) plan'
15391544
where

0 commit comments

Comments
 (0)