packages feed

mockcat-1.0.0.0: src/Test/MockCat/TH/TypeUtils.hs

{-# LANGUAGE TemplateHaskellQuotes #-}
module Test.MockCat.TH.TypeUtils
  ( splitApps,
    substituteType,
    isNotConstantFunctionType,
    collectTypeVars,
    needsTypeable,
    collectTypeableTargets,
    isStandardTypeCon
  )
where

import qualified Data.Map.Strict as Map
import Language.Haskell.TH (Name, Type (..))
import Test.MockCat.Param (Param)
import Test.MockCat.Cons ((:>))

splitApps :: Type -> (Type, [Type])
splitApps ty = go ty []
  where
    go (Language.Haskell.TH.AppT t1 t2) acc = go t1 (t2 : acc)
    go t acc = (t, acc)

substituteType :: Map.Map Language.Haskell.TH.Name Language.Haskell.TH.Type -> Language.Haskell.TH.Type -> Language.Haskell.TH.Type
substituteType subMap = go
  where
    go (Language.Haskell.TH.VarT name) = Map.findWithDefault (Language.Haskell.TH.VarT name) name subMap
    go (Language.Haskell.TH.AppT t1 t2) = Language.Haskell.TH.AppT (go t1) (go t2)
    go (Language.Haskell.TH.SigT t k) = Language.Haskell.TH.SigT (go t) k
    go (Language.Haskell.TH.ParensT t) = Language.Haskell.TH.ParensT (go t)
    go (Language.Haskell.TH.InfixT t1 n t2) = Language.Haskell.TH.InfixT (go t1) n (go t2)
    go (Language.Haskell.TH.UInfixT t1 n t2) = Language.Haskell.TH.UInfixT (go t1) n (go t2)
    go (Language.Haskell.TH.ForallT tvs ctx t) = Language.Haskell.TH.ForallT tvs (map go ctx) (go t)
    go t = t

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

needsTypeable :: Type -> Bool
needsTypeable = go
  where
    go (ForallT _ _ t) = go t
    go (AppT t1 t2) = go t1 || go t2
    go (SigT t _) = go t
    go (VarT _) = True
    go (ParensT t) = go t
    go (InfixT t1 _ t2) = go t1 || go t2
    go (UInfixT t1 _ t2) = go t1 || go t2
    go (ImplicitParamT _ t) = go t
    go _ = False

collectTypeVars :: Type -> [Name]
collectTypeVars (VarT name) = [name]
collectTypeVars (AppT t1 t2) = collectTypeVars t1 ++ collectTypeVars t2
collectTypeVars (SigT t _) = collectTypeVars t
collectTypeVars (ParensT t) = collectTypeVars t
collectTypeVars (InfixT t1 _ t2) = collectTypeVars t1 ++ collectTypeVars t2
collectTypeVars (UInfixT t1 _ t2) = collectTypeVars t1 ++ collectTypeVars t2
collectTypeVars (ForallT _ _ t) = collectTypeVars t
collectTypeVars (ImplicitParamT _ t) = collectTypeVars t
collectTypeVars _ = []

collectTypeableTargets :: Type -> [Type]
collectTypeableTargets ty =
  case ty of
    VarT _ -> [ty]
    AppT _ _ ->
      let (f, args) = splitApps ty
      in if isStandardTypeCon f
         then concatMap collectTypeableTargets args
         else [ty]
    SigT t _ -> collectTypeableTargets t
    ParensT t -> collectTypeableTargets t
    InfixT t1 _ t2 -> collectTypeableTargets t1 ++ collectTypeableTargets t2
    UInfixT t1 _ t2 -> collectTypeableTargets t1 ++ collectTypeableTargets t2
    ForallT _ _ t -> collectTypeableTargets t
    _ -> []

isStandardTypeCon :: Type -> Bool
isStandardTypeCon ArrowT = True
isStandardTypeCon ListT = True
isStandardTypeCon (TupleT _) = True
isStandardTypeCon (ConT n) =
  n `elem`
    [ ''Maybe
    , ''IO
    , ''Either
    , ''[]
    , ''(,)
    , ''(,,)
    , ''(,,,)
    , ''(,,,,)
    , ''Param
    , ''(:>)
    ]
isStandardTypeCon _ = False