glean-0.1.0.0: glean/lang/lsif/Data/LSIF/Env.hs
{-
Copyright (c) Meta Platforms, Inc. and affiliates.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree.
-}
module Data.LSIF.Env (
Env(..),
FileDefs(..),
emptyEnv,
-- some silly type tags
tagFile,
tagResultSet,
tagDefinitions,
-- tags as values
Id(..),
Id_,
VertexType(..),
ResultSetTy,
FileTy,
DefinitionTy,
IdVector,
-- modify env
appendRoot,
insertType,
insertTypes,
addToResultSet,
addToDefinitionFile,
addHoverToResultSet,
addMonikerToResultSet,
insertSymbolKind,
addFileContainsIds,
shareDefinitionFile,
-- lookups in env
getTypeOf,
getResultSetOf,
getDefinitionFile,
getHoverTextId,
getMonikerId,
getSymbolKind,
-- running a parser and getting results
Parse,
) where
import Control.Monad.State.Strict
import Data.Int ( Int64 )
import Data.IntMap ( IntMap )
import Data.Text ( Text )
import qualified Data.IntMap.Strict as IMap
import qualified Data.Vector as V
import qualified Data.Vector.Unboxed as U
import Data.LSIF.Gen ( SymbolKind, Id(..) )
-- Environment used to track things when we analyze the LSIF graph.
-- We can mostly process LSIF linearly, but for xrefs and related predicates
-- we track a few chains of values.
--
-- We also need to track types of identifiers (decls, refs, defns, files,
-- projects, and synthetic lsif node types, to distinguish things)
--
--
-- LSIF represents xrefs and target uses with cyclical graphs. Groups of related
-- vertices are shared via "result set" nodes, to which they point.
-- definitionResult or other nodes will refer to these result sets.
--
-- To embed into Glean, we process in two steps:
-- - first, find all anchor facts (files, ranges) and capture all edges between
-- files, refs, defs, result sets
-- - then, using that complete graph environment, extract ref -> (file,def)
-- pairs to generate File-keyed definition, hover and xref facts
--
-- We can't rely on ordering between different vertex and edge types in LSIF,
-- only that facts will be defined before they are used.
--
-- | Parser env, to track command statements in LSIF that change scope
data Env =
Env {
root :: [Text], -- ^ a list of path prefixes to strip from file paths
-- type environment. We often learn the inferred type of an id in its use
typeEnv :: !(IntMap VertexType),
-- tracking graph edges we care about
-- "next" edges associate ranges (defs, decls or xres) with resultsets
resultSet :: !(IntMap (Id_ ResultSetTy)),
-- textDocument/definition, edge from resultset back to ([def range], file)
definitionFile :: !(IntMap FileDefs),
-- textDocument/hover, edge from resultset to hovertext/lang
hoverText :: !(IntMap (Id_ HoverTextTy)),
-- symbol, file and project "moniker" records
moniker :: !(IntMap (Id_ MonikerTy)),
-- associate definition rangeids with their kind
symbolKinds :: !(IntMap SymbolKind),
-- track file "contains" edge associations from file to any ranges
fileContains :: !(IntMap [IdVector])
}
-- | Run the fact generate with state Env
type Parse a = forall m . Monad m => StateT Env m a
-- It is useful to record the type of ids along the way
-- We can infer these types from the use of the id, as edges lightly typed
data VertexType
= ProjectType -- lsif.Project
| FileType -- src.File / lsif.Document
| DefinitionType
| ReferenceType -- Item property:references or tag:reference
| DeclarationType
deriving (Eq, Show)
-- Payload for an xref is a file id and the id of the definition range
data FileDefs
= FileDefs
{-# UNPACK #-}!(Id_ FileTy)
!IdVector
-- | vectors of ids can be stored a bit more compactly
type IdVector = U.Vector Int64
fill :: V.Vector Id -> IdVector
fill v = U.generate (V.length v) (\i -> case v `V.unsafeIndex` i of Id n -> n)
-- tag the Id with a type when we put it in the env
type Id_ a = Id
data DefinitionTy
-- data DeclarationTy
-- data ReferenceTy
data ResultSetTy
data FileTy
data HoverTextTy
data MonikerTy
data RangeTy
tagResultSet :: Id -> Id_ ResultSetTy
tagResultSet = id
tagFile :: Id -> Id_ FileTy
tagFile = id
tagDefinitions :: V.Vector Id -> V.Vector (Id_ DefinitionTy)
tagDefinitions = id
-- Build up some lookup tables for the assoc lists between refs/defs/decls/files
-- to aid in generaing flatter angle facts
emptyEnv :: Env
emptyEnv = Env [] IMap.empty IMap.empty IMap.empty
IMap.empty IMap.empty IMap.empty IMap.empty
appendRoot :: Text -> Parse ()
appendRoot path = modify' (\e -> e { root = path : root e })
insertType :: Id -> VertexType -> Parse ()
insertType (Id n) ty = modify' $ \e -> e { typeEnv =
IMap.insert (fromIntegral n) ty (typeEnv e)
}
-- Add a bunch of types in one go
insertTypes :: V.Vector Id -> VertexType -> Parse ()
insertTypes ids ty = modify' $ \e -> e { typeEnv = IMap.union imap (typeEnv e) }
where
imap = IMap.fromAscList $ V.toList $
V.map (\(Id i) -> (fromIntegral i, ty)) ids
getTypeOf :: Id -> Parse (Maybe VertexType)
getTypeOf = lookupIMapEnv typeEnv
addToResultSet :: Id -> Id_ ResultSetTy -> Parse ()
addToResultSet (Id n) resultSetId = modify' $ \e ->
e { resultSet = IMap.insert (fromIntegral n) resultSetId (resultSet e) }
getResultSetOf :: Id -> Parse (Maybe (Id_ ResultSetTy))
getResultSetOf = lookupIMapEnv resultSet
addToDefinitionFile
:: Id_ ResultSetTy -> Id_ FileTy -> V.Vector (Id_ DefinitionTy) -> Parse ()
addToDefinitionFile (Id resultSetId) fileId defIds = modify' $ \e ->
e { definitionFile = IMap.insert
(fromIntegral resultSetId)
(FileDefs fileId (fill defIds)) (definitionFile e)
}
-- like addToDefinition but shares a reference (i.e. another edge) to exixting
-- file defs. We can use this to walk chains of resultsets/definition results
shareDefinitionFile :: Id_ ResultSetTy -> FileDefs -> Parse ()
shareDefinitionFile (Id resultSetId) fileDefs = modify' $ \e ->
e { definitionFile = IMap.insert
(fromIntegral resultSetId) fileDefs (definitionFile e)
}
getDefinitionFile :: Id_ ResultSetTy -> Parse (Maybe FileDefs)
getDefinitionFile = lookupIMapEnv definitionFile
addHoverToResultSet :: Id_ HoverTextTy -> Id_ ResultSetTy -> Parse ()
addHoverToResultSet hoverId (Id resultSetId) = modify' $ \e ->
e { hoverText = IMap.insert
(fromIntegral resultSetId) hoverId (hoverText e)
}
getHoverTextId :: Id_ ResultSetTy -> Parse (Maybe (Id_ HoverTextTy))
getHoverTextId = lookupIMapEnv hoverText
addMonikerToResultSet :: Id_ MonikerTy -> Id_ ResultSetTy -> Parse ()
addMonikerToResultSet monikerId (Id resultSetId) = modify' $ \e ->
e { moniker = IMap.insert
(fromIntegral resultSetId) monikerId (moniker e)
}
getMonikerId :: Id_ MonikerTy -> Parse (Maybe (Id_ HoverTextTy))
getMonikerId = lookupIMapEnv moniker
insertSymbolKind :: Id_ RangeTy -> SymbolKind -> Parse ()
insertSymbolKind (Id rangeId) kind = modify' $ \e ->
e { symbolKinds = IMap.insert
(fromIntegral rangeId) kind (symbolKinds e)
}
getSymbolKind :: Id -> Parse (Maybe SymbolKind)
getSymbolKind = lookupIMapEnv symbolKinds
addFileContainsIds :: Id_ FileTy -> V.Vector Id -> Parse ()
addFileContainsIds (Id fileId) contents = modify' $ \e ->
e { fileContains = IMap.insertWith (<>)
(fromIntegral fileId) [fill contents] (fileContains e)
}
lookupIMapEnv :: (Env -> IntMap a) -> Id -> Parse (Maybe a)
lookupIMapEnv f (Id n) = do
imap <- f <$> get
pure (IMap.lookup (fromIntegral n) imap)