grisette-0.13.0.1: src/Grisette/Internal/TH/Util.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
{-# HLINT ignore "Unused LANGUAGE pragma" #-}
-- |
-- Module : Grisette.Internal.TH.Util
-- Copyright : (c) Sirui Lu 2024
-- License : BSD-3-Clause (see the LICENSE file)
--
-- Maintainer : siruilu@cs.washington.edu
-- Stability : Experimental
-- Portability : GHC only
module Grisette.Internal.TH.Util
( constructorInfoToType,
tvIsMode,
tvIsNat,
tvIsStar,
tvIsStarToStar,
substDataType,
reifyDatatypeWithFreshNames,
singleParamClassParamKind,
binaryClassParamKind,
getTypeWithMaybeSubst,
dropLastTypeParam,
dropNTypeParam,
classParamKinds,
allSameKind,
classNumParam,
kindNumParam,
concatPreds,
putHaddock,
allUsedNamesMaybe,
allUsedNames,
isNonUnitTupleString,
isNonUnitTuple,
integerE,
mangleName,
dataTypeHasExistential,
)
where
#if MIN_VERSION_template_haskell(2,18,0)
import Language.Haskell.TH.Syntax
( DocLoc (DeclDoc),
ModName (ModName),
Name (Name),
NameFlavour (NameG, NameQ, NameS),
addModFinalizer,
putDoc,
)
#else
import Language.Haskell.TH.Syntax
( ModName (ModName),
Name (Name),
NameFlavour (NameG, NameQ, NameS),
)
#endif
import Control.Monad (when)
import Data.Char (isAlphaNum, ord)
import qualified Data.Map as M
import qualified Data.Set as S
import GHC.TypeNats (Nat)
import Grisette.Internal.Unified.EvalModeTag (EvalModeTag)
import Language.Haskell.TH
( Dec (ClassD),
Exp
( AppE,
AppTypeE,
ConE,
CondE,
InfixE,
LamE,
ListE,
LitE,
ParensE,
SigE,
TupE,
UInfixE,
VarE
),
Info (ClassI),
Kind,
Pred,
Q,
Type (AppT, ArrowT, ConT, ForallT, StarT, VarT),
integerL,
litE,
nameBase,
newName,
pprint,
reify,
varT,
)
import Language.Haskell.TH.Datatype
( ConstructorInfo (constructorContext, constructorFields, constructorVars),
DatatypeInfo (datatypeCons, datatypeInstTypes, datatypeVars),
TypeSubstitution (applySubstitution),
datatypeType,
reifyDatatype,
tvName,
)
import Language.Haskell.TH.Datatype.TyVarBndr
( Specificity (SpecifiedSpec),
TyVarBndrUnit,
TyVarBndr_,
mapTVFlag,
mapTVName,
tvKind,
)
-- | Convert a 'ConstructorInfo' to a 'Type' of the constructor.
constructorInfoToType :: DatatypeInfo -> ConstructorInfo -> Q Type
constructorInfoToType dataType info = do
let binders =
mapTVFlag (const SpecifiedSpec)
<$> datatypeVars dataType ++ constructorVars info
let ctx = constructorContext info
let fields = constructorFields info
let tyBody =
foldr (AppT . AppT ArrowT) (datatypeType dataType) fields
if null binders then return tyBody else return $ ForallT binders ctx tyBody
-- | Check if a type variable is of kind 'EvalModeTag'.
tvIsMode :: TyVarBndr_ flag -> Bool
tvIsMode = (== ConT ''EvalModeTag) . tvKind
-- | Check if a type variable is of kind 'Nat'.
tvIsNat :: TyVarBndr_ flag -> Bool
tvIsNat = (== ConT ''Nat) . tvKind
-- | Check if a type variable is of kind 'Data.Kind.Type'.
tvIsStar :: TyVarBndr_ flag -> Bool
tvIsStar = (== StarT) . tvKind
-- | Check if a type variable is of kind 'Data.Kind.Type -> Data.Kind.Type'.
tvIsStarToStar :: TyVarBndr_ flag -> Bool
tvIsStarToStar = (== (AppT (AppT ArrowT StarT) StarT)) . tvKind
-- | Substitute the type variables in a 'DatatypeInfo' with the given
-- substitution map.
substDataType :: DatatypeInfo -> M.Map Name Type -> DatatypeInfo
substDataType d substMap =
d
{ datatypeInstTypes = applySubstitution substMap <$> datatypeInstTypes d,
datatypeCons = applySubstitution substMap <$> datatypeCons d
}
-- | Convert a 'DatatypeInfo' to a 'DatatypeInfo' with fresh type variable
-- names.
datatypeToFreshNames :: DatatypeInfo -> Q DatatypeInfo
datatypeToFreshNames d = do
let vars = datatypeVars d
let names = tvName <$> vars
freshNames <- traverse (newName . show) names
let newDTVars = zipWith (\v n -> mapTVName (const n) v) vars freshNames
let substMap = M.fromList $ zip names (VarT <$> freshNames)
return $ substDataType d {datatypeVars = newDTVars} substMap
-- | Reify a datatype with fresh type variable names.
reifyDatatypeWithFreshNames :: Name -> Q DatatypeInfo
reifyDatatypeWithFreshNames name = do
d <- reifyDatatype name
datatypeToFreshNames d
-- | Check if all type variables have the same kind.
allSameKind :: [TyVarBndrUnit] -> Bool
allSameKind [] = True
allSameKind (x : xs) = all ((== tvKind x) . tvKind) xs
-- | Get the kinds of the type parameters of a class.
classParamKinds :: Name -> Q [Kind]
classParamKinds className = do
cls <- reify className
case cls of
ClassI (ClassD _ _ bndrs _ _) _ -> return $ tvKind <$> bndrs
_ ->
fail $
"symmetricClassParamKind:" <> show className <> " is not a class"
-- | Get the number of type parameters of a class.
classNumParam :: Name -> Q Int
classNumParam className = do
cls <- reify className
case cls of
ClassI (ClassD _ _ bndrs _ _) _ -> return $ length bndrs
_ ->
fail $
"classNumParam:" <> show className <> " is not a class"
-- | Get the kind of the single type parameter of a class.
singleParamClassParamKind :: Name -> Q Kind
singleParamClassParamKind className = do
cls <- reify className
case cls of
ClassI (ClassD _ _ bndrs _ _) _ ->
case bndrs of
[x] -> return $ tvKind x
_ ->
fail $
"singleParamClassParamKind: only support classes with one type "
<> "parameter, but "
<> show className
<> " has "
<> show (length bndrs)
_ ->
fail $
"singleParamClassParamKind:" <> show className <> " is not a class"
-- | Get the kind of the binary type parameter of a class.
binaryClassParamKind :: Name -> Q Kind
binaryClassParamKind className = do
cls <- reify className
case cls of
ClassI (ClassD _ _ bndrs _ _) _ ->
case bndrs of
[x, y] -> do
when (tvKind x /= tvKind y) $
fail "binaryClassParamKind: type parameters have different kinds"
return $ tvKind x
_ ->
fail $
"binaryClassParamKind: only support classes with two type "
<> "parameters, but "
<> show className
<> " has "
<> show (length bndrs)
_ ->
fail $
"binaryClassParamKind:" <> show className <> " is not a class"
-- | Get a type with a possible substitution.
getTypeWithMaybeSubst :: TyVarBndrUnit -> Maybe Type -> Q Type
getTypeWithMaybeSubst tv Nothing = varT $ tvName tv
getTypeWithMaybeSubst _ (Just t) = return t
-- | Drop the last instantiated type parameter of a type.
dropLastTypeParam :: Type -> Q Type
dropLastTypeParam (AppT c _) = return c
dropLastTypeParam v =
fail $
"dropLastTypeParam: have no type parameters: "
<> pprint v
<> " / "
<> show v
-- | Drop the last N instantiated type parameters of a type.
dropNTypeParam :: Int -> Type -> Q Type
dropNTypeParam 0 t = return t
dropNTypeParam n t = dropLastTypeParam t >>= dropNTypeParam (n - 1)
-- | Get the number of type parameters of a kind.
kindNumParam :: Kind -> Q Int
kindNumParam (AppT (AppT ArrowT _) k) = (1 +) <$> kindNumParam k
kindNumParam _ = return 0
-- | Concatenate two 'Maybe [Pred]'.
concatPreds :: Maybe [Pred] -> Maybe [Pred] -> Maybe [Pred]
concatPreds Nothing Nothing = Nothing
concatPreds (Just ps) Nothing = Just ps
concatPreds Nothing (Just ps) = Just ps
concatPreds (Just ps1) (Just ps2) = Just $ ps1 ++ ps2
#if MIN_VERSION_template_haskell(2,18,0)
-- | Put a haddock comment on a declaration.
putHaddock :: Name -> String -> Q ()
putHaddock name = addModFinalizer . putDoc (DeclDoc name)
#else
-- | Put a haddock comment on a declaration.
-- (No-op because compiling with GHC < 9.2)
putHaddock :: Name -> String -> Q ()
putHaddock _ _ = return ()
#endif
-- | Get the names used in an expression.
allUsedNamesMaybe :: Maybe Exp -> S.Set Name
allUsedNamesMaybe Nothing = S.empty
allUsedNamesMaybe (Just exp) = allUsedNames exp
-- | Get the names used in an expression.
allUsedNames :: Exp -> S.Set Name
allUsedNames (VarE nm) = S.singleton nm
allUsedNames (ConE n) = S.singleton n
allUsedNames (LitE _) = S.empty
allUsedNames (AppE e1 e2) = allUsedNames e1 `S.union` allUsedNames e2
allUsedNames (AppTypeE e1 _) = allUsedNames e1
allUsedNames (InfixE l e r) =
allUsedNamesMaybe l `S.union` allUsedNames e `S.union` allUsedNamesMaybe r
allUsedNames (UInfixE l e r) =
allUsedNames l `S.union` allUsedNames e `S.union` allUsedNames r
allUsedNames (ParensE e) = allUsedNames e
allUsedNames (LamE _ e) = allUsedNames e
allUsedNames (TupE es) = mconcat $ allUsedNamesMaybe <$> es
allUsedNames (CondE e1 e2 e3) =
allUsedNames e1 `S.union` allUsedNames e2 `S.union` allUsedNames e3
allUsedNames (ListE es) = mconcat $ allUsedNames <$> es
allUsedNames (SigE e _) = allUsedNames e
allUsedNames exp = error $ "allUsedNames: unsupported expression: " <> show exp
-- | Check if a string is the data constructor name of a non-unit tuple.
isNonUnitTupleString :: String -> Bool
isNonUnitTupleString ('(' : ',' : _) = True
isNonUnitTupleString _ = False
-- | Check if a name is the data constructor name of a non-unit tuple.
isNonUnitTuple :: Name -> Bool
isNonUnitTuple nm =
isNonUnitTupleString $ nameBase nm
-- | Convert an integer to an 'Exp'.
integerE :: (Integral a) => a -> Q Exp
integerE = litE . integerL . fromIntegral
-- | Mangle a name string to contain only alphanumeric characters and
-- underscores.
mangleName :: Name -> String
mangleName nm@(Name _ flavor) =
case flavor of
NameS -> mangleBaseName $ nameBase nm
NameQ mod -> mangleModName mod <> "_" <> mangleBaseName (nameBase nm)
NameG _ _ mod -> mangleModName mod <> "_" <> mangleBaseName (nameBase nm)
_ -> error $ "mangleName: unsupported name flavor: " <> show flavor
where
mangleModName (ModName m) = mangleBaseName m
mangleBaseName l = "Mangled" ++ go l
go [] = []
go (c : cs)
| isAlphaNum c || c == '_' = c : go cs
| otherwise = "_" <> show (ord c) <> go cs
-- | Check if a data type has existential variables in constructors.
dataTypeHasExistential :: Name -> Q Bool
dataTypeHasExistential typName = do
d <- reifyDatatype typName
return $ not $ all (null . constructorVars) $ datatypeCons d