{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ViewPatterns #-} -- | Dumps an index of all terms and their types module HsInspect.Index ( index, PackageEntries, ) where import Avail (AvailInfo (..)) import BinIface ( CheckHiWay (..), TraceBinIFaceReading (..), readBinIface, ) import Control.Monad import Control.Monad.IO.Class import Data.List (isSuffixOf) import Data.Maybe (catMaybes, maybeToList) import Data.Set (Set) import qualified Data.Set as Set import qualified GHC import GHC.PackageDb import HsInspect.Sexp import HsInspect.Util import HscTypes (ModIface (..)) import qualified Id as GHC import Json import Module (Module (..), moduleNameString, unitIdString) import Outputable (showPpr) import PackageConfig --import System.IO (hPutStrLn, stderr) import PackageConfig (packageConfigId) import Packages (explicitPackages, lookupPackage) import TcEnv (tcLookup) import TcRnMonad (initTcInteractive) import qualified TcRnTypes as GHC index :: GHC.GhcMonad m => m [PackageEntries] index = do -- TODO the home package dflags <- GHC.getSessionDynFlags let explicit = explicitPackages $ GHC.pkgState dflags pkgcfgs = maybeToList . lookupPackage dflags =<< explicit traverse getSymbols pkgcfgs -- TODO Maybe haddock-html -- TODO Maybe source definition (or should we leave source resolution to downstream?) getSymbols :: GHC.GhcMonad m => PackageConfig -> m PackageEntries getSymbols pkg = do let findHis dir = filter (".hi" `isSuffixOf`) <$> liftIO (walk dir) exposed = Set.fromList $ fst <$> exposedModules pkg unitid = packageConfigId pkg his <- join <$> traverse findHis (importDirs pkg) PackageEntries unitid . catMaybes <$> traverse (hiToSymbols exposed) his hiToSymbols :: GHC.GhcMonad m => Set GHC.ModuleName -> FilePath -> m (Maybe ModuleEntries) hiToSymbols exposed hi = do env <- GHC.getSession dflags <- GHC.getSessionDynFlags (_, hits) <- -- TODO use initTc instead of initTcInteractive liftIO . initTcInteractive env $ do iface <- readBinIface IgnoreHiWay QuietBinIFaceReading hi let m = mi_module iface modName = moduleName m if not $ Set.member (GHC.moduleName m) exposed then pure Nothing else do let thing (Avail name) = traverse tcLookup [name] -- TODO the fields in AvailTC thing (AvailTC name members _) = traverse tcLookup (name : members) things <- join <$> traverse thing (mi_exports iface) -- TODO refactor this code to return the Module and TcTyThing and -- do the conversion to Entry in the caller. pure . Just . ModuleEntries modName . catMaybes $ (tyrender dflags) <$> things pure $ join hits tyrender :: GHC.DynFlags -> GHC.TcTyThing -> Maybe Entry tyrender dflags (GHC.AGlobal (GHC.AnId var)) = Just $ Entry (showPpr dflags $ GHC.idName var) (showPpr dflags $ GHC.idType var) -- TODO investigate what we're skipping tyrender _ _ = Nothing -- TODO normalise the type string to make it easier for downstream tools to perform searches -- TODO note if this is the original definition point (vs a re-export) data Entry = Entry String String data ModuleEntries = ModuleEntries GHC.ModuleName [Entry] data PackageEntries = PackageEntries GHC.UnitId [ModuleEntries] instance ToSexp Entry where toSexp (Entry term typ) = alist [ ("name", SexpString term), ("type", SexpString typ) ] instance ToSexp ModuleEntries where toSexp (ModuleEntries modl entries) = alist [ ("module", SexpString . moduleNameString $ modl), ("ids", toSexp entries) ] instance ToSexp PackageEntries where toSexp (PackageEntries pkg modules) = alist [ ("unitid", SexpString . unitIdString $ pkg), ("modules", toSexp modules) ] instance ToJson Entry where json (Entry term typ) = JSObject [ ("name", JSString term), ("type", JSString typ) ] instance ToJson ModuleEntries where json (ModuleEntries modl entries) = JSObject [ ("module", JSString . moduleNameString $ modl), ("ids", JSArray $ json <$> entries) ] instance ToJson PackageEntries where json (PackageEntries pkg modules) = JSObject [ ("unitid", JSString . unitIdString $ pkg), ("modules", JSArray $ json <$> modules) ]