@@ -32,11 +32,14 @@ module Data.Generics.Product.Subtype
32 Subtype (..)
32 Subtype (..)
35+import Data.Generics.Internal.Families
35import Data.Generics.Internal.Lens
36import Data.Generics.Internal.Lens
36import Data.Generics.Internal.Void
37import Data.Generics.Internal.Void
37import Data.Generics.Product.Internal.Subtype
38import Data.Generics.Product.Internal.Subtype
39import GHC.Generics (Generic (Rep, to, from) )
40import GHC.Generics (Generic (Rep, to, from) )
41+import GHC.TypeLits (Symbol, TypeError, ErrorMessage (..))
42+import Data.Kind (Type, Constraint)
42-- == /Running example:/
45-- == /Running example:/
@@ -86,6 +89,12 @@ class Subtype sup sub where
86 -- >>> upcast human :: Animal
89 -- >>> upcast human :: Animal
87 -- Animal {name = "Tunyasz", age = 50}
90 -- Animal {name = "Tunyasz", age = 50}
92+ -- >>> upcast (upcast human :: Animal) :: Human
94+ -- ... The type 'Human' is not a subtype of 'Animal'.
95+ -- ... The following fields are missing from 'Human':
89 upcast :: sub -> sup
98 upcast :: sub -> sup
90 upcast s = s ^. super @sup
99 upcast s = s ^. super @sup
@@ -98,12 +107,12 @@ class Subtype sup sub where
99 {-# MINIMAL super | smash, upcast #-}
108 {-# MINIMAL super | smash, upcast #-}
101−-- TODO: improve type error by showing a diff of fields
103 ( Generic a
111 ( Generic a
104 , Generic b
112 , Generic b
105 , GSmash (Rep a) (Rep b)
113 , GSmash (Rep a) (Rep b)
106 , GUpcast (Rep a) (Rep b)
114 , GUpcast (Rep a) (Rep b)
115+ , ErrorUnless b a (CollectFieldsOrdered (Rep b) \\ CollectFieldsOrdered (Rep a))
107 ) => Subtype b a where
116 ) => Subtype b a where
108 smash p b = to $ gsmash (from p) (from b)
117 smash p b = to $ gsmash (from p) (from b)
109 upcast = to . gupcast . from
118 upcast = to . gupcast . from
@@ -113,3 +122,18 @@ instance {-# OVERLAPPING #-} Subtype a Void where
113 super = undefined
122 super = undefined
114instance {-# OVERLAPPING #-} Subtype Void a where
123instance {-# OVERLAPPING #-} Subtype Void a where
115 super = undefined
124 super = undefined
126+type family ErrorUnless (sub :: Type) (sup :: Type) (diff :: [Symbol]) :: Constraint where
130+ ErrorUnless sub sup fs
132+ ( 'Text "The type '"
134+ ':<>: 'Text "' is not a subtype of '"
135+ ':<>: 'ShowType sup ':<>: 'Text "'."
136+ ':$$: 'Text "The following fields are missing from '"
137+ ':<>: 'ShowType sub ':<>: 'Text "':"
138+ ':$$: ShowSymbols fs