large-records-0.1.0.0: src/Data/Record/Internal/TH/Name.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE PartialTypeSignatures #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeApplications #-}
{-# OPTIONS_GHC -Wno-partial-type-signatures #-}
-- | Names with statically known flavour
--
-- Intended for qualified import.
module Data.Record.Internal.TH.Name (
-- * Names
Name(..)
, Flavour(..)
, NameFlavour(..)
-- * Simple functions
, nameBase
, mapNameBase
-- * Working with qualified names
, Qualifier(..)
, qualify
, unqualified
, nameQualifier
-- * Fresh names
, newName
-- * Conversion
, fromTH
, fromTH'
, toTH
-- * Resolution
, LookupName(..)
, reify
-- * Construct TH
, classD
, conE
, conT
, newtypeD
, patSynD
, patSynSigD
, pragCompleteD
, recC
, recordPatSyn
, sigD
, varBangType
, varE
, varLocalP
, varGlobalP
) where
import Data.Kind
import Data.Maybe (fromMaybe)
import Language.Haskell.TH (Q)
import Language.Haskell.TH.Syntax (Quasi, runQ, NameSpace(..))
import qualified Language.Haskell.TH.Syntax as TH
import qualified Language.Haskell.TH.Lib as TH
{-------------------------------------------------------------------------------
Names
-------------------------------------------------------------------------------}
-- | Name flavours (used as a kind, not as a type)
--
-- Technically speaking there is one flavour missing: names that are locally
-- bound, but outside of the TH quote, something like
--
-- > foo x = [| .. x .. |]
--
-- However, we won't actually deal with such names.
data Flavour =
-- | Dynamically bound
--
-- Dynamically bound names will be bound to a global name by @ghc@ after
-- splicing the TH generated Haskelll code.
--
-- These are generated with 'mkName' (also used by @haskell-src-meta@).
Dynamic
-- | A new name
--
-- These are names either generated by 'newName' or are new names in a TH
-- declaration quote @[d| ... |]@.
| Unique
-- | Reference to a specific name defined outside of the TH quote
| Global
data NameFlavour :: Flavour -> Type where
-- | Dynamically bound name, with an optional module prefix (@T.foo@)
NameDynamic :: Maybe TH.ModName -> NameFlavour 'Dynamic
-- | Unique local name
NameUnique :: TH.Uniq -> NameFlavour 'Unique
-- | Global name bound outside of the TH quot
NameGlobal :: TH.NameSpace -> TH.PkgName -> TH.ModName -> NameFlavour 'Global
-- | Like TH's 'Name', but with statically known flavour.
data Name :: NameSpace -> Flavour -> Type where
Name :: TH.OccName -> NameFlavour flavour -> Name ns flavour
deriving instance Show (NameFlavour flavour)
deriving instance Eq (NameFlavour flavour)
deriving instance Ord (NameFlavour flavour)
deriving instance Show (Name ns flavour)
deriving instance Eq (Name ns flavour)
deriving instance Ord (Name ns flavour)
{-------------------------------------------------------------------------------
Simple functions
-------------------------------------------------------------------------------}
nameBase :: Name ns flavour -> String
nameBase (Name (TH.OccName occ) _) = occ
-- | Modify the unqualified part of the name
--
-- Since we often to do this derive one kind of name from another, the
-- namespace of the result is not related to the namespace of the argument.
mapNameBase :: (String -> String) -> Name ns flavour -> Name ns' flavour
mapNameBase f (Name (TH.OccName occ) flav) = Name (TH.OccName (f occ)) flav
{-------------------------------------------------------------------------------
Working with qualified names
-------------------------------------------------------------------------------}
data Qualifier = Unqual | Qual TH.ModName
qualify :: Qualifier -> String -> Name ns 'Dynamic
qualify Unqual occ = Name (TH.OccName occ) (NameDynamic Nothing)
qualify (Qual m) occ = Name (TH.OccName occ) (NameDynamic (Just m))
unqualified :: String -> Name ns 'Dynamic
unqualified = qualify Unqual
nameQualifier :: Name ns 'Dynamic -> Qualifier
nameQualifier (Name _ (NameDynamic (Just m))) = Qual m
nameQualifier (Name _ (NameDynamic Nothing)) = Unqual
{-------------------------------------------------------------------------------
Singleton
-------------------------------------------------------------------------------}
-- | Singleton type associated with 'Flavour'
data SFlavour :: Flavour -> Type where
SDynamic :: SFlavour 'Dynamic
SUnique :: SFlavour 'Unique
SGlobal :: SFlavour 'Global
deriving instance Show (SFlavour flavour)
class IsFlavour flavour where
isFlavour :: SFlavour flavour
instance IsFlavour 'Dynamic where isFlavour = SDynamic
instance IsFlavour 'Unique where isFlavour = SUnique
instance IsFlavour 'Global where isFlavour = SGlobal
{-------------------------------------------------------------------------------
Conversion
-------------------------------------------------------------------------------}
toFlavourF :: SFlavour flavour -> TH.NameFlavour -> Maybe (NameFlavour flavour)
toFlavourF SDynamic (TH.NameS) = Just $ NameDynamic Nothing
toFlavourF SDynamic (TH.NameQ m) = Just $ NameDynamic (Just m)
toFlavourF SUnique (TH.NameU u) = Just $ NameUnique u
toFlavourF SGlobal (TH.NameG n p m) = Just $ NameGlobal n p m
toFlavourF _ _ = Nothing
fromFlavourF :: NameFlavour flavour -> TH.NameFlavour
fromFlavourF (NameDynamic Nothing) = TH.NameS
fromFlavourF (NameDynamic (Just m)) = TH.NameQ m
fromFlavourF (NameUnique u) = TH.NameU u
fromFlavourF (NameGlobal n p m) = TH.NameG n p m
-- | Translate from a dynamically typed TH name
--
-- Returns 'Nothing' if the TH name does not have the specified flavour.
fromTH :: IsFlavour flavour => TH.Name -> Maybe (Name ns flavour)
fromTH (TH.Name occ flavour') = Name occ <$> toFlavourF isFlavour flavour'
-- | Variation on 'fromTH' that throws an exception on a flavour mismatch
fromTH' :: forall ns flavour. IsFlavour flavour => TH.Name -> Name ns flavour
fromTH' name@(TH.Name occ flavour') =
fromMaybe (error err) $ fromTH name
where
err :: String
err = concat [
"fromTH': name "
, show occ
, " has the wrong flavour: "
, show (isFlavour :: SFlavour flavour)
, " /= "
, show flavour'
]
-- | Forget type level information
toTH :: Name ns flavour -> TH.Name
toTH (Name occ flavour) = TH.Name occ (fromFlavourF flavour)
{-------------------------------------------------------------------------------
Resolution
-------------------------------------------------------------------------------}
class LookupName ns where
-- | Resolve existing name
lookupName :: Quasi m => Name ns 'Dynamic -> m (Maybe (Name ns 'Global))
instance LookupName 'TcClsName where
lookupName (Name occ (NameDynamic mMod)) =
fmap fromTH' <$>
runQ (TH.lookupTypeName $ qualifyDotted mMod occ)
instance LookupName 'DataName where
lookupName (Name occ (NameDynamic mMod)) =
fmap fromTH' <$>
runQ (TH.lookupValueName $ qualifyDotted mMod occ)
instance LookupName 'VarName where
lookupName (Name occ (NameDynamic mMod)) =
fmap fromTH' <$>
runQ (TH.lookupValueName $ qualifyDotted mMod occ)
-- | Get info about the given name
--
-- Only global names can be reified. See 'lookupName'.
reify :: Quasi m => Name ns 'Global -> m TH.Info
reify = runQ . TH.reify . toTH
{-------------------------------------------------------------------------------
Fresh names
-------------------------------------------------------------------------------}
newName :: String -> Q (Name ns 'Unique)
newName = fmap fromTH' . TH.newName
{-------------------------------------------------------------------------------
/Defining/ global names
Since these are all meant to define capturable names, these functions all take
an 'Dynamic' name as argument.
-------------------------------------------------------------------------------}
-- | Define pattern synonym
patSynD :: Name 'DataName 'Dynamic -> _
patSynD = TH.patSynD . toTH
-- | Define pattern synonym signature
patSynSigD :: Name 'DataName 'Dynamic -> _
patSynSigD = TH.patSynSigD . toTH
-- | Define function signature
sigD :: Name 'VarName 'Dynamic -> _
sigD = TH.sigD . toTH
-- | Define record field signature
varBangType :: Name 'VarName 'Dynamic -> _
varBangType = TH.varBangType . toTH
-- | Define record constructor
recC :: Name 'DataName 'Dynamic -> _
recC = TH.recC . toTH
-- | Define class
classD :: _ -> Name 'TcClsName 'Dynamic -> _
classD cxt = TH.classD cxt . toTH
-- | Define newtype
newtypeD :: _ -> Name 'TcClsName 'Dynamic -> _
newtypeD cxt = TH.newtypeD cxt . toTH
-- | Define record pattern synonym
recordPatSyn :: [String] -> _
recordPatSyn = TH.recordPatSyn . map (toTH . unqualified)
-- | Define COMPLETE pragma
pragCompleteD :: [Name 'DataName 'Dynamic] -> Maybe (Name 'TcClsName 'Dynamic) -> _
pragCompleteD constrs typ =
TH.pragCompleteD (toTH <$> constrs) (toTH <$> typ)
-- | Define pattern variable for use in a record pattern synonym
varGlobalP :: Name 'VarName 'Dynamic -> _
varGlobalP = TH.varP . toTH
-- | Define pattern variable for use in a local pattern match
varLocalP :: Name 'VarName 'Unique -> _
varLocalP = TH.varP . toTH
{-------------------------------------------------------------------------------
Referencing existing names
We can reference any flavour of name.
-------------------------------------------------------------------------------}
-- | Reference constructor
conE :: Name 'DataName flavour -> _
conE = TH.conE . toTH
-- | Reference type
conT :: Name 'TcClsName flavour -> _
conT = TH.conT . toTH
-- | Reference variable
varE :: Name 'VarName flavour -> _
varE = TH.varE . toTH
{-------------------------------------------------------------------------------
Internal auxiliary
-------------------------------------------------------------------------------}
-- | Qualify a name (for use in 'lookupTypeName' and co)
qualifyDotted :: Maybe TH.ModName -> TH.OccName -> String
qualifyDotted Nothing (TH.OccName occ) = occ
qualifyDotted (Just (TH.ModName m)) (TH.OccName occ) = m ++ "." ++ occ