mockcat-0.5.2.0: src/Test/MockCat/TH.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -Wno-name-shadowing #-}
{-# OPTIONS_GHC -Wno-unused-local-binds #-}
module Test.MockCat.TH
( showExp,
expectByExpr,
makeMock,
makeMockWithOptions,
MockOptions (..),
options,
makePartialMock,
makePartialMockWithOptions,
)
where
import Control.Monad (guard, unless)
import Control.Monad.State (get, modify)
import Control.Monad.Trans.Class (lift)
import Data.Data (Proxy (..))
import Data.Function ((&))
import Data.List (elemIndex, find, nub)
import Data.Maybe (fromMaybe, isJust)
import Data.Text (pack, splitOn, unpack)
import GHC.IO (unsafePerformIO)
import GHC.TypeLits (KnownSymbol, symbolVal)
import Language.Haskell.TH
( Cxt,
Dec (..),
Exp (..),
Extension (..),
Info (..),
Lit (..),
Name,
Pat (..),
Pred,
Q,
Quote (newName),
TyVarBndr (..),
Type (..),
isExtEnabled,
mkName,
pprint,
reify,
Inline (NoInline),
RuleMatch (FunLike),
Phases (AllPhases),
)
import Language.Haskell.TH.Lib
import Language.Haskell.TH.PprLib (Doc, hcat, parens, text)
import Language.Haskell.TH.Syntax (nameBase)
import Test.MockCat.Cons
import Test.MockCat.Mock
import Test.MockCat.MockT
import Test.MockCat.Param
import Unsafe.Coerce (unsafeCoerce)
import Prelude as P
showExp :: Q Exp -> Q String
showExp qexp = show . pprintExp <$> qexp
pprintExp :: Exp -> Doc
pprintExp (VarE name) = text (nameBase name)
pprintExp (ConE name) = text (nameBase name)
pprintExp (LitE lit) = pprintLit lit
pprintExp (AppE e1 e2) = parens $ hcat [pprintExp e1, text " ", pprintExp e2]
pprintExp (InfixE e1 e2 e3) = pprintInfixE e1 e2 e3
pprintExp (LamE pats body) = parens $ hcat [text "\\", pprintPats pats, text " -> ", pprintExp body]
pprintExp (TupE exps) = parens $ hcat (map (maybe (text "") pprintExp) exps)
pprintExp (ListE exps) = parens $ hcat (map pprintExp exps)
pprintExp (SigE e _) = pprintExp e
pprintExp x = text (pprint x)
pprintInfixE :: Maybe Exp -> Exp -> Maybe Exp -> Doc
pprintInfixE e1 e2 e3 =
parens $
hcat
[ maybe (text "") pprintExp e1,
maybe (text "") (const (text " ")) e1,
pprintExp e2,
text " ",
maybe (text "") pprintExp e3
]
pprintPats :: [Pat] -> Doc
pprintPats = hcat . map pprintPat
pprintPat :: Pat -> Doc
pprintPat (VarP name) = text (nameBase name)
pprintPat p = text (pprint p)
pprintLit :: Lit -> Doc
pprintLit (IntegerL n) = text (show n)
pprintLit (RationalL r) = text (show r)
pprintLit (StringL s) = text (show s)
pprintLit (CharL c) = text (show c)
pprintLit l = text (pprint l)
-- | Create a conditional parameter based on @Q Exp@.
--
-- In applying a mock function, if the argument does not satisfy this condition, an error is raised.
--
-- The conditional expression is displayed in the error message.
expectByExpr :: Q Exp -> Q Exp
expectByExpr qf = do
str <- showExp qf
[|ExpectCondition $qf str|]
data MockType = Total | Partial deriving (Eq)
-- | Options for generating mocks.
--
-- - prefix: Stub function prefix
-- - suffix: stub function suffix
-- - implicitMonadicReturn: If True, the return value of the stub function is wrapped in a monad automatically.
-- If Else, the return value of stub function is not wrapped in a monad, so required explicitly return monadic values.
data MockOptions = MockOptions {prefix :: String, suffix :: String, implicitMonadicReturn :: Bool}
-- | Default Options.
--
-- Stub function names are prefixed with “_”.
options :: MockOptions
options = MockOptions {prefix = "_", suffix = "", implicitMonadicReturn = True}
-- | Create a mock of the typeclasses that returns a monad according to the `MockOptions`.
--
-- Given a monad type class, generate the following.
--
-- - MockT instance of the given typeclass
-- - A stub function corresponding to a function of the original class type.
-- The name of stub function is the name of the original function with a “_” appended.
--
-- @
-- class (Monad m) => FileOperation m where
-- writeFile :: FilePath -\> Text -\> m ()
-- readFile :: FilePath -\> m Text
--
-- makeMockWithOptions [t|FileOperation|] options { prefix = "stub_" }
--
-- it "test runMockT" do
-- result \<- runMockT do
-- stub_readFile $ "input.txt" |\> pack "content"
-- stub_writeFile $ "output.text" |\> pack "content" |\> ()
-- somethingProgram
--
-- result `shouldBe` ()
-- @
makeMockWithOptions :: Q Type -> MockOptions -> Q [Dec]
makeMockWithOptions = flip doMakeMock Total
-- | Create a mock of a typeclasses that returns a monad.
--
-- Given a monad type class, generate the following.
--
-- - MockT instance of the given typeclass
-- - A stub function corresponding to a function of the original class type.
-- The name of stub function is the name of the original function with a “_” appended.
--
-- The prefix can be changed.
-- In that case, use `makeMockWithOptions`.
--
-- @
-- class (Monad m) => FileOperation m where
-- writeFile :: FilePath -\> Text -\> m ()
-- readFile :: FilePath -\> m Text
--
-- makeMock [t|FileOperation|]
--
-- spec :: Spec
-- spec = do
-- it "test runMockT" do
-- result \<- runMockT do
-- _readFile $ "input.txt" |\> pack "content"
-- _writeFile $ "output.text" |\> pack "content" |\> ()
-- somethingProgram
--
-- result `shouldBe` ()
-- @
makeMock :: Q Type -> Q [Dec]
makeMock t = doMakeMock t Total options
-- | Create a partial mock of a typeclasses that returns a monad.
--
-- Given a monad type class, generate the following.
--
-- - MockT instance of the given typeclass
-- - A stub function corresponding to a function of the original class type.
-- The name of stub function is the name of the original function with a “_” appended.
--
-- For functions that are not stubbed in the test, the real function is used as appropriate for the context.
--
-- The prefix can be changed.
-- In that case, use `makePartialMockWithOptions`.
--
-- @
-- class Monad m => Finder a b m | a -> b, b -> a where
-- findIds :: m [a]
-- findById :: a -> m b
--
-- instance Finder Int String IO where
-- findIds = pure [1, 2, 3]
-- findById id = pure $ "{id: " <> show id <> "}"
--
-- findValue :: Finder a b m => m [b]
-- findValue = do
-- ids <- findIds
-- mapM findById ids
--
-- makePartialMock [t|Finder|]
--
-- spec :: Spec
-- spec = do
-- it "Use all real functions." do
-- values <- runMockT findValue
-- values `shouldBe` ["{id: 1}", "{id: 2}", "{id: 3}"]
--
-- it "Only findIds should be stubbed." do
-- values <- runMockT do
-- _findIds [1 :: Int, 2]
-- findValue
-- values `shouldBe` ["{id: 1}", "{id: 2}"]
-- @
makePartialMock :: Q Type -> Q [Dec]
makePartialMock t = doMakeMock t Partial options
-- | `makePartialMock` with options
makePartialMockWithOptions :: Q Type -> MockOptions -> Q [Dec]
makePartialMockWithOptions = flip doMakeMock Partial
doMakeMock :: Q Type -> MockType -> MockOptions -> Q [Dec]
doMakeMock t mockType options = do
verifyExtension DataKinds
verifyExtension FlexibleInstances
verifyExtension FlexibleContexts
ty <- t
className <- getClassName <$> t
reify className >>= \case
ClassI (ClassD _ _ [] _ _) _ ->
fail $ "A type parameter is required for class " <> show className
ClassI (ClassD cxt _ typeVars _ decs) _ -> do
monadVarNames <- getMonadVarNames cxt typeVars
case nub monadVarNames of
[] -> fail "Monad parameter not found."
(monadVarName : rest)
| length rest > 1 -> fail "Monad parameter must be unique."
| otherwise -> makeMockDecs ty mockType className monadVarName cxt typeVars decs options
t -> error $ "unsupported type: " <> show t
makeMockDecs :: Type -> MockType -> Name -> Name -> Cxt -> [TyVarBndr a] -> [Dec] -> MockOptions -> Q [Dec]
makeMockDecs ty mockType className monadVarName cxt typeVars decs options = do
let classParamNames = filter (className /=) (getClassNames ty)
newTypeVars = drop (length classParamNames) typeVars
varAppliedTypes = zipWith (\t i -> VarAppliedType t (safeIndex classParamNames i)) (getTypeVarNames typeVars) [0 ..]
instanceDec <-
instanceD
(createCxt cxt mockType className monadVarName newTypeVars varAppliedTypes)
(createInstanceType ty monadVarName newTypeVars)
(map (createInstanceFnDec mockType options) decs)
mockFnDecs <- concat <$> mapM (createMockFnDec monadVarName varAppliedTypes options) decs
pure $ instanceDec : mockFnDecs
getMonadVarNames :: Cxt -> [TyVarBndr a] -> Q [Name]
getMonadVarNames cxt typeVars = do
let parentClassInfos = toClassInfos cxt
typeVarNames = getTypeVarNames typeVars
-- VarInfos (class names is empty)
emptyClassVarInfos = map (`VarName2ClassNames` []) typeVarNames
varInfos <- collectVarInfos parentClassInfos emptyClassVarInfos
pure $ (\(VarName2ClassNames n _) -> n) <$> filterMonadicVarInfos varInfos
getTypeVarNames :: [TyVarBndr a] -> [Name]
getTypeVarNames = map getTypeVarName
getTypeVarName :: TyVarBndr a -> Name
getTypeVarName (PlainTV name _) = name
getTypeVarName (KindedTV name _ _) = name
toClassInfos :: Cxt -> [ClassName2VarNames]
toClassInfos = map toClassInfo
toClassInfo :: Pred -> ClassName2VarNames
toClassInfo (AppT t1 t2) = do
let (ClassName2VarNames name vars) = toClassInfo t1
ClassName2VarNames name (vars ++ getTypeNames t2)
toClassInfo (ConT name) = ClassName2VarNames name []
toClassInfo _ = error "Unsupported Type structure"
getTypeNames :: Pred -> [Name]
getTypeNames (VarT name) = [name]
getTypeNames (ConT name) = [name]
getTypeNames _ = []
collectVarInfos :: [ClassName2VarNames] -> [VarName2ClassNames] -> Q [VarName2ClassNames]
collectVarInfos classInfos = mapM (collectVarInfo classInfos)
collectVarInfo :: [ClassName2VarNames] -> VarName2ClassNames -> Q VarName2ClassNames
collectVarInfo classInfos (VarName2ClassNames vName classNames) = do
varClassNames <- collectVarClassNames vName classInfos
pure $ VarName2ClassNames vName (classNames ++ varClassNames)
collectVarClassNames :: Name -> [ClassName2VarNames] -> Q [Name]
collectVarClassNames varName classInfos = do
let targetClassInfos = filterClassInfo varName classInfos
concat <$> mapM (collectVarClassNames_ varName) targetClassInfos
collectVarClassNames_ :: Name -> ClassName2VarNames -> Q [Name]
collectVarClassNames_ name (ClassName2VarNames cName vNames) = do
case elemIndex name vNames of
Nothing -> pure []
Just i -> do
ClassI (ClassD cxt _ typeVars _ _) _ <- reify cName
let -- type variable names
typeVarNames = getTypeVarNames typeVars
-- type variable name of same position
typeVarName = typeVarNames !! i
-- parent class information
parentClassInfos = toClassInfos cxt
case parentClassInfos of
[] -> pure [cName]
_ -> do
result <- concat <$> mapM (collectVarClassNames_ typeVarName) parentClassInfos
pure $ cName : result
filterClassInfo :: Name -> [ClassName2VarNames] -> [ClassName2VarNames]
filterClassInfo name = filter (hasVarName name)
where
hasVarName :: Name -> ClassName2VarNames -> Bool
hasVarName name (ClassName2VarNames _ varNames) = name `elem` varNames
filterMonadicVarInfos :: [VarName2ClassNames] -> [VarName2ClassNames]
filterMonadicVarInfos = filter hasMonadInVarInfo
hasMonadInVarInfo :: VarName2ClassNames -> Bool
hasMonadInVarInfo (VarName2ClassNames _ classNames) = ''Monad `elem` classNames
createCxt :: [Pred] -> MockType -> Name -> Name -> [TyVarBndr a] -> [VarAppliedType] -> Q [Pred]
createCxt cxt mockType className monadVarName tyVars varAppliedTypes = do
newCxt <- mapM (createPred monadVarName) cxt
monadAppT <- appT (conT ''Monad) (varT monadVarName)
let hasMonad = P.any (\(ClassName2VarNames c _) -> c == ''Monad) $ toClassInfos newCxt
pure $ case mockType of
Total -> newCxt ++ ([monadAppT | not hasMonad])
Partial -> do
let classAppT = constructClassAppT className $ toVarTs tyVars
varAppliedClassAppT = updateType classAppT varAppliedTypes
newCxt ++ ([monadAppT | not hasMonad]) ++ [varAppliedClassAppT]
toVarTs :: [TyVarBndr a] -> [Type]
toVarTs tyVars = VarT <$> getTypeVarNames tyVars
constructClassAppT :: Name -> [Type] -> Type
constructClassAppT className = foldl AppT (ConT className)
createPred :: Name -> Pred -> Q Pred
createPred monadVarName a@(AppT t@(ConT ty) b@(VarT varName))
| monadVarName == varName && ty == ''Monad = pure a
| monadVarName == varName && ty /= ''Monad = appT (pure t) (appT (conT ''MockT) (varT varName))
| otherwise = appT (createPred monadVarName t) (pure b)
createPred monadVarName (AppT ty a@(VarT varName))
| monadVarName == varName = appT (pure ty) (appT (conT ''MockT) (varT varName))
| otherwise = appT (createPred monadVarName ty) (pure a)
createPred monadVarName (AppT ty1 ty2) = appT (createPred monadVarName ty1) (createPred monadVarName ty2)
createPred _ ty = pure ty
createInstanceType :: Type -> Name -> [TyVarBndr a] -> Q Type
createInstanceType className monadName tvbs = do
types <- mapM (tyVarBndrToType monadName) tvbs
pure $ foldl AppT className types
tyVarBndrToType :: Name -> TyVarBndr a -> Q Type
tyVarBndrToType monadName (PlainTV name _)
| monadName == name = appT (conT ''MockT) (varT monadName)
| otherwise = varT name
tyVarBndrToType monadName (KindedTV name _ _)
| monadName == name = appT (conT ''MockT) (varT monadName)
| otherwise = varT name
createInstanceFnDec :: MockType -> MockOptions -> Dec -> Q Dec
createInstanceFnDec mockType options (SigD fnName funType) = do
names <- sequence $ typeToNames funType
let r = mkName "result"
params = varP <$> names
args = varE <$> names
fnNameStr = createFnName fnName options
fnBody = case mockType of
Total -> generateInstanceMockFnBody fnNameStr args r options
Partial -> generateInstanceRealFnBody fnName fnNameStr args r options
fnClause = clause params (normalB fnBody) []
funD fnName [fnClause]
createInstanceFnDec _ _ dec = fail $ "unsuported dec: " <> pprint dec
generateInstanceMockFnBody :: String -> [Q Exp] -> Name -> MockOptions -> Q Exp
generateInstanceMockFnBody fnNameStr args r options = do
returnExp <- if options.implicitMonadicReturn
then [| pure $(varE r) |]
else [| lift $(varE r) |]
[|
MockT $ do
defs <- get
let mock =
defs
& findParam (Proxy :: Proxy $(litT (strTyLit fnNameStr)))
& fromMaybe (error $ "no answer found stub function `" ++ fnNameStr ++ "`.")
$(bangP $ varP r) = $(generateStubFn args [|mock|])
$(pure returnExp)
|]
generateInstanceRealFnBody :: Name -> String -> [Q Exp] -> Name -> MockOptions -> Q Exp
generateInstanceRealFnBody fnName fnNameStr args r options = do
returnExp <- if options.implicitMonadicReturn
then [| pure $(varE r) |]
else [| lift $(varE r) |]
[|
MockT $ do
defs <- get
case findParam (Proxy :: Proxy $(litT (strTyLit fnNameStr))) defs of
Just mock -> do
let $(bangP $ varP r) = $(generateStubFn args [|mock|])
$(pure returnExp)
Nothing -> lift $ $(foldl appE (varE fnName) args)
|]
generateStubFn :: [Q Exp] -> Q Exp -> Q Exp
generateStubFn [] = $([|generateConstantStubFn|])
generateStubFn args = $([|generateNotConstantsStubFn args|])
generateNotConstantsStubFn :: [Q Exp] -> Q Exp -> Q Exp
generateNotConstantsStubFn args mock = foldl appE [|stubFn $(mock)|] args
generateConstantStubFn :: Q Exp -> Q Exp
generateConstantStubFn mock = [|stubFn $(mock)|]
createMockFnDec :: Name -> [VarAppliedType] -> MockOptions -> Dec -> Q [Dec]
createMockFnDec monadVarName varAppliedTypes options (SigD sigFnName ty) = do
let fnName = createFnName sigFnName options
mockFnName = mkName fnName
params = mkName "p"
updatedType = updateType ty varAppliedTypes
fnType = if options.implicitMonadicReturn
then createMockBuilderFnType monadVarName updatedType
else updatedType
fnDecs <- if isNotConstantFunctionType ty then
doCreateMockFnDecs fnName mockFnName params fnType monadVarName updatedType
else
if options.implicitMonadicReturn then
doCreateConstantMockFnDecs fnName mockFnName fnType monadVarName
else
doCreateEmptyVerifyParamMockFnDecs fnName mockFnName params fnType monadVarName updatedType
pragmaDec <- pragInlD mockFnName NoInline FunLike AllPhases
pure $ pragmaDec : fnDecs
createMockFnDec _ _ _ dec = fail $ "unsupport dec: " <> pprint dec
doCreateMockFnDecs :: (Quote m) => String -> Name -> Name -> Type -> Name -> Type -> m [Dec]
doCreateMockFnDecs funNameStr mockFunName params funType monadVarName updatedType = do
newFunSig <- do
let verifyParams = createMockBuilderVerifyParams updatedType
sigD
mockFunName
[t|
(MockBuilder $(varT params) ($(pure funType)) ($(pure verifyParams)), Monad $(varT monadVarName)) =>
$(varT params) ->
MockT $(varT monadVarName) ()
|]
createMockFn <- [|createNamedMock|]
mockBody <- createMockBody funNameStr createMockFn
newFun <- funD mockFunName [clause [varP $ mkName "p"] (normalB (pure mockBody)) []]
pure $ newFunSig : [newFun]
doCreateConstantMockFnDecs :: (Quote m) => String -> Name -> Type -> Name -> m [Dec]
doCreateConstantMockFnDecs funNameStr mockFunName ty monadVarName = do
newFunSig <- sigD mockFunName [t|(Monad $(varT monadVarName)) => $(pure ty) -> MockT $(varT monadVarName) ()|]
createMockFn <- [|createNamedConstantMock|]
mockBody <- createMockBody funNameStr createMockFn
newFun <- funD mockFunName [clause [varP $ mkName "p"] (normalB (pure mockBody)) []]
pure $ newFunSig : [newFun]
-- MockBuilder constraints, but verifyParams are empty.
doCreateEmptyVerifyParamMockFnDecs :: (Quote m) => String -> Name -> Name -> Type -> Name -> Type -> m [Dec]
doCreateEmptyVerifyParamMockFnDecs funNameStr mockFunName params funType monadVarName updatedType = do
newFunSig <- do
let verifyParams = createMockBuilderVerifyParams updatedType
sigD
mockFunName
[t|
(MockBuilder $(varT params) ($(pure funType)) (), Monad $(varT monadVarName)) =>
$(varT params) ->
MockT $(varT monadVarName) ()
|]
createMockFn <- [|createNamedMock|]
mockBody <- createMockBody funNameStr createMockFn
newFun <- funD mockFunName [clause [varP $ mkName "p"] (normalB (pure mockBody)) []]
pure $ newFunSig : [newFun]
createMockBody :: (Quote m) => String -> Exp -> m Exp
createMockBody funNameStr createMockFn =
[|
MockT $
modify
( ++
[ Definition
(Proxy :: Proxy $(litT (strTyLit funNameStr)))
(unsafePerformIO $ $(pure createMockFn) $(litE (stringL funNameStr)) p)
shouldApplyToAnything
]
)
|]
isNotConstantFunctionType :: Type -> Bool
isNotConstantFunctionType (AppT (AppT ArrowT _) _) = True
isNotConstantFunctionType (AppT t1 t2) = isNotConstantFunctionType t1 || isNotConstantFunctionType t2
isNotConstantFunctionType (TupleT _) = False
isNotConstantFunctionType (ForallT _ _ t) = isNotConstantFunctionType t
isNotConstantFunctionType _ = False
updateType :: Type -> [VarAppliedType] -> Type
updateType (AppT (VarT v1) (VarT v2)) varAppliedTypes = do
let x = maybe (VarT v1) ConT (findClass v1 varAppliedTypes)
y = maybe (VarT v2) ConT (findClass v2 varAppliedTypes)
AppT x y
updateType ty _ = ty
hasClass :: Name -> [VarAppliedType] -> Bool
hasClass varName = P.any (\(VarAppliedType v c) -> (v == varName) && isJust c)
findClass :: Name -> [VarAppliedType] -> Maybe Name
findClass varName types = do
guard $ hasClass varName types
(VarAppliedType _ c) <- find (\(VarAppliedType v _) -> v == varName) types
c
createFnName :: Name -> MockOptions -> String
createFnName funName options = do
options.prefix <> nameBase funName <> options.suffix
createMockBuilderFnType :: Name -> Type -> Type
createMockBuilderFnType monadVarName a@(AppT (VarT var) ty)
| monadVarName == var = ty
| otherwise = a
createMockBuilderFnType monadVarName (AppT ty ty2) = AppT ty (createMockBuilderFnType monadVarName ty2)
createMockBuilderFnType monadVarName (ForallT _ _ ty) = createMockBuilderFnType monadVarName ty
createMockBuilderFnType _ ty = ty
createMockBuilderVerifyParams :: Type -> Type
createMockBuilderVerifyParams (AppT (AppT ArrowT ty) (AppT (VarT _) _)) = AppT (ConT ''Param) ty
createMockBuilderVerifyParams (AppT (AppT ArrowT ty) ty2) =
AppT (AppT (ConT ''(:>)) (AppT (ConT ''Param) ty)) (createMockBuilderVerifyParams ty2)
createMockBuilderVerifyParams (AppT (VarT _) (ConT c)) = AppT (ConT ''Param) (ConT c)
createMockBuilderVerifyParams (ForallT _ _ ty) = createMockBuilderVerifyParams ty
createMockBuilderVerifyParams a = a
findParam :: (KnownSymbol sym) => Proxy sym -> [Definition] -> Maybe a
findParam pa definitions = do
let definition = find (\(Definition s _ _) -> symbolVal s == symbolVal pa) definitions
fmap (\(Definition _ mock _) -> unsafeCoerce mock) definition
typeToNames :: Type -> [Q Name]
typeToNames (AppT (AppT ArrowT _) t2) = newName "a" : typeToNames t2
typeToNames (ForallT _ _ ty) = typeToNames ty
typeToNames _ = []
getClassName :: Type -> Name
getClassName (ConT name) = name
getClassName (AppT ty _) = getClassName ty
getClassName d = error $ "unsupported class definition: " <> show d
getClassNames :: Type -> [Name]
getClassNames (AppT (ConT name1) (ConT name2)) = [name1, name2]
getClassNames (AppT ty (ConT name)) = getClassNames ty ++ [name]
getClassNames (AppT ty1 ty2) = getClassNames ty1 ++ getClassNames ty2
getClassNames _ = []
data ClassName2VarNames = ClassName2VarNames Name [Name]
instance Show ClassName2VarNames where
show (ClassName2VarNames cName varNames) = showClassDef cName varNames
data VarName2ClassNames = VarName2ClassNames Name [Name]
instance Show VarName2ClassNames where
show (VarName2ClassNames varName classNames) = show varName <> " class is " <> unwords (showClassName <$> classNames)
data VarAppliedType = VarAppliedType {name :: Name, appliedClassName :: Maybe Name}
deriving (Show)
showClassName :: Name -> String
showClassName n = splitLast "." $ show n
showClassDef :: Name -> [Name] -> String
showClassDef className varNames = showClassName className <> " " <> unwords (show <$> varNames)
splitLast :: String -> String -> String
splitLast delimiter = last . split delimiter
split :: String -> String -> [String]
split delimiter str = unpack <$> splitOn (pack delimiter) (pack str)
safeIndex :: [a] -> Int -> Maybe a
safeIndex [] _ = Nothing
safeIndex (x : _) 0 = Just x
safeIndex (_ : xs) n
| n < 0 = Nothing
| otherwise = safeIndex xs (n - 1)
verifyExtension :: Extension -> Q ()
verifyExtension e = isExtEnabled e >>= flip unless (fail $ "Language extensions `" ++ show e ++ "` is required.")