recover-rtti-0.1.0.0: src/Debug/RecoverRTTI/Constr.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -Wno-redundant-constraints #-}
-- | Type-level metadata
module Debug.RecoverRTTI.Constr (
Constr(..)
, KnownConstr
, knownConstr
, prettyKnownConstr
, elimKnownConstr
-- | Compute all known constructors
, Constrs
, GConstrs
, GConstrsOfType
, ConstrOf
, IsConstrOf(..)
, checkIsConstrOf
, Sing(..)
) where
import Data.Kind
import Data.List (intercalate)
import Data.SOP
import Data.Type.Equality
import GHC.Generics
import GHC.TypeLits
import Debug.RecoverRTTI.Util
import Debug.RecoverRTTI.Util.TypeLevel
{-------------------------------------------------------------------------------
Type-level metadata
-------------------------------------------------------------------------------}
data Constr a = Constr {
constrPkg :: a
, constrModl :: a
, constrName :: a
}
deriving (Show, Eq)
type family ConstrPkg (c :: Constr a) :: a where ConstrPkg ('Constr p _ _) = p
type family ConstrModl (c :: Constr a) :: a where ConstrModl ('Constr _ m _) = m
type family ConstrName (c :: Constr a) :: a where ConstrName ('Constr _ _ c) = c
class (
KnownSymbol (ConstrPkg c)
, KnownSymbol (ConstrModl c)
, KnownSymbol (ConstrName c)
, c ~ 'Constr (ConstrPkg c) (ConstrModl c) (ConstrName c)
) => KnownConstr c
instance (
KnownSymbol (ConstrPkg c)
, KnownSymbol (ConstrModl c)
, KnownSymbol (ConstrName c)
, c ~ 'Constr (ConstrPkg c) (ConstrModl c) (ConstrName c)
) => KnownConstr c
knownConstr ::
forall c. Sing (c :: Constr Symbol) -> Constr String
knownConstr SConstr = Constr {
constrPkg = symbolVal (Proxy @(ConstrPkg c))
, constrModl = symbolVal (Proxy @(ConstrModl c))
, constrName = symbolVal (Proxy @(ConstrName c))
}
prettyKnownConstr :: Sing (c :: Constr Symbol) -> String
prettyKnownConstr s = intercalate "." [constrPkg, constrModl, constrName]
where
Constr{constrPkg, constrModl, constrName} = knownConstr s
elimKnownConstr :: forall r.
Constr String
-> (forall c. Sing (c :: Constr Symbol) -> r)
-> r
elimKnownConstr Constr{constrPkg, constrModl, constrName} k =
elimKnownSymbol constrPkg $ \pPkg ->
elimKnownSymbol constrModl $ \pModl ->
elimKnownSymbol constrName $ \pName ->
go pPkg pModl pName
where
go :: forall pkg modl constr. (
KnownSymbol pkg
, KnownSymbol modl
, KnownSymbol constr
)
=> Proxy pkg -> Proxy modl -> Proxy constr -> r
go _ _ _ = k (sing :: Sing ('Constr pkg modl constr))
{-------------------------------------------------------------------------------
Singleton
-------------------------------------------------------------------------------}
data instance Sing (c :: Constr Symbol) where
SConstr :: KnownConstr c => Sing c
instance KnownConstr c => SingI (c :: Constr Symbol) where
sing = SConstr
instance Show (Sing (c :: Constr Symbol)) where
showsPrec p proxy = showParen (p >= 11) $
showString "SConstr "
. showsPrec 11 (knownConstr proxy)
instance DecidableEquality (Constr Symbol) where
decideEquality :: forall c c'.
Sing (c :: Constr Symbol)
-> Sing (c' :: Constr Symbol)
-> Maybe (c :~: c')
decideEquality SConstr SConstr =
case ( decideEquality (sing :: Sing (ConstrPkg c)) (sing :: Sing (ConstrPkg c'))
, decideEquality (sing :: Sing (ConstrModl c)) (sing :: Sing (ConstrModl c'))
, decideEquality (sing :: Sing (ConstrName c)) (sing :: Sing (ConstrName c'))
) of
(Just Refl, Just Refl, Just Refl) ->
Just Refl
_otherwise ->
Nothing
{-------------------------------------------------------------------------------
Reverse direction: from type to the known constructors
-------------------------------------------------------------------------------}
-- | Compute all constructors of the given type
type family Constrs (a :: Type) :: [Constr Symbol] where
Constrs a = GConstrs (Rep a)
type family GConstrs f :: [Constr Symbol] where
GConstrs (M1 D ('MetaData typ modl pkg isNewtype) f) =
GConstrsOfType pkg modl f '[]
type family GConstrsOfType pkg modl f acc :: [Constr Symbol] where
GConstrsOfType pkg modl (f :+: g) acc =
GConstrsOfType pkg modl g (GConstrsOfType pkg modl f acc)
GConstrsOfType pkg modl (M1 C ('MetaCons constr fixity isRecord) f) acc =
'Constr pkg modl constr ': acc
-- | Require that specified type has the given constructor
--
-- Intended usage:
--
-- > castUserDefined :: forall c a. ConstrOf a c => UserDefined c -> a
-- > castUserDefined = unsafeCoerce
type family ConstrOf (a :: Type) (c :: Constr Symbol) :: Constraint where
ConstrOf a c =
Assert
(Elem c (Constrs a))
(TypeError (
( 'ShowType c
':<>: 'Text " is not a valid constructor of "
':<>: 'ShowType a
)
':$$: ( 'Text "Valid constructors are: "
':<>: 'ShowType (Constrs a)
)
))
data IsConstrOf (a :: Type) (c :: Constr Symbol) where
IsConstrOf :: Elem c (Constrs a) ~ 'True => IsConstrOf a c
checkIsConstrOf :: forall (a :: Type) (c :: Constr Symbol).
SingI (Constrs a)
=> Sing c -> Maybe (IsConstrOf a c)
checkIsConstrOf s =
aux <$> checkIsElem s (sing @_ @(Constrs a))
where
aux :: IsElem c (Constrs a) -> IsConstrOf a c
aux IsElem = IsConstrOf