static-ls-0.1.2: src/StaticLS/HIE/File.hs
{-# LANGUAGE ScopedTypeVariables #-}
module StaticLS.HIE.File (
getHieFileFromTdi,
getHieFile,
modToHieFile,
modToSrcFile,
srcFilePathToHieFilePath,
hieFilePathToSrcFilePath,
-- | An alternate way of getting file information by pre-indexing hie files -
-- far slower on startup and currently unused
getHieFileMap,
hieFileMapToSrcMap,
)
where
import Control.Applicative ((<|>))
import Control.Exception (SomeException, catch)
import Control.Monad ((<=<))
import Control.Monad.IO.Unlift (MonadIO, liftIO)
import Control.Monad.Trans.Except (ExceptT (..))
import Control.Monad.Trans.Maybe (MaybeT (..), exceptToMaybeT, runMaybeT)
import Data.Bifunctor (first, second)
import qualified Data.Map as Map
import qualified GHC
import qualified GHC.Iface.Ext.Binary as GHC
import qualified GHC.Iface.Ext.Types as GHC
import GHC.Stack (HasCallStack)
import qualified GHC.Types.Name.Cache as GHC
import qualified HieDb
import qualified Language.LSP.Protocol.Types as LSP
import StaticLS.FilePath
import StaticLS.HIE.File.Except
import qualified StaticLS.HieDb as HieDb
import StaticLS.Maybe (flatMaybeT, toAlt)
import StaticLS.SrcFiles
import StaticLS.StaticEnv
import qualified System.Directory as Dir
import System.FilePath ((</>))
-- | Retrieve a hie info from a lsp text document identifier
getHieFileFromTdi :: (HasStaticEnv m, MonadIO m) => LSP.TextDocumentIdentifier -> MaybeT m GHC.HieFile
getHieFileFromTdi = exceptToMaybeT . getHieFile <=< tdiToHieFilePath
tdiToHieFilePath :: (HasStaticEnv m, MonadIO m) => LSP.TextDocumentIdentifier -> MaybeT m HieFilePath
tdiToHieFilePath = srcFilePathToHieFilePath <=< (MaybeT . pure . LSP.uriToFilePath . (._uri))
-- | Retrieve an hie file from a module name
modToHieFile :: (HasStaticEnv m, MonadIO m) => GHC.ModuleName -> MaybeT m GHC.HieFile
modToHieFile = exceptToMaybeT . getHieFile <=< modToHieFilePath
-- | Retrieve a src file from a module name
modToSrcFile :: (HasStaticEnv m, MonadIO m) => GHC.ModuleName -> MaybeT m SrcFilePath
modToSrcFile = hieFilePathToSrcFilePath <=< modToHieFilePath
{- | Fetch a src file from an hie file, checking hiedb but falling back on a file manipulation method
if not indexed
-}
srcFilePathToHieFilePath :: (HasStaticEnv m, MonadIO m) => SrcFilePath -> MaybeT m HieFilePath
srcFilePathToHieFilePath srcPath =
srcFilePathToHieFilePathFromFile srcPath
<|> srcFilePathToHieFilePathHieDb srcPath
-- | Fetch an hie file from a src file
hieFilePathToSrcFilePath :: (HasStaticEnv m, MonadIO m) => HieFilePath -> MaybeT m SrcFilePath
hieFilePathToSrcFilePath hiePath = do
hieFilePathToSrcFilePathHieDb hiePath
<|> hieFilePathToSrcFilePathFromFile hiePath
-----------------------------------------------------------------------------------
-- Primitive functions for looking up hie information
-----------------------------------------------------------------------------------
-- | Retrieve an hie file from a hie filepath
getHieFile :: (HasCallStack, MonadIO m) => HieFilePath -> ExceptT HieFileReadException m GHC.HieFile
getHieFile hieFilePath = do
-- Attempt to read valid hie file version
-- NOTE: attempting to override an incorrect header and read an hie file
-- seems to cause infinite hangs. TODO: explore why?
nameCache <- liftIO $ GHC.initNameCache 'a' []
result <-
liftIO
( fmap
(first HieFileVersionException)
(GHC.readHieFileWithVersion ((== GHC.hieVersion) . fst) nameCache hieFilePath)
`catch` (\(_ :: SomeException) -> pure . Left $ HieFileReadException)
)
ExceptT $ pure (second GHC.hie_file_result result)
-----------------------------------------------------------------------------------
-- HieDb Method of file lookups - requires hiedb to be indexed using --src-base-dirs from 0.4.4.0
-----------------------------------------------------------------------------------
srcFilePathToHieFilePathHieDb :: (HasStaticEnv m, MonadIO m) => SrcFilePath -> MaybeT m HieFilePath
srcFilePathToHieFilePathHieDb srcPath = do
absSrcPath <- liftIO $ Dir.makeAbsolute srcPath
Just hieModRow <- runHieDbMaybeT $ \hieDb -> do
HieDb.lookupHieFileFromSource hieDb absSrcPath
pure $ HieDb.hieModuleHieFile hieModRow
hieFilePathToSrcFilePathHieDb :: (HasStaticEnv m, MonadIO m) => SrcFilePath -> MaybeT m HieFilePath
hieFilePathToSrcFilePathHieDb hiePath = do
absHiePath <- liftIO $ Dir.makeAbsolute hiePath
Just hieModRow <- runHieDbMaybeT $ \hieDb -> do
HieDb.lookupHieFileFromHie hieDb absHiePath
toAlt . HieDb.modInfoSrcFile $ HieDb.hieModInfo hieModRow
modToHieFilePath :: (HasStaticEnv m, MonadIO m) => GHC.ModuleName -> MaybeT m HieFilePath
modToHieFilePath modName =
flatMaybeT $ runHieDbMaybeT $ \hieDb ->
runMaybeT $ do
Right unitId <- liftIO (HieDb.resolveUnitId hieDb modName)
Just hieModRow <- liftIO $ HieDb.lookupHieFile hieDb modName unitId
pure hieModRow.hieModuleHieFile
-----------------------------------------------------------------------------------
-- File/Directory method for getting hie files - faster but somewhat "hacky"
-- Useful as a fallback
-----------------------------------------------------------------------------------
hieFilePathToSrcFilePathFromFile :: (HasStaticEnv m, MonadIO m) => HieFilePath -> MaybeT m SrcFilePath
hieFilePathToSrcFilePathFromFile hiePath = do
hieFile <- exceptToMaybeT $ getHieFile hiePath
liftIO $ Dir.makeAbsolute hieFile.hie_hs_file
{- | Retrieve a hie file path from a src path
Substitutes the src directory with the hie directory and the src file extension with
the hie file extension. Fragile, but works well in practice.
Presently necessary because hiedb does not currently index the hs_src file location
in the `mods` table
-}
srcFilePathToHieFilePathFromFile :: (HasStaticEnv m, MonadIO m) => SrcFilePath -> MaybeT m HieFilePath
srcFilePathToHieFilePathFromFile srcPath = do
staticEnv <- getStaticEnv
let hieDir = staticEnv.wsRoot </> staticEnv.hieFilesPath
subRootExtensionFilepath staticEnv.wsRoot hieDir ".hie" srcPath
-----------------------------------------------------------------------------------
-- Map index method for getting hie files - too slow in practice on startup but makes
-- finding references for functions that are used a lot much faster
-----------------------------------------------------------------------------------
data HieInfo = HieInfo
{ hieFilePath :: HieFilePath
, hieFile :: GHC.HieFile
}
getHieFileMap :: FilePath -> HieFilePath -> IO (Map.Map SrcFilePath HieInfo)
getHieFileMap wsroot hieDir = do
let hieFullPath = wsroot </> hieDir
hieFilePaths <- HieDb.getHieFilesIn hieFullPath
nameCache <- GHC.initNameCache 'a' []
srcPathHieInfoPairs <- mapM (srcFileToHieFileInfo nameCache) hieFilePaths
pure $ Map.fromList srcPathHieInfoPairs
where
srcFileToHieFileInfo :: GHC.NameCache -> HieFilePath -> IO (SrcFilePath, HieInfo)
srcFileToHieFileInfo nameCache hieFilePath = do
hieFileResult <- GHC.readHieFile nameCache hieFilePath
absSrcFilePath <- Dir.makeAbsolute hieFileResult.hie_file_result.hie_hs_file
absHieFilePath <- Dir.makeAbsolute hieFilePath
let hieInfo =
HieInfo
{ hieFilePath = absHieFilePath
, hieFile = hieFileResult.hie_file_result
}
pure (absSrcFilePath, hieInfo)
hieFileMapToSrcMap :: Map.Map SrcFilePath HieInfo -> Map.Map HieFilePath SrcFilePath
hieFileMapToSrcMap =
Map.fromList . fmap (\(srcPath, hieInfo) -> (hieInfo.hieFilePath, srcPath)) . Map.toList