packages feed

hiedb 0.4.4.0 → 0.8.0.0

raw patch · 12 files changed

Files

CHANGELOG.md view
@@ -1,8 +1,48 @@ # Revision history for hiedb +## 0.7.0.0++* DB schema change: added new bool field `refs.is_generated` to distinguish between references coming from source code+and those generated by the compiler+* Extended `hie-db ref-graph` cli command by adding possibility to filter by occurrence name / module / unit-id:+    `hiedb ref-graph [NAME] [MODULE] [-u|--unit-id UNITID]`++## 0.6.0.2++* Bump base and ghc version bounds to support GHC 9.12++## 0.6.0.1++* Bump base and ghc version bounds to support GHC 9.10++## 0.6.0.0 -- 2024-02-11++* Add index on column `unit` of table `mods`+* Add new table `imports` which indexes import statements+* Add new cli options that allow selectively skipping indexing of some things:+    `--skip-refs`              Skip refs table when indexing+    `--skip-decls`             Skip decls table when indexing+    `--skip-defs`              Skip defs table when indexing+    `--skip-exports`           Skip exports table when indexing+    `--skip-imports`           Skip imports table when indexing+    `--skip-types`             Skip types and typerefs table when indexing+    `--skip-typerefs`          Skip typerefs table when indexing+* Fix a bug where duplicate entries were inserted into `typerefs` table during indexing+* Fix a bug in `searchDef` query which was mistakenly not including ':' when searching by occurrence names++## 0.5.0.1 -- 2024-01-12++* Fix incorrect Show Symbol instance in 0.5.0.0++## 0.5.0.0 -- 2024-01-12++* Handle duplicate record fields in GHC 9.8 instead of crashing+ ## 0.4.4.0 -- 2023-11-13+ * Add `--src-base-dir` option allowing for src file indexing in `mods`-* 9.8.1 support+* Support GHC 9.8.1+* Drop support for GHC 8.10 * Add `lookupHieFileFromHash` * Add `lookupPackage` * Add `removeDependencySrcFiles`
− Setup.hs
@@ -1,2 +0,0 @@-import Distribution.Simple-main = defaultMain
hiedb.cabal view
@@ -1,6 +1,6 @@ cabal-version:       2.4 name:                hiedb-version:             0.4.4.0+version:             0.8.0.0 synopsis:            Generates a references DB from .hie files description:         Tool and library to index and query a collection of `.hie` files bug-reports:         https://github.com/wz1000/HieDb/issues@@ -10,15 +10,20 @@ maintainer:          zubin.duggal@gmail.com copyright:           Zubin Duggal category:            Development-extra-source-files:-  CHANGELOG.md+extra-doc-files:   README.md+  CHANGELOG.md+extra-source-files:   test/data/*.hs   test/data/Sub/*.hs-tested-with:         GHC ==8.8.1 || ==8.8.2  || ==8.8.3  || ==8.8.4-                     || ==8.10.1 || ==8.10.2 || ==8.10.3 || ==8.10.4-                     || ==9.0.1-+tested-with:         GHC ==9.0.2+                      || ==9.2.8+                      || ==9.4.8+                      || ==9.6.7+                      || ==9.8.4+                      || ==9.10.3+                      || ==9.12.2+                      || ==9.14.1  source-repository head   type: git@@ -26,15 +31,17 @@  common common-options   default-language:    Haskell2010-  build-depends:       base >= 4.12 && < 4.20+  build-depends:       base >= 4.12 && < 4.23   ghc-options:         -Wall-                       -Wincomplete-uni-patterns-                       -Wincomplete-record-updates                        -Wcompat                        -Widentities-                       -Wredundant-constraints+                       -Wincomplete-record-updates+                       -Wincomplete-uni-patterns                        -Wpartial-fields-                       -Wno-unrecognised-pragmas+                       -Wredundant-constraints+                       -Wunused-packages+                       -Wno-name-shadowing+ executable hiedb   import:              common-options   hs-source-dirs:      exe@@ -55,7 +62,7 @@                        HieDb.Dump,                        HieDb.Html,                        HieDb.Run-  build-depends:       ghc >= 8.6 && < 9.9+  build-depends:       ghc >= 8.6 && < 9.15                      , array                      , containers                      , filepath@@ -81,9 +88,9 @@   build-tool-depends: hiedb:hiedb   build-depends:      directory                     , filepath-                    , ghc >= 8.6                     , ghc-paths                     , hiedb                     , hspec                     , process                     , temporary+                    , algebraic-graphs
src/HieDb/Compat.hs view
@@ -25,7 +25,11 @@     , mkVarOccFS     , Name     , nameSrcSpan+#if __GLASGOW_HASKELL__ >= 903+    , NameCacheUpdater+#else     , NameCacheUpdater(..)+#endif     , NameCache     , nsNames     , initNameCache@@ -81,11 +85,13 @@     , IfaceTyCon(..)     , field_label     , dfs+    , fieldNameSpace_maybe+    , fieldName+    , mkFastStringByteString ) where  import Compat.HieTypes -#if __GLASGOW_HASKELL__ >= 900 import GHC.Data.FastString as FS import GHC.Driver.Session import GHC.Iface.Env@@ -111,33 +117,15 @@ #else import GHC.Utils.Outputable (showSDoc, ppr, (<+>), hang, text) #endif-#else-import DynFlags-import FastString-import Fingerprint-import FieldLabel-import Module-import Name-import NameCache-import Outputable (showSDoc, ppr, (<+>), hang, text)-#if __GLASGOW_HASKELL__ < 903-import IfaceEnv (NameCacheUpdater(..))-#endif-import IfaceType-import UniqSupply-import SrcLoc-import SysTools-import qualified Avail-#endif -#if __GLASGOW_HASKELL__ >= 900+import qualified Algebra.Graph.AdjacencyMap           as Graph+import qualified Algebra.Graph.AdjacencyMap.Algorithm as Graph+ import GHC.Types.SrcLoc import Compat.HieUtils  import qualified Data.Map as M import qualified Data.Set as S-import qualified Algebra.Graph.AdjacencyMap           as Graph-import qualified Algebra.Graph.AdjacencyMap.Algorithm as Graph   -- nodeInfo' :: Ord a => HieAST a -> NodeInfo a@@ -155,22 +143,7 @@                                         GT -> b : mergeSorted la bs     mergeSorted as [] = as     mergeSorted [] bs = bs-#else-import qualified FastString as FS -nodeInfo' :: HieAST TypeIndex -> NodeInfo TypeIndex-nodeInfo' = nodeInfo-type Unit = UnitId-unitString :: Unit -> String-unitString = unitIdString-stringToUnit :: String -> Unit-stringToUnit = stringToUnitId-moduleUnit :: Module -> Unit-moduleUnit = moduleUnitId-unhelpfulSpanFS :: FS.FastString -> FS.FastString-unhelpfulSpanFS = id-#endif- #if __GLASGOW_HASKELL__ < 902 type HiePath = FastString #endif@@ -228,4 +201,18 @@ dfs = Graph.dfs #else dfs = flip Graph.dfs+#endif++fieldNameSpace_maybe :: NameSpace -> Maybe FastString+#if __GLASGOW_HASKELL__ >= 907+-- This is horrible, we can improve it once+-- https://gitlab.haskell.org/ghc/ghc/-/issues/24244 is addressed+fieldNameSpace_maybe ns = fieldOcc_maybe (mkOccName ns "")+#else+fieldNameSpace_maybe _ = Nothing+#endif++#if __GLASGOW_HASKELL__ < 907+fieldName :: FastString -> NameSpace+fieldName _ = varName #endif
src/HieDb/Create.hs view
@@ -4,6 +4,8 @@ {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE LambdaCase #-}+{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}+{-# HLINT ignore "Use camelCase" #-} module HieDb.Create where  import Prelude hiding (mod)@@ -15,10 +17,13 @@  import Control.Exception import Control.Monad+import Control.Monad.Cont import Control.Monad.IO.Class+import Control.Monad.State.Strict (evalStateT)  import qualified Data.Array as A import qualified Data.Map as M+import qualified Data.IntMap.Strict as IMap  import Data.Int import Data.List ( isSuffixOf )@@ -35,7 +40,7 @@ import HieDb.Utils  sCHEMA_VERSION :: Integer-sCHEMA_VERSION = 6+sCHEMA_VERSION = 9  dB_VERSION :: Integer dB_VERSION = read (show sCHEMA_VERSION ++ "999" ++ show hieVersion)@@ -57,26 +62,87 @@   else     throwIO $ IncompatibleSchemaVersion dB_VERSION ver +-- | Commmon implementation for the @withHieDb*@ functions that prepared the+-- hiedb file, if it didn't already exist. Also prepares any statements that+-- we'll need to do indexing.+withHieDbImpl :: FilePath -> (HieDb -> IO a) -> IO a+withHieDbImpl fp act = withConnection fp $ \connection -> do+  liftIO $ setupHieDb connection++  -- We manually prepare statements instead of using the machinery in+  -- sqlite-simple so we can reuse these across `.hie` files more easily.+  -- This was benchmarked to be faster:+  --   https://github.com/wz1000/HieDb/pull/86+  withPreparedHieDbStatements connection $ \statements -> do+    act $ HieDb connection statements++withPreparedHieDbStatements :: Connection -> (HieDbStatements -> IO a) -> IO a+withPreparedHieDbStatements connection action = do+  let prepareInternalTableDeletion = do+        deletions+          <- traverse (createStatement connection)+            [ "DELETE FROM refs  WHERE hieFile = ?"+            , "DELETE FROM decls WHERE hieFile = ?"+            , "DELETE FROM defs  WHERE hieFile = ?"+            , "DELETE FROM typerefs WHERE hieFile = ?"+            , "DELETE FROM mods  WHERE hieFile = ?"+            , "DELETE FROM exports WHERE hieFile = ?"+            ]+        pure $ \fp -> mapM_ (`runStatementFor_` fp) deletions++  runContT+    ( HieDbStatements+        <$> createStatement connection "INSERT INTO mods VALUES (?,?,?,?,?,?,?)"+        <*> createStatement connection "INSERT INTO refs VALUES (?,?,?,?,?,?,?,?,?)"+        <*> createStatement connection "INSERT INTO decls VALUES (?,?,?,?,?,?,?)"+        <*> createStatement connection "INSERT INTO imports VALUES (?,?,?,?,?,?)"+        <*> createStatement connection "INSERT INTO defs VALUES (?,?,?,?,?,?)"+        <*> createStatement connection "INSERT INTO exports VALUES (?,?,?,?,?,?,?,?)"+        <*> createStatement connection "INSERT INTO typerefs VALUES (?,?,?,?,?,?,?)"+        <*> createStatement connection "INSERT INTO typenames(name,mod,unit) VALUES (?,?,?)"+        <*> createStatement connection "SELECT id FROM typenames WHERE name = ? AND mod = ? AND unit = ?"+        <*> prepareInternalTableDeletion+    )+    action+ {-| Given path to @.hiedb@ file, constructs 'HieDb' and passes it to given function. -} withHieDb :: FilePath -> (HieDb -> IO a) -> IO a-withHieDb fp f = withConnection fp (checkVersion f . HieDb)+withHieDb fp f = withHieDbImpl fp $ \hiedb -> do+  checkVersion f hiedb  {-| Given GHC LibDir and path to @.hiedb@ file, constructs DynFlags (required for printing info from @.hie@ files) and 'HieDb' and passes them to given function. -} withHieDbAndFlags :: LibDir -> FilePath -> (DynFlags -> HieDb -> IO a) -> IO a-withHieDbAndFlags libdir fp f = do+withHieDbAndFlags libdir fp f = withHieDbImpl fp $ \hiedb -> do   dynFlags <- dynFlagsForPrinting libdir-  withConnection fp (checkVersion (f dynFlags) . HieDb)+  checkVersion (f dynFlags) hiedb  {-| Initialize database schema for given 'HieDb'. -} initConn :: HieDb -> IO ()-initConn (getConn -> conn) = do+{-# DEPRECATED initConn "Use setupHieDb instead." #-}+initConn = setupHieDb . getConn++{-| Initialize database schema for given 'HieDb'.+-}+setupHieDb :: Connection -> IO ()+setupHieDb conn = do   execute_ conn "PRAGMA busy_timeout = 500;"   execute_ conn "PRAGMA journal_mode = WAL;"   execute_ conn "PRAGMA foreign_keys = ON;"++  -- The default setting for synchronous is 'FULL'. `FULL` will issue fsync's+  -- and wait to ensure that every write is done durably. Switching to `NORMAL`+  -- moves this to the checkpoints by the WAL (when using a WAL), losing us+  -- durability. Practically this means that committed transaction may be rolled+  -- back on system failure. This is fine, as the hiedb file will recover by+  -- reindexing. See the following.+  -- - https://sqlite.org/pragma.html#pragma_synchronous+  -- - https://sqlite.org/wal.html+  -- - https://github.com/wz1000/HieDb/pull/86 for benchmarks.+  execute_ conn "PRAGMA synchronous = NORMAL;"   execute_ conn "PRAGMA defer_foreign_keys = ON;"    execute_ conn "CREATE TABLE IF NOT EXISTS mods \@@ -91,6 +157,7 @@                 \, CONSTRAINT real_has_src CHECK ( (NOT is_real) OR (hs_src IS NOT NULL) ) \                 \)"   execute_ conn "CREATE INDEX IF NOT EXISTS mod_hash ON mods(hieFile,hash)"+  execute_ conn "CREATE INDEX IF NOT EXISTS mod_unit ON mods(unit)"    execute_ conn "CREATE TABLE IF NOT EXISTS exports \                 \( hieFile TEXT NOT NULL \@@ -114,6 +181,7 @@                 \, sc   INTEGER NOT NULL \                 \, el   INTEGER NOT NULL \                 \, ec   INTEGER NOT NULL \+                \, is_generated BOOLEAN NOT NULL \                 \, FOREIGN KEY(hieFile) REFERENCES mods(hieFile) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED \                 \)"   execute_ conn "CREATE INDEX IF NOT EXISTS refs_mod ON refs(hieFile)"@@ -130,6 +198,19 @@                 \)"   execute_ conn "CREATE INDEX IF NOT EXISTS decls_mod ON decls(hieFile)" +  execute_ conn "CREATE TABLE IF NOT EXISTS imports \+                \( hieFile    TEXT NOT NULL \+                \, mod     TEXT NOT NULL \+                \, sl      INTEGER NOT NULL \+                \, sc      INTEGER NOT NULL \+                \, el      INTEGER NOT NULL \+                \, ec      INTEGER NOT NULL \+                \, FOREIGN KEY(hieFile) REFERENCES mods(hieFile) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED \+                \)"+  execute_ conn "CREATE INDEX IF NOT EXISTS imports_mod ON imports(mod)"+  execute_ conn "CREATE INDEX IF NOT EXISTS imports_hiefile ON imports(hieFile)"++   execute_ conn "CREATE TABLE IF NOT EXISTS defs \                 \( hieFile    TEXT NOT NULL \                 \, occ        TEXT NOT NULL \@@ -169,7 +250,7 @@ corresponding record in DB. -} addArr :: HieDb -> A.Array TypeIndex HieTypeFlat -> IO (A.Array TypeIndex (Maybe Int64))-addArr (getConn -> conn) arr = do+addArr hiedb arr = do   forM arr $ \case     HTyVarTy n -> addName n     HTyConApp tc _ -> addName (ifaceTyConName tc)@@ -182,8 +263,8 @@         let occ = nameOccName n             mod = moduleName m             uid = moduleUnit m-        execute conn "INSERT INTO typenames(name,mod,unit) VALUES (?,?,?)" (occ,mod,uid)-        Just . fromOnly . head <$> query conn "SELECT id FROM typenames WHERE name = ? AND mod = ? AND unit = ?" (occ,mod,uid)+        runStatementFor_ (insertTypenamesStatement (preparedStatements hiedb)) (occ,mod,uid)+        fmap fromOnly <$> runStatementFor (queryTypenamesStatement (preparedStatements hiedb)) (occ,mod,uid)  {-| Add references to types from given @.hie@ file to DB. -} addTypeRefs@@ -200,19 +281,47 @@     asts = getAsts $ hie_asts hf     addTypesFromAst :: HieAST TypeIndex -> IO ()     addTypesFromAst ast = do-      mapM_ (addTypeRef db path arr ixs (nodeSpan ast))-        $ mapMaybe (\x -> guard (any (not . isOccurrence) (identInfo x)) *> identType x)+      flip evalStateT IMap.empty+        $ mapM_ (addTypeRef db path arr ixs (nodeSpan ast))+        $ mapMaybe (\x -> guard (not (all isOccurrence (identInfo x))) *> identType x)         $ M.elems         $ nodeIdentifiers         $ nodeInfo' ast       mapM_ addTypesFromAst $ nodeChildren ast +-- | Options to skip indexing phases+data SkipOptions =+  SkipOptions+    { skipRefs :: Bool+    , skipDecls :: Bool+    , skipDefs :: Bool+    , skipExports :: Bool+    , skipImports :: Bool+    , skipTypes :: Bool+    -- ^ Note skip types will also skip type refs since it is dependent+    , skipTypeRefs :: Bool+    }+    deriving Show++defaultSkipOptions :: SkipOptions+defaultSkipOptions =+  SkipOptions+    { skipRefs = False+    , skipDecls = False+    , skipDefs = False+    , skipExports = False+    , skipImports = False+    , skipTypes = False+    -- ^ Note skip types will also skip type refs since it is dependent+    , skipTypeRefs = False+    }+ {-| Adds all references from given @.hie@ file to 'HieDb'. The indexing is skipped if the file was not modified since the last time it was indexed. The boolean returned is true if the file was actually indexed -}-addRefsFrom :: (MonadIO m, NameCacheMonad m) => HieDb -> Maybe FilePath -> FilePath -> m Bool-addRefsFrom c@(getConn -> conn) mSrcBaseDir path = do+addRefsFrom :: (MonadIO m, NameCacheMonad m) => HieDb -> Maybe FilePath -> SkipOptions -> FilePath ->  m Bool+addRefsFrom c@(getConn -> conn) mSrcBaseDir skipOptions path = do   hash <- liftIO $ getFileHash path   mods <- liftIO $ query conn "SELECT * FROM mods WHERE hieFile = ? AND hash = ?" (path, hash)   case mods of@@ -229,10 +338,10 @@                 (\srcBaseDir ->  do                     srcFullPath <- makeAbsolute (srcBaseDir </> hie_hs_file hieFile)                     fileExists <- doesFileExist srcFullPath-                    pure $ if fileExists then RealFile srcFullPath else (FakeFile Nothing)+                    pure $ if fileExists then RealFile srcFullPath else FakeFile Nothing                 )                 mSrcBaseDir-        addRefsFromLoaded c path srcfile hash hieFile+        addRefsFromLoadedInternal c path srcfile hash skipOptions hieFile  addRefsFromLoaded   :: MonadIO m@@ -244,11 +353,25 @@   -> Fingerprint -- ^ The hash of the @.hie@ file   -> HieFile -- ^ Data loaded from the @.hie@ file   -> m ()-addRefsFromLoaded-  db@(getConn -> conn) path sourceFile hash hf =+addRefsFromLoaded db path sourceFile hash hf =+    addRefsFromLoadedInternal db path sourceFile hash defaultSkipOptions hf++addRefsFromLoadedInternal+  :: MonadIO m+  => HieDb -- ^ HieDb into which we're adding the file+  -> FilePath -- ^ Path to @.hie@ file+  -> SourceFile -- ^ Path to .hs file from which @.hie@ file was created+                -- Also tells us if this is a real source file?+                -- i.e. does it come from user's project (as opposed to from project's dependency)?+  -> Fingerprint -- ^ The hash of the @.hie@ file+  -> SkipOptions -- ^ Skip indexing certain tables+  -> HieFile -- ^ Data loaded from the @.hie@ file+  -> m ()+addRefsFromLoadedInternal+  db@(getConn -> conn) path sourceFile hash skipOptions hf =     liftIO $ withTransaction conn $ do-      deleteInternalTables conn path-      addRefsFromLoaded_unsafe db path sourceFile hash hf+      deleteInternalTablesStatement (preparedStatements db) (Only path)+      addRefsFromLoaded_unsafe db path sourceFile hash skipOptions hf  -- | Like 'addRefsFromLoaded' but without: --   1) using a transaction@@ -256,42 +379,64 @@ -- --   Mostly useful to index a new database from scratch as fast as possible addRefsFromLoaded_unsafe-  :: MonadIO m-  => HieDb -- ^ HieDb into which we're adding the file+  :: HieDb -- ^ HieDb into which we're adding the file   -> FilePath -- ^ Path to @.hie@ file   -> SourceFile -- ^ Path to .hs file from which @.hie@ file was created                 -- Also tells us if this is a real source file?                 -- i.e. does it come from user's project (as opposed to from project's dependency)?   -> Fingerprint -- ^ The hash of the @.hie@ file+  -> SkipOptions -- ^ Skip indexing certain tables   -> HieFile -- ^ Data loaded from the @.hie@ file-  -> m ()+  -> IO () addRefsFromLoaded_unsafe- db@(getConn -> conn) path sourceFile hash hf = liftIO $ do+ db path sourceFile hash skipOptions hf = do    let isBoot = "boot" `isSuffixOf` path       mod    = moduleName smod       uid    = moduleUnit smod       smod   = hie_module hf-      refmap = generateReferencesMap $ getAsts $ hie_asts hf+      asts   = getAsts $ hie_asts hf+      refmapAll = generateReferencesMap asts+      refmapSourceOnly = generateReferencesMap $ fmap (dropNodeInfos GeneratedInfo) asts+      refmapGeneratedOnly = generateReferencesMap $ fmap (dropNodeInfos SourceInfo) asts       (srcFile, isReal) = case sourceFile of         RealFile f -> (Just f, True)         FakeFile mf -> (mf, False)       modrow = HieModuleRow path (ModuleInfo mod uid isBoot srcFile isReal hash) -  execute conn "INSERT INTO mods VALUES (?,?,?,?,?,?,?)" modrow+      -- We want to distinguish between references from source (NodeOrigin is SourceInfo)+      -- vs. generated by compiler (NodeOrigin is GeneratedInfo).+      -- Unfortunately generateReferencesMap throws away the info about NodeOrigin,+      -- so we need to use this to preprocess the ASTs from which the references map is generated.+      dropNodeInfos :: NodeOrigin -> HieAST a -> HieAST a+      dropNodeInfos originToDrop (Node (SourcedNodeInfo sniMap) sp children) =+        let sourceOnlyNodeInfo = SourcedNodeInfo $ M.delete originToDrop sniMap+        in Node sourceOnlyNodeInfo sp (map (dropNodeInfos originToDrop) children) -  let (rows,decls) = genRefsAndDecls path smod refmap-  executeMany conn "INSERT INTO refs  VALUES (?,?,?,?,?,?,?,?)" rows-  executeMany conn "INSERT INTO decls VALUES (?,?,?,?,?,?,?)" decls+  runStatementFor_ (insertModsStatement (preparedStatements db)) modrow -  let defs = genDefRow path smod refmap-  executeMany conn "INSERT INTO defs VALUES (?,?,?,?,?,?)" defs+  let AstInfo refsSrc declsSrc importsSrc = genAstInfo path smod SourceInfo refmapSourceOnly+      AstInfo refsGen declsGen importsGen = genAstInfo path smod GeneratedInfo refmapGeneratedOnly +  unless (skipRefs skipOptions) $+    mapM_ (runStatementFor_ (insertRefsStatement (preparedStatements db))) (refsSrc <> refsGen)+  unless (skipDecls skipOptions) $+    mapM_ (runStatementFor_ (insertDeclsStatement (preparedStatements db))) (declsSrc <> declsGen)+  unless (skipImports skipOptions) $+    mapM_ (runStatementFor_ (insertImportsStatement (preparedStatements db))) (importsSrc <> importsGen)++  let defs = genDefRow path smod refmapAll+  unless (skipDefs skipOptions) $+    mapM_ (runStatementFor_ (insertDefsStatement (preparedStatements db))) defs+   let exports = generateExports path $ hie_exports hf-  executeMany conn "INSERT INTO exports VALUES (?,?,?,?,?,?,?,?)" exports+  unless (skipExports skipOptions) $+    mapM_ (runStatementFor_ (insertExportsStatement (preparedStatements db))) exports -  ixs <- addArr db (hie_types hf)-  addTypeRefs db path hf ixs+  unless (skipTypes skipOptions) $ do+    ixs <- addArr db (hie_types hf)+    unless (skipTypeRefs skipOptions) $ do+      addTypeRefs db path hf ixs  {-| Add path to .hs source given path to @.hie@ file which has already been indexed. No action is taken if the corresponding @.hie@ file has not been indexed yet.@@ -316,21 +461,21 @@  {-| Delete all occurrences of given @.hie@ file from the database -} deleteFileFromIndex :: HieDb -> FilePath -> IO ()-deleteFileFromIndex (getConn -> conn) path = withTransaction conn $ do-  deleteInternalTables conn path+deleteFileFromIndex db@(getConn -> conn) path = withTransaction conn $ do+  deleteInternalTablesStatement (preparedStatements db) (Only path)  {-| Delete all entries associated with modules for which the 'modInfoSrcFile' doesn't exist on the disk. Doesn't delete it if there is no associated 'modInfoSrcFile' -} deleteMissingRealFiles :: HieDb -> IO ()-deleteMissingRealFiles (getConn -> conn) = withTransaction conn $ do+deleteMissingRealFiles db@(getConn -> conn) = withTransaction conn $ do   missing_file_keys <- fold_ conn "SELECT hieFile,hs_src FROM mods WHERE hs_src IS NOT NULL AND is_real" [] $     \acc (path,src) -> do       exists <- doesFileExist src       pure $ if exists then acc else path : acc   forM_ missing_file_keys $ \path -> do-    deleteInternalTables conn path+    deleteInternalTablesStatement (preparedStatements db) (Only path)  {-| Garbage collect typenames with no references - it is a good idea to call this function after a sequence of database updates (inserts or deletes)@@ -338,6 +483,9 @@ garbageCollectTypeNames :: HieDb -> IO Int garbageCollectTypeNames (getConn -> conn) = do   execute_ conn "DELETE FROM typenames WHERE NOT EXISTS ( SELECT 1 FROM typerefs WHERE typerefs.id = typenames.id LIMIT 1 )"+  -- Tack on a call to optimize that'll vacuum and update any table statistics+  -- that might have changed. See https://sqlite.org/pragma.html#pragma_optimize.+  execute_ conn "PRAGMA optimize;"   changes conn  deleteInternalTables :: Connection -> FilePath -> IO ()
src/HieDb/Query.hs view
@@ -5,7 +5,8 @@ {-# LANGUAGE ScopedTypeVariables #-} module HieDb.Query where -import           Algebra.Graph.AdjacencyMap (AdjacencyMap, edges, vertexSet, vertices, overlay)+import           Algebra.Graph.AdjacencyMap (AdjacencyMap, edges, induce, vertexSet, vertices, overlay, transpose)+import           Algebra.Graph.AdjacencyMap.Algorithm (reachable) import           Algebra.Graph.Export.Dot hiding ((:=)) import qualified Algebra.Graph.Export.Dot as G @@ -40,7 +41,7 @@ getAllIndexedMods (getConn -> conn) = query_ conn "SELECT * FROM mods"  {-| List all module exports -}-getAllIndexedExports :: HieDb -> IO [(ExportRow)]+getAllIndexedExports :: HieDb -> IO [ExportRow] getAllIndexedExports (getConn -> conn) = query_ conn "SELECT * FROM exports"  {-| List all exports of the given module -}@@ -140,20 +141,39 @@                               [":occ" := occ,":mod" := mn, ":unit" := uid]  findOneDef :: HieDb -> OccName -> Maybe ModuleName -> Maybe Unit -> IO (Either HieDbErr (Res DefRow))-findOneDef conn occ mn muid = wrap <$> findDef conn occ mn muid-  where-    wrap [x]    = Right x-    wrap []     = Left $ NameNotFound occ mn muid-    wrap (x:xs) = Left $ AmbiguousUnitId (defUnit x :| map defUnit xs)-    defUnit (_:.i) = i+findOneDef = findOneVia . findDef  searchDef :: HieDb -> String -> IO [Res DefRow] searchDef conn cs   = query (getConn conn) "SELECT defs.*,mods.mod,mods.unit,mods.is_boot,mods.hs_src,mods.is_real,mods.hash \                          \FROM defs JOIN mods USING (hieFile) \                          \WHERE occ LIKE ? \-                         \LIMIT 200" (Only $ '_':cs++"%")+                         \LIMIT 200" (Only $ '_':':':cs++"%") +findDecl :: HieDb -> OccName -> Maybe ModuleName -> Maybe Unit -> IO [Res DeclRow]+findDecl conn occ mn uid+  = queryNamed (getConn conn) "SELECT decls.*, mods.mod,mods.unit,mods.is_boot,mods.hs_src,mods.is_real,mods.hash \+                              \FROM decls JOIN mods USING (hieFile) \+                              \WHERE occ = :occ AND (:mod IS NULL OR mod = :mod) AND (:unit IS NULL OR unit = :unit)"+                              [":occ" := occ,":mod" := mn, ":unit" := uid]++findOneDecl :: HieDb -> OccName -> Maybe ModuleName -> Maybe Unit -> IO (Either HieDbErr (Res DeclRow))+findOneDecl = findOneVia . findDecl++findOneVia+  :: Functor f+  => (OccName -> Maybe ModuleName -> Maybe Unit -> f [Res a])+  -> OccName+  -> Maybe ModuleName+  -> Maybe Unit+  -> f (Either HieDbErr (Res a))+findOneVia f occ mn muid = wrap <$> f occ mn muid+  where+    wrap [x]    = Right x+    wrap []     = Left $ NameNotFound occ mn muid+    wrap (x:xs) = Left $ AmbiguousUnitId (declUnit x :| map declUnit xs)+    declUnit (_:.i) = i+ {-| @withTarget db t f@ runs function @f@ with HieFile specified by HieTarget @t@. In case the target is given by ModuleName (and optionally Unit) it is first resolved from HieDb, which can lead to error if given file is not indexed/Module name is ambiguous.@@ -184,8 +204,27 @@  type Vertex = (String, String, String, Int, Int, Int, Int) -declRefs :: HieDb -> IO ()-declRefs db = do+-- | Find a @'Res' 'DeclRow'@ by name and return it as a 'Vertex'+lookupVertex :: HieDb -> OccName -> Maybe ModuleName -> Maybe Unit -> IO (Either HieDbErr Vertex)+lookupVertex db occ mm muid =+  fmap declRowToVertex <$> findOneDecl db occ mm muid+ where+  declRowToVertex :: Res DeclRow -> Vertex+  declRowToVertex (dr :. mi) =+    ( moduleNameString $ modInfoName mi+    , declSrc dr+    , occNameToField $ declNameOcc dr+    , declSLine dr+    , declSCol dr+    , declELine dr+    , declECol dr+    )++  occNameToField :: OccName -> String+  occNameToField occ = toNsChar (occNameSpace occ) ++ occNameString occ++declRefs :: HieDb -> Maybe Vertex -> IO ()+declRefs db mv = do   graph <- getGraph db   writeFile     "refs.dot"@@ -197,9 +236,14 @@               ]           }         )-        graph+        (maybe id pruneToCallersOf mv graph)     ) +pruneToCallersOf :: Vertex -> AdjacencyMap Vertex -> AdjacencyMap Vertex+pruneToCallersOf v g = induce (`elem` vs) g+ where+  vs = reachable (transpose g) v+ getGraph :: HieDb -> IO (AdjacencyMap Vertex) getGraph (getConn -> conn) = do   es <-@@ -224,7 +268,7 @@      one :: Symbol -> IO [Vertex]     one s = do-      let n = toNsChar (occNameSpace $ symName s) : occNameString (symName s)+      let n = symName s           m = moduleNameString $ moduleName $ symModule s           u = unitString (moduleUnit $ symModule s)       query conn "SELECT mods.mod, decls.hieFile, decls.occ, decls.sl, decls.sc, decls.el, decls.ec \
src/HieDb/Run.hs view
@@ -5,6 +5,7 @@ {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ApplicativeDo #-} module HieDb.Run where  import Prelude hiding (mod)@@ -36,6 +37,7 @@ import Data.Foldable import Data.IORef import Data.List.Extra+import Data.Traversable (for)  import Numeric.Natural @@ -80,6 +82,7 @@   , reindex :: Bool   , keepMissing :: Bool   , srcBaseDir :: Maybe FilePath+  , skipIndexingOptions :: SkipOptions   }  data Command@@ -99,7 +102,7 @@   | TypesAtPoint HieTarget (Int,Int) (Maybe (Int,Int))   | DefsAtPoint  HieTarget (Int,Int) (Maybe (Int,Int))   | InfoAtPoint  HieTarget (Int,Int) (Maybe (Int,Int))-  | RefGraph+  | RefGraph (Maybe String) (Maybe ModuleName) (Maybe Unit)   | Dump FilePath   | Reachable [Symbol]   | Unreachable [Symbol]@@ -127,10 +130,28 @@   <*> switch (long "reindex" <> short 'r' <> help "Re-index all files in database before running command, deleting those with missing '.hie' files")   <*> switch (long "keep-missing" <> help "Keep missing files when re-indexing")   <*> optional (strOption (long "src-base-dir" <> help "Provide a base directory to index src files as real files"))+  <*> skipFlags   where     colourFlag = flag' True (long "colour" <> long "color" <> help "Force coloured output")             <|> flag' False (long "no-colour" <> long "no-color" <> help "Force uncoloured ouput")             <|> pure colr+    skipFlags = do+        refs <- switch (long "skip-refs" <> help "Skip refs table when indexing")+        decls <- switch (long "skip-decls" <> help "Skip decls table when indexing")+        defs <- switch (long "skip-defs" <> help "Skip defs table when indexing")+        exports <- switch (long "skip-exports" <> help "Skip exports table when indexing")+        imports <- switch (long "skip-imports" <> help "Skip imports table when indexing")+        types <- switch (long "skip-types" <> help "Skip types and typerefs table when indexing")+        typeRefs <- switch (long "skip-typerefs" <> help "Skip typerefs table when indexing")+        pure $ SkipOptions+          { skipRefs = refs+          , skipDecls = decls+          , skipDefs = defs+          , skipExports = exports+          , skipImports = imports+          , skipTypes = types+          , skipTypeRefs = typeRefs+          }  cmdParser :: Parser Command cmdParser@@ -185,7 +206,10 @@                             <*> posParser 'S'                             <*> optional (posParser 'E'))               $ progDesc "Print name, module name, unit id for symbol at point/span")-  <> command "ref-graph" (info (pure RefGraph) $ progDesc "Generate a reachability graph")+  <> command "ref-graph" (info (RefGraph <$> optional (strArgument (metavar "NAME"))+                                         <*> optional moduleNameParser+                                         <*> maybeUnitId)+                         $ progDesc "Generate a reachability graph")   <> command "dump" (info (Dump <$> strArgument (metavar "HIE")) $ progDesc "Dump a HIE AST")   <> command "reachable" (info (Reachable <$> some symbolParser)                          $ progDesc "Find all symbols reachable from the given symbols")@@ -222,7 +246,7 @@     Just w -> do       hPutStr hndl $ replicate w ' '       hPutStr hndl "\r"-      pure $ take (w-8) $ msg'+      pure $ take (w-8) msg'   liftIO $ hPutStr hndl msg   x <- act f   if x@@ -238,7 +262,7 @@    istart <- offsetTime   (length -> done, length -> skipped)<- runDbM nc $ partition id <$>-    zipWithM (\f n -> progress' h (length files) n (addRefsFrom conn (srcBaseDir opts)) f) files [0..]+    zipWithM (\f n -> progress' h (length files) n (addRefsFrom conn (srcBaseDir opts) (skipIndexingOptions opts)) f) files [0..]   indexTime <- istart    start <- offsetTime@@ -253,7 +277,6 @@   when (trace opts) $     setHieTrace conn (Just $ T.hPutStrLn stderr . ("\n****TRACE: "<>))   when (reindex opts) $ do-    initConn conn     files' <- map hieModuleHieFile <$> getAllIndexedMods conn     files <- fmap catMaybes $ forM files' $ \f -> do       exists <- doesFileExist f@@ -269,9 +292,8 @@       hPutStrLn stderr $ "Re-indexing " ++ show n ++ " files, deleting " ++ show (n-orig) ++ " files"     doIndex conn opts stderr files   case cmd of-    Init -> initConn conn+    Init -> pure ()     Index dirs -> do-      initConn conn       files <- concat <$> mapM getHieFilesIn dirs       doIndex conn opts stderr files     TypeRefs typ mn muid -> do@@ -372,11 +394,7 @@         reportAmbiguousErr opts (Left $ NoNameAtPoint target sp)       forM_ names $ \name -> do         case nameSrcSpan name of-#if __GLASGOW_HASKELL__ >= 900           RealSrcSpan dsp _ -> do-#else-          RealSrcSpan dsp -> do-#endif             unless (quiet opts) $               hPutStrLn stderr $ unwords ["Name", ppName opts (nameOccName name),"at",ppSpan opts sp,"is defined at:"]             contents <- case nameModule_maybe name of@@ -413,7 +431,11 @@     InfoAtPoint target sp mep -> hieFileCommand conn opts target $ \hf -> do       mapM_ (uncurry $ printInfo dynFlags) $ pointCommand hf sp mep $ \ast ->         (hieTypeToIface . flip recoverFullType (hie_types hf) <$> nodeInfo' ast, nodeSpan ast)-    RefGraph -> declRefs conn+    RefGraph mname mm muid -> do+      mv <- for mname $ \n -> do+        let ns = if isCons n then dataName else varName+        reportAmbiguousErr opts =<< lookupVertex conn (mkOccName ns n) mm muid+      declRefs conn mv     Dump path -> do       nc <- newIORef =<< makeNc       runDbM nc $ dump dynFlags path@@ -510,7 +532,7 @@                   beforeLines = takeEnd n beforeLines'                   afterLines  = take    n afterLines' -                  (beforeChars,during') = BS.splitAt (sc-1) $ BS.concat $ intersperse "\n" $ duringLines+                  (beforeChars,during') = BS.splitAt (sc-1) $ BS.concat $ intersperse "\n" duringLines                   (during,afterChars) = BS.splitAt (BS.length during' - (BS.length (last duringLines) - ec) - 1) during'                    before = BS.unlines beforeLines <> beforeChars
src/HieDb/Types.hs view
@@ -1,5 +1,4 @@ {-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE ViewPatterns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE BlockArguments #-}@@ -8,6 +7,7 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE CPP #-} {-# OPTIONS_GHC -Wno-orphans #-}+{-# LANGUAGE TypeApplications #-} module HieDb.Types where  import Prelude hiding (mod)@@ -15,10 +15,13 @@ import Data.IORef  import qualified Data.Text as T+import qualified Data.Text.Encoding as T +import Control.Exception+import Control.Monad (void)+import Control.Monad.Cont (ContT(..)) import Control.Monad.IO.Class import Control.Monad.Reader-import Control.Exception  import Data.List.NonEmpty (NonEmpty(..)) @@ -32,8 +35,82 @@  import HieDb.Compat -newtype HieDb = HieDb { getConn :: Connection }+data HieDb = HieDb+  { getConn :: !Connection+  , preparedStatements :: HieDbStatements+  } +-- | Record of prepared statements that are relevant during the slowest+-- operation, indexing.+--+-- See benchmarks in https://github.com/wz1000/HieDb/pull/86, where statement+-- preparation results in a ~28% improvement in indexing hie files generated+-- from HLS.+data HieDbStatements = HieDbStatements+  { insertModsStatement :: !(StatementFor HieModuleRow)+  , insertRefsStatement :: !(StatementFor RefRow)+  , insertDeclsStatement :: !(StatementFor DeclRow)+  , insertImportsStatement :: !(StatementFor ImportRow)+  , insertDefsStatement :: !(StatementFor DefRow)+  , insertExportsStatement :: !(StatementFor ExportRow)+  , insertTyperefsStatement :: !(StatementFor TypeRef)+  , insertTypenamesStatement :: !(StatementFor (OccName, ModuleName, Unit))+  , queryTypenamesStatement :: !(StatementFor (OccName, ModuleName, Unit))+  , deleteInternalTablesStatement :: !(Only FilePath -> IO ())+  }++-- | A type-safe wrapper connecting the query preparation with the datatype the+-- query is intended for. The type variable 'a' should correspond to the+-- datatype that will be bound to the statement that is wrapped.+newtype StatementFor a = StatementFor Statement++-- | Equivalent to unit so we can avoid parsing anything when doing insertions.+--+-- Many of our indexing operations are just simple inserts that don't return+-- results. We can't use the array of @sqlite-simple.execute@ functions as they+-- can't make use of the statements we've prepared.+data NoOutput = NoOutput++instance FromRow NoOutput where+  fromRow = pure NoOutput++-- | Helper for preparing multiple statements, each of which has to be+-- bracket-wrapped. Done via ContT, so we can use do/applicative notation.+createStatement :: Connection -> Query -> ContT r IO (StatementFor a)+createStatement connection query = fmap StatementFor (ContT (withStatement connection query))++-- | Run a statement that was built for an datatype in mind, ensuring that we do+-- indeed pass that datatype.+--+-- This function is preferably inlined so it'd get specialized with the datatype+-- encoder.+runStatementFor_ :: ToRow a => StatementFor a -> a -> IO ()+{-# INLINE runStatementFor_ #-}+runStatementFor_ (StatementFor statement) params = do+  withBind statement params $+    -- sqlite-simple doesn't offer the best interface for executing prepared+    -- insertions. `nextRow` requires a hint to know what it needs to parse if it+    -- encounters content from sqlite. Though this code path isn't hit as our+    -- insertions don't return anything, we still need to provide a parsable+    -- datatype. Bit of a leaky abstraction, can be cleaned up by using the+    -- lower-level `direct-sqlite` instead.+    void (nextRow @NoOutput statement)++-- | Run a statement that was built for an datatype in mind, ensuring that we do+-- indeed pass that datatype.+--+-- This function is preferably inlined so it'd get specialized with the datatype+-- encoder.+--+-- NB: While the input variable acts as a witness and is carried around, the+-- output variable is unconstrained. When using this function, double check that+-- the output type is what you expect it to be.+runStatementFor :: (ToRow a, FromRow b) => StatementFor a -> a -> IO (Maybe b)+{-# INLINE runStatementFor #-}+runStatementFor (StatementFor statement) params = do+  withBind statement params $+    nextRow statement+ data HieDbException   = IncompatibleSchemaVersion   { expectedVersion :: Integer, gotVersion :: Integer }@@ -90,32 +167,37 @@ instance FromField Fingerprint where   fromField fld = readHexFingerprint . T.unpack <$> fromField fld -toNsChar :: NameSpace -> Char+toNsChar :: NameSpace -> String toNsChar ns-  | isVarNameSpace ns = 'v'-  | isDataConNameSpace ns = 'c'-  | isTcClsNameSpace ns  = 't'-  | isTvNameSpace ns = 'z'+  | Just fld_par <- fieldNameSpace_maybe ns+  = ('f':unpackFS fld_par) ++ ":"+  | isVarNameSpace ns = "v:"+  | isDataConNameSpace ns = "c:"+  | isTcClsNameSpace ns  = "t:"+  | isTvNameSpace ns = "z:"   | otherwise = error "namespace not recognized" -fromNsChar :: Char -> Maybe NameSpace-fromNsChar 'v' = Just varName-fromNsChar 'c' = Just dataName-fromNsChar 't' = Just tcClsName-fromNsChar 'z' = Just tvName-fromNsChar _ = Nothing+fromNsChar :: T.Text -> Maybe NameSpace+fromNsChar ns+  | Just ('f',fieldNameSpace) <- T.uncons ns+  = Just (fieldName $ mkFastStringByteString $ T.encodeUtf8 fieldNameSpace)+fromNsChar "v" = Just varName+fromNsChar "c" = Just dataName+fromNsChar "t" = Just tcClsName+fromNsChar "z" = Just tvName+fromNsChar _   = Nothing  instance ToField OccName where-  toField occ = SQLText $ T.pack $ toNsChar (occNameSpace occ) : occNameString occ+  toField occ = SQLText $ T.pack $ toNsChar (occNameSpace occ) ++ occNameString occ instance FromField OccName where   fromField fld =     case fieldData fld of       SQLText t ->-        case T.uncons t of-          Just (nsChar,occ)-            | Just ns <- fromNsChar nsChar ->-              return $ mkOccName ns (T.unpack occ)-          _ -> returnError ConversionFailed fld "OccName encoding invalid"+        case T.break (== ':') t of+          (nsText,occ)+            | Just ns <- fromNsChar nsText ->+              return $ mkOccName ns (T.unpack $ T.tail occ)+          _ -> returnError ConversionFailed fld ("OccName encoding invalid: " ++ show t)       _ -> returnError Incompatible fld "Expected a SQL string representing an OccName"  data HieModuleRow@@ -145,15 +227,17 @@   , refSCol :: Int   , refELine :: Int   , refECol :: Int+  , refIsGenerated :: Bool -- ^ True if the reference to this name is generated by GHC (NodeOrigin is GeneratedInfo)+                           -- False if it comes from the source code (NodeOrigin is SourceInfo)   }  instance ToRow RefRow where-  toRow (RefRow a b c d e f g h) = toRow ((a,b,c):.(d,e,f):.(g,h))+  toRow (RefRow a b c d e f g h i) = toRow ((a,b,c):.(d,e,f):.(g,h,i))  instance FromRow RefRow where   fromRow = RefRow <$> field <*> field <*> field                    <*> field <*> field <*> field-                   <*> field <*> field+                   <*> field <*> field <*> field  data DeclRow   = DeclRow@@ -173,6 +257,25 @@   fromRow = DeclRow <$> field <*> field <*> field <*> field                     <*> field <*> field <*> field +data ImportRow+  = ImportRow+    { importSrc :: FilePath+    , importModuleName :: ModuleName+    , importSLine :: Int+    , importSCol :: Int+    , importELine :: Int+    , importECol :: Int+    }++instance FromRow ImportRow where+  fromRow =+    ImportRow+      <$> field <*> field <*> field <*> field+      <*> field <*> field++instance ToRow ImportRow where+  toRow (ImportRow a b c d e f) = toRow ((a,b,c,d):.(e,f))+ data TypeName = TypeName   { typeName :: OccName   , typeMod :: ModuleName@@ -265,22 +368,21 @@     } deriving (Eq, Ord)  instance Show Symbol where-    show s =  toNsChar (occNameSpace $ symName s)-           :  ':'-           :  occNameString (symName s)-           <> ":"-           <> moduleNameString (moduleName $ symModule s)-           <> ":"-        --    <> unitIdString (moduleUnit $ symModule s)-           <> unitString (moduleUnit $ symModule s)+    show s =     toNsChar (occNameSpace $ symName s)+              <> occNameString (symName s)+              <> ":"+              <> moduleNameString (moduleName $ symModule s)+              <> ":"+        --       <> unitIdString (moduleUnit $ symModule s)+              <> unitString (moduleUnit $ symModule s)  instance Read Symbol where   readsPrec = const $ R.readP_to_S readSymbol  readNameSpace :: R.ReadP NameSpace readNameSpace = do-  c <- R.get-  maybe R.pfail return (fromNsChar c)+  c <- R.many1 R.get+  maybe R.pfail return (fromNsChar $ T.pack c)  readColon :: R.ReadP () readColon = () <$ R.char ':'
src/HieDb/Utils.hs view
@@ -5,7 +5,6 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TupleSections #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE CPP #-} module HieDb.Utils where@@ -19,13 +18,11 @@ import qualified Compat.HieTypes as HieTypes import Compat.HieUtils import qualified Data.Map as M-+import qualified Data.Set as S  import System.Directory import System.FilePath -import Control.Arrow ( (&&&) )-import Data.Bifunctor ( bimap ) import Data.List (find) import Control.Monad.IO.Class import qualified Data.Array as A@@ -38,50 +35,69 @@  import HieDb.Types import HieDb.Compat-import Database.SQLite.Simple-import Control.Concurrent+import Control.Monad.State.Strict (StateT, get, put)+import qualified Data.IntSet as ISet+import qualified Data.IntMap.Strict as IMap+import Data.IntMap.Strict (IntMap)+import Data.IntSet (IntSet)+import Control.Monad (guard, unless)+import GHC.Builtin.Utils -addTypeRef :: HieDb -> FilePath -> A.Array TypeIndex HieTypeFlat -> A.Array TypeIndex (Maybe Int64) -> RealSrcSpan -> TypeIndex -> IO ()-addTypeRef (getConn -> conn) hf arr ixs sp = go 0+#if __GLASGOW_HASKELL__ >= 903+import Control.Concurrent.MVar (readMVar)+#endif++-- Each AST Node can have a lot of repetitive type information,+-- esp. when deriving is involved.+-- This StateT state keeps track of which types in AST we already indexed+-- to avoid having repeated (Depth, Type) inserted under the same RealSrcSpan.+--+-- This `IntMap IntSet` is morally `Map Depth (Set TypeIndex)` mapping+-- the depth (of a type within tree structure of types - ends up as typerefs.depth)+-- to a list of type IDs (identifiers of types assigned as id within typenames table)+-- that we already indexed.+type TypeIndexing a = StateT (IntMap IntSet) IO a++addTypeRef :: HieDb -> FilePath -> A.Array TypeIndex HieTypeFlat -> A.Array TypeIndex (Maybe Int64) -> RealSrcSpan -> TypeIndex -> TypeIndexing ()+addTypeRef hiedb hf arr ixs sp = go 0   where     sl = srcSpanStartLine sp     sc = srcSpanStartCol sp     el = srcSpanEndLine sp     ec = srcSpanEndCol sp-    go :: TypeIndex -> Int -> IO ()-    go d i = do+    go :: Int -> TypeIndex -> TypeIndexing ()+    go depth i = do       case ixs A.! i of         Nothing -> pure ()         Just occ -> do-          let ref = TypeRef occ hf d sl sc el ec-          execute conn "INSERT INTO typerefs VALUES (?,?,?,?,?,?,?)" ref-      let next = go (d+1)+          let ref = TypeRef occ hf depth sl sc el ec+          indexed <- get+          let isTypeIndexed = ISet.member (fromIntegral occ) (IMap.findWithDefault ISet.empty depth indexed)+          unless isTypeIndexed $ do+            liftIO $ runStatementFor_ (insertTyperefsStatement (preparedStatements hiedb)) ref+            put $ IMap.alter (\case+                  Nothing -> Just $ ISet.singleton (fromIntegral occ)+                  Just s -> Just $ ISet.insert (fromIntegral occ) s+                ) depth indexed+      let next = go (depth + 1)       case arr A.! i of         HTyVarTy _ -> pure ()-#if __GLASGOW_HASKELL__ >= 808         HAppTy x (HieArgs xs) -> mapM_ next (x:map snd xs)-#else-        HAppTy x y -> mapM_ next [x,y]-#endif         HTyConApp _ (HieArgs xs) -> mapM_ (next . snd) xs         HForAllTy ((_ , a),_) b -> mapM_ next [a,b]-#if __GLASGOW_HASKELL__ >= 900         HFunTy a b c -> mapM_ next [a,b,c]-#else-        HFunTy a b -> mapM_ next [a,b]-#endif         HQualTy a b -> mapM_ next [a,b]         HLitTy _ -> pure ()-        HCastTy a -> go d a+        HCastTy a -> go depth a         HCoercionTy -> pure ()  makeNc :: IO NameCache makeNc = do #if __GLASGOW_HASKELL__ >= 903-  initNameCache 'z' []+  initNameCache 'r' knownKeyNames #else-  uniq_supply <- mkSplitUniqSupply 'z'-  return $ initNameCache uniq_supply []+  uniq_supply <- mkSplitUniqSupply 'r'+  return $ initNameCache uniq_supply knownKeyNames #endif  -- | Recursively search for @.hie@ and @.hie-boot@  files in given directory@@ -122,11 +138,7 @@ #endif   return $ case lookupOrigNameCache nsns mdl occ of     Just name -> case nameSrcSpan name of-#if __GLASGOW_HASKELL__ >= 900       RealSrcSpan sp _ -> Right (sp, mdl)-#else-      RealSrcSpan sp -> Right (sp, mdl)-#endif       UnhelpfulSpan msg -> Left $ NameUnhelpfulSpan name (unpackFS $ unhelpfulSpanFS msg)     Nothing -> Left $ NameNotFound occ (Just $ moduleName mdl) (Just $ moduleUnit mdl) @@ -143,18 +155,10 @@  dynFlagsForPrinting :: LibDir -> IO DynFlags dynFlagsForPrinting (LibDir libdir) = do-  systemSettings <- initSysTools-#if __GLASGOW_HASKELL__ >= 808-                    libdir-#else-                    (Just libdir)-#endif-#if __GLASGOW_HASKELL__ >= 905+  systemSettings <- initSysTools libdir   return $ defaultDynFlags systemSettings-#elif __GLASGOW_HASKELL__ >= 810-  return $ defaultDynFlags systemSettings $ LlvmConfig [] []-#else-  return $ defaultDynFlags systemSettings ([], [])+#if __GLASGOW_HASKELL__ < 905+    (LlvmConfig [] []) #endif  isCons :: String -> Bool@@ -162,16 +166,31 @@ isCons (x:_) | isUpper x = True isCons _ = False -genRefsAndDecls :: FilePath -> Module -> M.Map Identifier [(Span, IdentifierDetails a)] -> ([RefRow],[DeclRow])-genRefsAndDecls path smdl refmap = genRows $ flat $ M.toList refmap+data AstInfo =+  AstInfo+    { astInfoRefs :: [RefRow]+    , astInfoDecls :: [DeclRow]+    , astInfoImports :: [ImportRow]+    }++instance Semigroup AstInfo where+  AstInfo r1 d1 i1 <> AstInfo r2 d2 i2 = AstInfo (r1 <> r2) (d1 <> d2) (i1 <> i2)++instance Monoid AstInfo where+  mempty = AstInfo [] [] []++genAstInfo :: FilePath -> Module -> NodeOrigin -> M.Map Identifier [(Span, IdentifierDetails a)] -> AstInfo+genAstInfo path smdl nodeOrigin refmap = genRows $ flat $ M.toList refmap   where+    isGenerated = nodeOrigin == GeneratedInfo     flat = concatMap (\(a,xs) -> map (a,) xs)-    genRows = foldMap go-    go = bimap maybeToList maybeToList . (goRef &&& goDec)+    genRows = foldMap mkAstInfo +    mkAstInfo x = AstInfo (maybeToList $ goRef x) (maybeToList $ goDec x) (maybeToList $ goImport x)+     goRef (Right name, (sp,_))       | Just mod <- nameModule_maybe name = Just $-          RefRow path occ (moduleName mod) (moduleUnit mod) sl sc el ec+          RefRow path occ (moduleName mod) (moduleUnit mod) sl sc el ec isGenerated           where             occ = nameOccName name             sl = srcSpanStartLine sp@@ -180,6 +199,16 @@             ec = srcSpanEndCol sp     goRef _ = Nothing +    goImport (Left modName, (sp, IdentifierDetails _ contextInfos)) = do+          _ <- guard $ not $ S.disjoint contextInfos $ S.fromList [IEThing Import, IEThing ImportAs, IEThing ImportHiding]+          let+            sl = srcSpanStartLine sp+            sc = srcSpanStartCol sp+            el = srcSpanEndLine sp+            ec = srcSpanEndCol sp+          Just $ ImportRow path modName sl sc el ec+    goImport _ = Nothing+     goDec (Right name,(_,dets))       | Just mod <- nameModule_maybe name       , mod == smdl@@ -211,11 +240,7 @@   where     genRows = mapMaybe go     getSpan name dets-#if __GLASGOW_HASKELL__ >= 900       | RealSrcSpan sp _ <- nameSrcSpan name = Just sp-#else-      | RealSrcSpan sp <- nameSrcSpan name = Just sp-#endif       | otherwise = do           (sp, _dets) <- find defSpan dets           pure sp
test/Main.hs view
@@ -1,13 +1,15 @@ {-# LANGUAGE CPP #-} module Main where +import qualified Algebra.Graph.AdjacencyMap as AdjacencyMap+import Data.Bifunctor (bimap) import GHC.Paths (libdir, ghc)-import HieDb (HieDb, HieModuleRow (..), LibDir (..), ModuleInfo (..), withHieDb, withHieFile, addRefsFromLoaded, deleteMissingRealFiles)-import HieDb.Query (getAllIndexedMods, lookupHieFile, resolveUnitId, lookupHieFileFromSource)+import HieDb (HieDb, HieModuleRow (..), LibDir (..), ModuleInfo (..), withHieDb, withHieFile, addRefsFromLoaded, deleteMissingRealFiles, defaultSkipOptions)+import HieDb.Query (Vertex, getAllIndexedMods, lookupHieFile, resolveUnitId, lookupHieFileFromSource, lookupVertex, getGraph, pruneToCallersOf) import HieDb.Run (Command (..), Options (..), runCommand) import HieDb.Types (HieDbErr (..), SourceFile(..), runDbM) import HieDb.Utils (makeNc)-import HieDb.Compat (stringToUnit, moduleNameString, mkModuleName, getFileHash)+import HieDb.Compat (stringToUnit, moduleNameString, mkModuleName, getFileHash, mkOccName, varName) import System.Directory (findExecutable, getCurrentDirectory, removeDirectoryRecursive) import System.Exit (ExitCode (..), die) import System.FilePath ((</>))@@ -88,6 +90,7 @@             -- Index a new real file, and delete it             let contents = unlines                   [ "module Test123 where"+                  , "import Prelude"                   , "foobarbaz :: Int"                   , "foobarbaz = 1"                   ]@@ -124,7 +127,29 @@             afterMods <- getAllIndexedMods conn             originalMods `shouldBe` afterMods +        describe "pruneToCallersOf" $ do+          it "prunes the graph to recursive callers of an identifier" $ \conn -> do+            result <- lookupVertex conn (mkOccName varName "showInt") Nothing Nothing +            case result of+              Left _ -> fail "Failed to lookup vertex for v:showInt"+              Right v -> do+                g <- getGraph conn++                let+                  renderFn :: Vertex -> String+                  renderFn (modName, _, fnName, _, _, _, _) = modName <> "." <> fnName++                  callers =+                      map (bimap renderFn (map renderFn))+                        $ AdjacencyMap.adjacencyList+                        $ pruneToCallersOf v g++                callers `shouldBe`+                  [ ("Module1.v:function2", ["Sub.Module2.v:showInt"])+                  , ("Sub.Module2.v:showInt", [])+                  ]+ cliSpec :: Spec cliSpec =   -- TODO commands not covered: init, type-refs, ref-graph, dump, reachable, unreachable, html@@ -143,7 +168,7 @@               ]         (exitCode, actualStdin, _actualStdErr) <- runHieDbCli ["ls"]         exitCode `shouldBe` ExitSuccess-        (sort $ lines actualStdin) `shouldBe` (sort expectedOutput)+        sort (lines actualStdin) `shouldBe` sort expectedOutput      describe "name-refs" $       it "lists all references of given function" $ do@@ -167,8 +192,7 @@       it "Prints types of symbol under cursor" $         runHieDbCli ["point-types", "Module1", "10", "10" ]           `succeedsWithStdin` unlines-            [ "Int -> Bool" {- types of `even` function under cursor -}-            , "forall a. Integral a => a -> Bool"+            [ "Bool -> Bool" {- type of `not` function under cursor -}             ]       it "Fails for symbols that don't have type associated" $ do         (exitCode, actualStdout, actualStderr) <- runHieDbCli ["point-types", "Module1", "8", "21"]@@ -180,7 +204,7 @@       it "outputs the location of symbol when definition site can be found is indexed" $         runHieDbCli ["point-defs", "Module1", "13", "29"]           `succeedsWithStdin` unlines-            [ "Sub.Module2:7:1-7:8"+            [ "Sub.Module2:8:1-8:8"             ]       it "Fails with informative error message when there's no symbol at given point" $ do         (exitCode, actualStdout, actualStderr) <- runHieDbCli ["point-defs", "Module1", "13", "13"]@@ -192,60 +216,52 @@         (exitCode, actualStdout, actualStderr) <- runHieDbCli ["point-defs", "Module1", "13", "24"]         actualStdout `shouldBe` ""         exitCode `shouldBe` ExitFailure 1-        actualStderr `shouldBe` "Couldn't find name: $ from module GHC.Base(base)\n"+        actualStderr `shouldBe`+#if MIN_VERSION_base(4,20,0)+          "Couldn't find name: $ from module GHC.Internal.Base(ghc-internal)\n"+#else+          "Couldn't find name: $ from module GHC.Base(base)\n"+#endif      describe "point-info" $ do       it "gives information about symbol at specified location" $-        runHieDbCli ["point-info", "Sub.Module2", "10", "10"]+        runHieDbCli ["point-info", "Sub.Module2", "11", "11"]           `succeedsWithStdin` unlines-            [ "Span: test/data/Sub/Module2.hs:10:7-23"+            [ "Span: test/data/Sub/Module2.hs:11:7-23"             , "Constructors: {(ConDeclH98, ConDecl)}"             , "Identifiers:"             , "Symbol:c:Data1Constructor1:Sub.Module2:main"-            , "Data1Constructor1 defined at test/data/Sub/Module2.hs:10:7-23"-#if __GLASGOW_HASKELL__ >= 900-            , "    Details:  Nothing {declaration of constructor bound at: test/data/Sub/Module2.hs:10:7-23}"-#else-            , "    IdentifierDetails Nothing {Decl ConDec (Just SrcSpanOneLine \"test/data/Sub/Module2.hs\" 10 7 24)}"-#endif+            , "Data1Constructor1 defined at test/data/Sub/Module2.hs:11:7-23"+            , "    Details:  Nothing {declaration of constructor bound at: test/data/Sub/Module2.hs:11:7-23}"             , "Types:\n"             ]       it "correctly prints type signatures" $         runHieDbCli ["point-info", "Module1", "10", "10"]           `succeedsWithStdin` unlines-            [ "Span: test/data/Module1.hs:10:8-11"-#if __GLASGOW_HASKELL__ >= 902-            , "Constructors: {(XExpr, HsExpr), (HsVar, HsExpr)}"-#elif __GLASGOW_HASKELL__ >= 900-            , "Constructors: {(HsVar, HsExpr), (XExpr, HsExpr)}"-#else-            , "Constructors: {(HsVar, HsExpr), (HsWrap, HsExpr)}"-#endif+            [ "Span: test/data/Module1.hs:10:8-10"+            , "Constructors: {(HsVar, HsExpr)}"             , "Identifiers:"-            , "Symbol:v:even:GHC.Real:base"-            , "even defined at <no location info>"-#if __GLASGOW_HASKELL__ >= 900-            , "    Details:  Just forall a. Integral a => a -> Bool {usage}"-            , "$dIntegral defined at <no location info>"-            , "    Details:  Just Integral Int {usage of evidence variable}"+#if MIN_VERSION_base(4,22,0)+            , "Symbol:v:not:GHC.Internal.Classes:ghc-internal" #else-            , "    IdentifierDetails Just forall a. Integral a => a -> Bool {Use}"+            , "Symbol:v:not:GHC.Classes:ghc-prim" #endif+            , "not defined at <no location info>"+            , "    Details:  Just Bool -> Bool {usage}"             , "Types:"-            , "Int -> Bool"-            , "forall a. Integral a => a -> Bool"+            , "Bool -> Bool"             , ""             ]      describe "name-def" $       it "lookup definition of name" $         runHieDbCli ["name-def", "showInt"]-          `succeedsWithStdin` "Sub.Module2:7:1-7:8\n"+          `succeedsWithStdin` "Sub.Module2:8:1-8:8\n"      describe "type-def" $       it "lookup definition of type" $         runHieDbCli ["type-def", "Data1"]-          `succeedsWithStdin` "Sub.Module2:9:1-11:28\n"+          `succeedsWithStdin` "Sub.Module2:10:1-12:28\n"      describe "cat" $       describe "dumps module source stored in .hie file" $ do@@ -371,4 +387,5 @@   , reindex = False   , keepMissing = False   , srcBaseDir = Nothing+  , skipIndexingOptions = defaultSkipOptions   }
test/data/Module1.hs view
@@ -5,9 +5,9 @@  import Sub.Module2 (showInt) -function1 :: Int -> String-function1 i =-    if even i then "It's even" else "It's odd"+function1 :: Bool -> String+function1 b =+    if not b then "It's false" else "It's true"  function2 :: Int -> IO () function2 i = putStrLn $ showInt i
test/data/Sub/Module2.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE DuplicateRecordFields #-} module Sub.Module2     ( showInt     , Data1(..)@@ -9,3 +10,9 @@ data Data1     = Data1Constructor1     | Data1Constructor2 Int++data Data2+    = Data2C { foo :: Int, bar :: String }++data Data3+    = Data3C { foo :: Int, baz :: Bool }