Skip to content

Commit 32cb074

Browse files
constrained-generators: Improve error messages for functions that do
type-level hacks on lists
1 parent 1563f7e commit 32cb074

File tree

5 files changed

+95
-48
lines changed

5 files changed

+95
-48
lines changed

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

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,6 @@
2222
-- Random Natural, Arbitrary Natural, Uniform Natural
2323
{-# OPTIONS_GHC -Wno-orphans #-}
2424

25-
-- The pattern completeness checker is much weaker before ghc-9.0. Rather than introducing redundant
26-
-- cases and turning off the overlap check in newer ghc versions we disable the check for old
27-
-- versions.
28-
#if __GLASGOW_HASKELL__ < 900
29-
{-# OPTIONS_GHC -Wno-incomplete-patterns #-}
30-
{-# OPTIONS_GHC -Wno-unused-imports #-} -- Somehow in GHC 8.10.7, Natural can only be imported from GHC.Natural
31-
#endif
32-
3325
module Constrained.NumSpec where
3426

3527
import Constrained.AbstractSyntax

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

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,6 @@
1616
{-# LANGUAGE ViewPatterns #-}
1717
{-# OPTIONS_GHC -Wno-orphans #-}
1818

19-
-- The pattern completeness checker is much weaker before ghc-9.0. Rather than introducing redundant
20-
-- cases and turning off the overlap check in newer ghc versions we disable the check for old
21-
-- versions.
22-
#if __GLASGOW_HASKELL__ < 900
23-
{-# OPTIONS_GHC -Wno-incomplete-patterns #-}
24-
#endif
25-
2619
module Constrained.Spec.Map where
2720

2821
import Constrained.AbstractSyntax

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

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,6 @@
2222
{-# LANGUAGE ViewPatterns #-}
2323
{-# OPTIONS_GHC -Wno-orphans -Wno-name-shadowing #-}
2424

25-
-- The pattern completeness checker is much weaker before ghc-9.0. Rather than introducing redundant
26-
-- cases and turning off the overlap check in newer ghc versions we disable the check for old
27-
-- versions.
28-
#if __GLASGOW_HASKELL__ < 900
29-
{-# OPTIONS_GHC -Wno-incomplete-patterns #-}
30-
#endif
31-
3225
module Constrained.Spec.Set where
3326

3427
import Constrained.AbstractSyntax

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

Lines changed: 71 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
{-# LANGUAGE OverloadedStrings #-}
1111
{-# LANGUAGE PartialTypeSignatures #-}
1212
{-# LANGUAGE PatternSynonyms #-}
13+
{-# LANGUAGE PolyKinds #-}
1314
{-# LANGUAGE QuantifiedConstraints #-}
1415
{-# LANGUAGE RankNTypes #-}
1516
{-# LANGUAGE ScopedTypeVariables #-}
@@ -23,15 +24,9 @@
2324
{-# OPTIONS_GHC -Wno-orphans #-}
2425
{-# OPTIONS_GHC -Wno-redundant-constraints #-}
2526

26-
-- The pattern completeness checker is much weaker before ghc-9.0. Rather than introducing redundant
27-
-- cases and turning off the overlap check in newer ghc versions we disable the check for old
28-
-- versions.
29-
#if __GLASGOW_HASKELL__ < 900
30-
{-# OPTIONS_GHC -Wno-incomplete-patterns #-}
31-
#endif
32-
3327
module Constrained.Spec.SumProd (
3428
IsNormalType,
29+
ProdAsListComputes,
3530
caseOn,
3631
branch,
3732
branchW,
@@ -133,6 +128,7 @@ import Constrained.TheKnot (
133128
prodSnd_,
134129
prod_,
135130
)
131+
import Constrained.TypeErrors
136132
import Data.Typeable (Typeable)
137133
import GHC.TypeLits (Symbol)
138134
import GHC.TypeNats
@@ -250,20 +246,43 @@ type family ResultType t where
250246
ResultType (a -> b) = ResultType b
251247
ResultType a = a
252248

253-
type IsNormalType a = (Cases a ~ '[a], Args a ~ '[a], IsProd a, CountCases a ~ 1)
249+
type IsNormalType a =
250+
( AssertComputes
251+
(Cases a)
252+
( Text "Failed to compute Cases in a use of IsNormalType for "
253+
:$$: ShowType a
254+
:<>: Text ", are you missing an IsNormalType constraint?"
255+
)
256+
, Cases a ~ '[a]
257+
, AssertComputes
258+
(Args a)
259+
( Text "Failed to compute Args in a use of IsNormalType for "
260+
:<>: ShowType a
261+
:<>: Text ", are you missing an IsNormalType constraint?"
262+
)
263+
, Args a ~ '[a]
264+
, IsProd a
265+
, CountCases a ~ 1
266+
)
254267

255268
type family Cases t where
256269
Cases (Sum a b) = a : Cases b
257270
Cases a = '[a]
258271

259272
type IsProductType a =
260273
( HasSimpleRep a
274+
, AssertComputes
275+
(Cases (SimpleRep a))
276+
( Text "Failed to compute Cases in a use of IsProductType for "
277+
:$$: ShowType a
278+
:<>: Text ", are you missing an IsProductType constraint?"
279+
)
261280
, Cases (SimpleRep a) ~ '[SimpleRep a]
262281
, SimpleRep a ~ SumOver (Cases (SimpleRep a))
263282
, IsProd (SimpleRep a)
264283
, HasSpec (SimpleRep a)
265284
, TypeSpec a ~ TypeSpec (SimpleRep a)
266-
, All HasSpec (ProductAsList a)
285+
, All HasSpec (Args (SimpleRep a))
267286
)
268287

269288
type ProductAsList a = Args (SimpleRep a)
@@ -472,15 +491,45 @@ branchW ::
472491
branchW w body =
473492
Weighted (Just w) (bind (buildBranch @p body . toArgs @a))
474493

494+
-- | ProdAsListComputes is here to make sure that in situations like this:
495+
--
496+
-- > type family Foobar k
497+
-- >
498+
-- > ex :: HasSpec (Foobar k) => Specification (Int, Foobar k)
499+
-- > ex = constrained $ \ p -> match p $ \ i _ -> (i ==. 10)
500+
--
501+
-- Where you're trying to work with an unevaluated type family in constraints.
502+
-- You get reasonable type errors prompting you to add the @IsNormalType (Foobar k)@ constraint
503+
-- like this:
504+
--
505+
-- > • Type list computation is stuck on
506+
-- > Args (Foobar k)
507+
-- > Have you considered adding an IsNormalType or ProdAsListComputes constraint?
508+
-- > • In the first argument of ‘($)’, namely ‘match p’
509+
-- > In the expression: match p $ \ i _ -> (i ==. 10)
510+
-- > In the second argument of ‘($)’, namely
511+
-- > ‘\ p -> match p $ \ i _ -> (i ==. 10)’
512+
-- > |
513+
-- > 503 | ex = constrained $ \ p -> match p $ \ i _ -> (i ==. 10)
514+
-- > | ^^^^^
515+
--
516+
-- Which should help you come to the conclusion that you need to do something
517+
-- like this for everything to compile:
518+
--
519+
-- > ex :: (HasSpec (Foobar k), IsNormalType (Foobar k)) => Specification (Int, Foobar k)
520+
type ProdAsListComputes a =
521+
AssertSpineComputes
522+
(Text "Have you considered adding an IsNormalType or ProdAsListComputes constraint?")
523+
(ProductAsList a)
524+
475525
match ::
476526
forall p a.
477527
( IsProductType a
478528
, IsPred p
479529
, GenericRequires a
530+
, ProdAsListComputes a
480531
) =>
481-
Term a ->
482-
FunTy (MapList Term (ProductAsList a)) p ->
483-
Pred
532+
Term a -> FunTy (MapList Term (ProductAsList a)) p -> Pred
484533
match p m = caseOn p (branch @p m)
485534

486535
-- NOTE: `ResultType r ~ Term a` is NOT a redundant constraint,
@@ -536,11 +585,13 @@ forAll' ::
536585
, All HasSpec (Args (SimpleRep a))
537586
, IsPred p
538587
, IsProd (SimpleRep a)
588+
, IsProductType a
539589
, HasSpec a
540590
, GenericRequires a
591+
, ProdAsListComputes a
541592
) =>
542593
Term t ->
543-
FunTy (MapList Term (Args (SimpleRep a))) p ->
594+
FunTy (MapList Term (ProductAsList a)) p ->
544595
Pred
545596
forAll' xs f = forAll xs $ \x -> match @p x f
546597

@@ -554,10 +605,12 @@ constrained' ::
554605
, All HasSpec (Args (SimpleRep a))
555606
, IsProd (SimpleRep a)
556607
, HasSpec a
608+
, IsProductType a
557609
, IsPred p
558610
, GenericRequires a
611+
, ProdAsListComputes a
559612
) =>
560-
FunTy (MapList Term (Args (SimpleRep a))) p ->
613+
FunTy (MapList Term (ProductAsList a)) p ->
561614
Specification a
562615
constrained' f = constrained $ \x -> match @p x f
563616

@@ -572,12 +625,15 @@ reify' ::
572625
, IsProd (SimpleRep b)
573626
, HasSpec a
574627
, HasSpec b
628+
, IsProductType b
629+
, IsProd a
575630
, IsPred p
576631
, GenericRequires b
632+
, ProdAsListComputes b
577633
) =>
578634
Term a ->
579635
(a -> b) ->
580-
FunTy (MapList Term (Args (SimpleRep b))) p ->
636+
FunTy (MapList Term (ProductAsList b)) p ->
581637
Pred
582638
reify' a r f = reify a r $ \x -> match @p x f
583639

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

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
{-# OPTIONS_GHC -Wno-unticked-promoted-constructors #-}
99

1010
module Constrained.TypeErrors (
11+
Computes,
1112
AssertComputes,
13+
AssertSpineComputes,
1214
module X,
1315
) where
1416

@@ -35,21 +37,32 @@ import GHC.TypeLits as X
3537
-- force the evaluation of `ty` and consequently doesn't end up with GHC wanting to report
3638
-- that `Computes tyThatDoesntCompute (TE err)` fails and consequently normalizing `TE err`
3739
-- and finally arriving at `TypeError err`.
38-
type family Computes (ty :: Type) (err :: Constraint) :: Constraint where
39-
Computes Dummy _ =
40+
type family Computes (ty :: k0) (err :: Constraint) (a :: k) :: k where
41+
Computes Dummy _ _ =
4042
TypeError
4143
(Text "This shouldn't be reachable because " :<>: ShowType Dummy :<>: Text " shouldn't be exported!")
42-
Computes _ _ = ()
44+
Computes (Dummy : as) _ _ =
45+
TypeError
46+
(Text "This shouldn't be reachable because " :<>: ShowType Dummy :<>: Text " shouldn't be exported!")
47+
Computes _ _ a = a
4348

4449
-- This is intentionally hidden in here to avoid any funny business
4550
data Dummy
4651

47-
-- NOTE: this indirection is necessary only for older versions of GHC where this is
48-
-- "the right" amount of strictness - apparently it's not necessary on newer versions of GHC!
49-
-- The alternative would be to just do `AssertComputes ty em = Computes ty (TypeError em)` below
50-
-- and that works fine on e.g. ghc 9.12 (and other recent versions) but fails on 8.10.7 because
51-
-- GHC is "too eager" to throw the type error.
52-
type family TE em where
53-
TE em = TypeError em
52+
type AssertComputes ty em = Computes ty (TypeError em) (() :: Constraint)
53+
54+
type family AssertSpineComputesF (help :: ErrorMessage) (xs :: [k]) (err :: ()) :: Constraint where
55+
AssertSpineComputesF _ '[] _ = ()
56+
AssertSpineComputesF help (_ : xs) err = AssertSpineComputes help xs
5457

55-
type AssertComputes ty em = Computes ty (TE em)
58+
type AssertSpineComputes help (xs :: [k]) =
59+
AssertSpineComputesF
60+
help
61+
xs
62+
( TypeError
63+
( Text "Type list computation is stuck on "
64+
:$$: Text " "
65+
:<>: ShowType xs
66+
:$$: help
67+
)
68+
)

0 commit comments

Comments
 (0)