packages feed

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)