ema-0.8.0.0: src/Ema/Route/Generic/RGeneric.hs
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE UndecidableSuperClasses #-}
module Ema.Route.Generic.RGeneric (
RGeneric (RCode, rfrom, rto),
RHasDatatypeInfo (RDatatypeName, RConstructorNames),
) where
import GHC.TypeLits (ErrorMessage (Text), TypeError)
import GHC.TypeLits.Extra (Impossible (impossible), TypeErr)
import Generics.SOP
import Generics.SOP.Type.Metadata qualified as SOPM
import Prelude hiding (All, Generic)
-- | Like `Generic` but for Route types only.
class (Generic r, HasDatatypeInfo r) => RGeneric r where
type RCode r :: [Type]
rfrom :: r -> NS I (RCode r)
rto :: NS I (RCode r) -> r
class (RGeneric r, HasDatatypeInfo r) => RHasDatatypeInfo r where
type RDatatypeName r :: SOPM.DatatypeName
type RConstructorNames r :: [SOPM.ConstructorName]
instance (Generic r, HasDatatypeInfo r, RGeneric' (Code r), All RouteNP (Code r)) => RGeneric r where
type RCode r = RCode' (Code r)
rfrom = rfrom' @(Code r) . unSOP . from
rto = to . SOP . rto' @(Code r)
instance (RGeneric r, HasDatatypeInfo r) => RHasDatatypeInfo r where
type RDatatypeName r = RDatatypeName' (DatatypeInfoOf r)
type RConstructorNames r = RConstructorNames' (DatatypeInfoOf r)
class RGeneric' (xss :: [[Type]]) where
type RCode' xss :: [Type]
rfrom' :: NS (NP I) xss -> NS I (RCode' xss)
rto' :: NS I (RCode' xss) -> NS (NP I) xss
instance RGeneric' '[] where
type RCode' '[] = '[]
rfrom' = \case {}
rto' = \case {}
instance (RGeneric' xss, RouteNP xs) => RGeneric' (xs ': xss) where
type RCode' (xs ': xss) = RouteNPType xs ': RCode' xss
rfrom' = \case
Z routeNP -> Z $ I $ fromRouteNP routeNP
S rest -> S $ rfrom' @xss rest
rto' = \case
Z (I t) -> Z $ toRouteNP t
S rest -> S $ rto' @xss rest
type family RDatatypeName' (info :: SOPM.DatatypeInfo) :: SOPM.DatatypeName where
RDatatypeName' ( 'SOPM.ADT _ name _ _) =
name
RDatatypeName' ( 'SOPM.Newtype _ name _) =
name
type family RConstructorNames' (info :: SOPM.DatatypeInfo) :: [SOPM.ConstructorName] where
RConstructorNames' ( 'SOPM.ADT _ _ constrs _) =
GetConstructorNames constrs
RConstructorNames' ( 'SOPM.Newtype _ _ constr) =
'[GetConstructorName constr]
type family GetConstructorName (info :: SOPM.ConstructorInfo) :: SOPM.ConstructorName where
GetConstructorName ( 'SOPM.Constructor name) =
name
GetConstructorName ( 'SOPM.Infix name _ _) =
name
GetConstructorName ( 'SOPM.Record name _) =
name
type family GetConstructorNames (infos :: [SOPM.ConstructorInfo]) :: [SOPM.ConstructorName] where
GetConstructorNames '[] = '[]
GetConstructorNames (x ': xs) = GetConstructorName x ': GetConstructorNames xs
{- | Class of `NP` that can be used in a route tyupe.
Ensures that the constructor can have 0 or 1 products only.
-}
class RouteNP (xs :: [Type]) where
type RouteNPType xs :: Type
fromRouteNP :: NP I xs -> RouteNPType xs
toRouteNP :: RouteNPType xs -> NP I xs
instance RouteNP '[] where
type RouteNPType '[] = ()
fromRouteNP Nil = ()
toRouteNP () = Nil
instance RouteNP (x ': '[]) where
type RouteNPType (x ': '[]) = x
fromRouteNP (I x :* Nil) = x
toRouteNP x = I x :* Nil
instance (TypeErr ( 'Text "MultiRoute: too many arguments")) => RouteNP (x ': x' ': xs) where
type RouteNPType (x ': x' ': xs) = TypeError ( 'Text "MultiRoute: too many arguments")
fromRouteNP _ = impossible
toRouteNP _ = impossible