packages feed

glean-0.1.0.0: glean/glass/Glean/Glass/Neighborhood.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 Glean.Glass.Neighborhood ( searchNeighborhood) where

import Data.Ord ( comparing )
import Data.Text ( Text )
import qualified Data.Map.Strict as Map

import Util.List ( uniq, uniqBy )

import Glean.Haxl.Repos as Glean ( withRepo, RepoHaxl, ReposHaxl )

import qualified Glean.Schema.Code.Types as Code

import Glean.Glass.Utils ( fst4 )
import Glean.Glass.Describe
    ( mkSymbolDescription, mkBriefSymbolDescription )
import Glean.Glass.Repos ( Language(Language_Hack), ScmRevisions )
import Glean.Glass.Types
    ( SymbolId(SymbolId),
      RelatedNeighborhoodRequest(..),
      SymbolDescription,
      RelatedNeighborhoodResult(..),
      InheritedSymbols(..),
      RelatedSymbols(RelatedSymbols),
      RelationDirection(RelationDirection_Parent,
                        RelationDirection_Child),
      RelationType(RelationType_Extends, RelationType_Contains,
                   RelationType_RequireExtends, RelationType_RequireImplements,
                   RelationType_RequireClass),
      RepoName,
      SymbolBasicDescription,
      SymbolDescription(..) )
import Glean.Glass.Search as Search
    ( CodeEntityLocation(CodeEntityLocation, entityName, entityRange,
                         entityFile, entity),
      SearchEntity(..) )
import Glean.Glass.SearchRelated (InheritedContainer, LocatedEntity)
import qualified Glean.Glass.SearchRelated as Search
import Glean.Glass.Search.Class ( ResultLocation )

type SearchRelatedQuery u w = Code.Entity -> RepoName
      -> RepoHaxl u w [Search.RelatedLocatedEntities]
type SearchRelatedList u w = Code.Entity -> RepoName
      -> RepoHaxl u w [Search.LocatedEntity]

-- being careful to keep things with clear data dependencies
searchNeighborhood
  :: Int
  -> RelatedNeighborhoodRequest
  -> SymbolId
  -> RepoName
  -> ScmRevisions -> Language -> SearchEntity (ResultLocation Code.Entity)
  -> ReposHaxl u w RelatedNeighborhoodResult
searchNeighborhood limit
    RelatedNeighborhoodRequest{..} sym repo scmRevs lang baseEntity =
  withRepo (entityRepo baseEntity) $ do
    let entity = fst4 $ decl baseEntity
    a <- childrenContains1Level
          (fromIntegral relatedNeighborhoodRequest_children_limit) entity repo
    b <- childrenExtends1Level
          (fromIntegral relatedNeighborhoodRequest_inherited_limit) entity repo
    c <- parentContainsNLevel
          (fromIntegral relatedNeighborhoodRequest_parent_depth) entity repo
    d <- parentExtends1Level limit entity repo
    e <- parentRequire RelationType_RequireExtends limit entity repo
    f <- parentRequire RelationType_RequireImplements limit entity repo
    g <- parentRequire RelationType_RequireClass limit entity repo

    (parents, members, inheritedSyms) <- inheritedSymbols lang limit
      (fromIntegral relatedNeighborhoodRequest_inherited_limit)
      (if relatedNeighborhoodRequest_hide_uninteresting
        then Search.HideUninteresting
        else Search.ShowAll)
      entity repo
    -- syms visible to the client, we need their full details
    let !syms = uniqBy (comparing snd) $ fromSearchEntity sym baseEntity :
            a ++ flattenEdges c ++ members
              -- full descriptions of final methods
    descriptions <- Map.fromAscList <$> mapM (mkDescribe repo scmRevs) syms
    -- brief descriptions for inherited things
    basics <- Map.fromAscList <$> mapM (mkBriefDescribe repo scmRevs)
      (uniqBy (comparing snd) $ (b ++ d ++ e ++ f ++ g) ++ parents)

    return $ RelatedNeighborhoodResult {
        relatedNeighborhoodResult_childrenContained = map snd a,
        relatedNeighborhoodResult_childrenExtended = map snd b,
        relatedNeighborhoodResult_containsParents = symbolIdPairs c,
        relatedNeighborhoodResult_parentsExtended = map snd d,
        relatedNeighborhoodResult_inheritedSymbols = inheritedSyms,
        relatedNeighborhoodResult_symbolDetails = descriptions,
        relatedNeighborhoodResult_symbolBasicDetails = basics,
        relatedNeighborhoodResult_requireExtends = map snd e,
        relatedNeighborhoodResult_requireImplements = map snd f,
        relatedNeighborhoodResult_requireClass = map snd g
      }

-- building map of sym id -> descriptions, by first occurence
mkDescribe
  :: RepoName
  -> ScmRevisions
  -> Search.LocatedEntity
  -> Glean.RepoHaxl u w (Text, SymbolDescription)
mkDescribe repo scmRevs e@(_,SymbolId rawSymId) =
  (rawSymId,) <$> describe repo scmRevs e

describe
   :: RepoName
  -> ScmRevisions
  -> Search.LocatedEntity
  -> Glean.RepoHaxl u w SymbolDescription
describe repo scmRevs ((entity, entityFile, entityRange, entityName), symId)
  = mkSymbolDescription symId scmRevs repo CodeEntityLocation{..} Nothing

-- for children by inheritance, we only need name, kind, parent name, sym id
-- and signature. In particular, we don't e.g. need comments or annotations
-- or type xrefs
mkBriefDescribe
  :: RepoName
  -> ScmRevisions
  -> Search.LocatedEntity
  -> Glean.RepoHaxl u w (Text, SymbolBasicDescription)
mkBriefDescribe repo scmRevs e@(_,SymbolId rawSymId) =
  (rawSymId,) <$> briefDescribe repo scmRevs e

briefDescribe
  :: RepoName
  -> ScmRevisions
  -> Search.LocatedEntity
  -> Glean.RepoHaxl u w SymbolBasicDescription
briefDescribe repo scmRevs
    ((entity, entityFile, entityRange, entityName), symId)
  = mkBriefSymbolDescription symId scmRevs repo CodeEntityLocation{..}
    Nothing

childrenContains1Level :: Int -> SearchRelatedList u w
childrenContains1Level limit baseEntity repo = map Search.childRL <$>
  Search.searchRelatedEntities
    limit
    Search.ShowAll
    Search.NotRecursive
    RelationDirection_Child
    RelationType_Contains
    baseEntity
    repo

-- children by `extends`. typically a list. this includes method overrides
-- note that some methods have a lot of overrides (50k+) so be careful with
-- the limit values
childrenExtends1Level :: Int -> SearchRelatedList u w
childrenExtends1Level limit baseEntity repo = map Search.childRL <$>
  Search.searchRelatedEntities
    limit
    Search.ShowAll
    Search.NotRecursive
    RelationDirection_Child
    RelationType_Extends
    baseEntity
    repo

-- Direct inheritance parents
parentExtends1Level :: Int -> SearchRelatedList u w
parentExtends1Level limit baseEntity repo = map Search.parentRL <$>
  Search.searchRelatedEntities
    limit
    Search.ShowAll
    Search.NotRecursive
    RelationDirection_Parent
    RelationType_Extends
    baseEntity
    repo

-- N levels of container hierarchy
parentContainsNLevel :: Int -> SearchRelatedQuery u w
parentContainsNLevel limit baseEntity repo = Search.searchRelatedEntities
  limit
  Search.ShowAll
  Search.Recursive
  RelationDirection_Parent
  RelationType_Contains
  baseEntity
  repo

--  Returns (recursively) parent entities, inherited members
--  and inherited symbols of a container entity
--
--  If not directly available in DB computes an over approximation
--  of inherited methods computed by unioning the contained children
--  of inheritedLimit levels of extended parents
inheritedSymbols
  :: Language -> Int -> Int -> Search.SearchStyle -> Code.Entity -> RepoName
  -> RepoHaxl u w ([LocatedEntity], [LocatedEntity], [InheritedSymbols])

-- Optimized implementation for Hack: all members inherited into scope by parent
inheritedSymbols Language_Hack
  memberLimit _inheritedLimit style entity repo = do
    inherited <- Search.searchInheritedEntities style memberLimit entity repo
    let parents = map fst inherited
        members = concatMap snd inherited
        inheritedSyms = map inheritedSymbolIdSets inherited
    return (parents, members, inheritedSyms)

-- Fall back implementation: enumerate inherited parents, and find their members
inheritedSymbols _ memberLimit inheritedLimit style entity repo = do
    topoEdges <- Search.searchRelatedEntities  -- "extend relationship"
      inheritedLimit
      style
      Search.Recursive
      RelationDirection_Parent
      RelationType_Extends
      entity
      repo
    let parents = uniq (map Search.parentRL topoEdges)
    inherited <- mapM (childrenOf memberLimit repo) parents
    let inheritedSyms = map inheritedSymbolIdSets inherited
    let members = concatMap snd inherited
    return (parents, members, inheritedSyms)

-- Fetch the "required" parent entities
-- This only exists for Hack entity so we can avoid
-- useless queries for other languages
parentRequire :: RelationType -> Int -> SearchRelatedList u w
parentRequire relationType limit baseEntity repo =
  case baseEntity of
    Code.Entity_hack _ ->
        map Search.parentRL <$>
          Search.searchRelatedEntities
            limit
            Search.ShowAll
            Search.NotRecursive
            RelationDirection_Parent
            relationType
            baseEntity
            repo
    _ -> return []

childrenOf
  :: Int -> RepoName -> Search.LocatedEntity
  -> Glean.RepoHaxl u w (Search.LocatedEntity, [Search.LocatedEntity])
childrenOf limit repo parent = (parent,) <$>
  childrenContains1Level limit (toEntity parent) repo

toEntity :: Search.LocatedEntity -> Code.Entity
toEntity ((entity, _file, _rangespan, _name), _symId) = entity

symbolIdPairs :: [Search.RelatedLocatedEntities] -> [RelatedSymbols]
symbolIdPairs = map (\Search.RelatedLocatedEntities{..} ->
  RelatedSymbols (snd parentRL) (snd childRL) Nothing)

inheritedSymbolIdSets :: InheritedContainer -> InheritedSymbols
inheritedSymbolIdSets (parent, children) = InheritedSymbols {
    inheritedSymbols_base = snd parent,
    inheritedSymbols_provides = map snd children
  }

fromSearchEntity
  :: SymbolId
  -> SearchEntity (ResultLocation Code.Entity)
  -> Search.LocatedEntity
fromSearchEntity symId SearchEntity{..} = (decl, symId)

flattenEdges :: [Search.RelatedLocatedEntities] -> [Search.LocatedEntity]
flattenEdges pairs = concat
  [ [ e1, e2 ] | Search.RelatedLocatedEntities e1 e2 <- pairs ]