content: {-
    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.
  -}
  
  {-# LANGUAGE TypeApplications #-}
  {-# LANGUAGE ApplicativeDo #-}
  
  module Glean.Glass.Query.Cxx
    (
      documentSymbolsForCxx,
      fileIncludeLocationsForCxx,
      usrHashToDeclaration,
      usrHashToXRefs,
      usrToDeclaration,
    ) where
  
  import Data.Maybe ( catMaybes )
  import Data.Map.Strict ( Map )
  import qualified Data.Map as Map
  import Data.Text (Text)
  import Safe (atMay)
  import Util.List ( uniq )
  
  import qualified Glean
  import Glean.Angle
  import Glean.Haxl.Repos as Glean
  
  import qualified Glean.Schema.CodemarkupCxx.Types as Code
  import qualified Glean.Schema.CodemarkupPp.Types as Code
  import qualified Glean.Schema.CodemarkupTypes.Types as Code
  import qualified Glean.Schema.Code.Types as Code
  import qualified Glean.Schema.CodeCxx.Types as Cxx
  import qualified Glean.Schema.CodePp.Types as Pp
  import qualified Glean.Schema.Cxx1.Types as Cxx
  import qualified Glean.Schema.Src.Types as Src
  import qualified Glean.Schema.CodeScip.Types as Scip
  import qualified Glean.Schema.CodemarkupScip.Types as Scip
  import Glean.Util.ToAngle ( ToAngle(toAngle) )
  import qualified Glean.Util.Range as Range
  
  import Glean.Glass.XRefs ( XRef, GenXRef(..), fetchCxxIdlXRefs )
  import Glean.Glass.Utils
  import qualified Glean.Schema.Codemarkup.Types as Code
  
  --
  -- The cxx1 schema is complicated for xrefs and decls, as files have multiple
  -- "traces" that instantiate them.
  --
  -- These are represented by cxx1.Trace values, and cxx1.FileXRef facts
  --
  -- We pick one trace and filexref set to start with, and use that to
  -- consistently index.
  --
  -- Definitions in file:
  -- * codemarkup.{cxx,pp}.{Cxx,Pp}ResolveTraceLocations
  --
  -- Computing xrefs:
  -- * fixed xrefs
  -- * variable xrefs
  -- * uses of decls to their defs
  -- * decls to def xrefs
  -- * def to decl xrefs
  --
  -- A frequent source of problems is enumerating too many traces and filexrefs
  --
  -- We also have a set of different kind of symbols
  -- * declarations, definitions, enumerators
  -- Compounding this, xrefs come in many forms
  -- * direct, indirect, unknown ,..
  --
  -- Finally, fixed and external xrefs are logically just xrefs, but
  -- one requires lookup and zip.
  --
  -- Finally, finally, we have to do c preprocessor symbols at the same time, via
  -- PPTrace tables
  --
  documentSymbolsForCxx
    :: Maybe Int
    -> Bool  -- ^ include references?
    -> Bool  -- ^ include xlang references?
    -> Glean.IdOf Src.File
    -> Glean.RepoHaxl u w ([GenXRef], [(Code.Location, Code.Entity)], Bool)
  documentSymbolsForCxx mlimit includeRefs includeXlangRefs fileId = do
    mTraceId <- getFirstFileTrace fileId
    case mTraceId of
      Nothing -> return ([], [], False)
      Just traceId -> do
        -- these can run concurrently
        (defns, trunc1) <- fileEntityLocations mlimit traceId
        (xrefs, trunc2{- one of the sub queries truncated-}) <- if includeRefs
          then fileEntityXRefLocations mlimit fileId traceId includeXlangRefs
          else return ([], False)
        let (xrefs', trunc3) = maybeTake mlimit xrefs
        return (xrefs', defns, trunc1 || trunc2 || trunc3)
  
  maybeTake :: Maybe Int -> [a] -> ([a], Bool)
  maybeTake Nothing xs = (xs, False)
  maybeTake (Just n) xs = (take n xs, m > n)
    where
      m = length xs
  
  -- | Get first (arbitrary) file trace
  -- TODO: we can generalize this to return more traces
  getFirstFileTrace
    :: Glean.IdOf Src.File
    -> Glean.RepoHaxl u w (Maybe (Glean.IdOf Cxx.Trace))
  getFirstFileTrace = fetchFactIdOnly . cxxFileTrace
  
  -- | Get first file xrefs set, and keep the fact id around
  -- > cxx1.FileXRefs { xmap = { file = "fbcode/admarket/lib/stringdb/StringDB.h"
  --
  getFirstFileXRefs
    :: Glean.IdOf Src.File
    -> Glean.RepoHaxl u w (Maybe (Glean.IdOf Cxx.FileXRefs))
  getFirstFileXRefs = fetchFactIdOnly . cxxFileXRefs
  
  --
  -- | Find the cxx and pp entities associated with a file, using
  -- a specific trace.
  --
  fileEntityLocations
    :: Maybe Int
    -> Glean.IdOf Cxx.Trace
    -> Glean.RepoHaxl u w ([(Code.Location, Code.Entity)], Bool)
  fileEntityLocations mlimit traceId = searchRecursiveWithLimit mlimit $
    cxxPpResolveTraceLocations traceId
  
  -- | Find xrefs from this file, and their associated entities,
  -- using a specific FileXRefMap annd cxx1.Trace
  --
  -- Share the Cxx.Trace fact between Cpp decls and xrefs for consistency
  --
  fileEntityXRefLocations
    :: Maybe Int
    -> Glean.IdOf Src.File
    -> Glean.IdOf Cxx.Trace
    -> Bool  -- ^ include xlang refs
    -> Glean.RepoHaxl u w ([GenXRef], Bool)
  fileEntityXRefLocations mlimit fileId traceId includeXlangRefs = do
    let reg (locEntities, trunc) = (PlainXRef <$> locEntities, trunc)
    mresult <- getFirstFileXRefs fileId
    extractIdl <- case (mresult, includeXlangRefs) of
      -- fetch from Glean a map from generated entities to their idl entity
      (Just xrefId, True) -> fetchCxxIdlXRefs mlimit xrefId
      _ -> return reg
    fixedAndVariable <- case mresult of -- C++ xrefs rely on a cxxFileXRefs fact
      Nothing -> return []
      Just xrefId -> do
        fixedXRefs <- fixedXRefs mlimit xrefId
        variableXRefs <- externalXRefs mlimit xrefId fileId
        return [fixedXRefs, variableXRefs]
    -- these do not depend on a cxx1.FileXRefs fact, run independently
    ppxrefs <- ppXRefs mlimit traceId
    defXRefs <- declToDefXRefs mlimit traceId
    spellingXRefs <- spellingXRefs mlimit fileId
    let result = (extractIdl <$> fixedAndVariable) ++
          (reg <$> [ppxrefs, defXRefs, spellingXRefs])
    return (concatMap fst result, any snd result)
  
  -- spelling (easily discoverable) xrefs
  spellingXRefs
    :: Maybe Int
    -> Glean.IdOf Src.File
    -> Glean.RepoHaxl u w ([XRef], Bool)
  spellingXRefs mlimit fileId = searchRecursiveWithLimit mlimit $
    cxxFileEntitySpellingXRefLocations fileId
  
  -- fixed (easily discoverable) xrefs
  fixedXRefs
    :: Maybe Int
    -> Glean.IdOf Cxx.FileXRefs
    -> Glean.RepoHaxl u w ([XRef], Bool)
  fixedXRefs mlimit xmapId = searchRecursiveWithLimit mlimit $
    cxxFileEntityXMapFixedXRefLocations xmapId
  
  -- and the external xrefs (zip)
  externalXRefs
    :: Maybe Int
    -> Glean.IdOf Cxx.FileXRefs
    -> Glean.IdOf Src.File
    -> Glean.RepoHaxl u w ([XRef], Bool)
  externalXRefs mlimit xrefId fileId = do
    -- process concurrently
    maybeRawXRefs <- variableXRefs xrefId
    declToDefMap  <- externalDeclToDefXRefs mlimit xrefId
    declLocMap <- externalDeclToLocations mlimit xrefId
    case maybeRawXRefs of
      Nothing -> return ([], False)
      Just (sources, targets) -> do
        locations <- mapM (mapM (cxxXRefTargetToLocation (fst declLocMap)))
          [ xrefs | Cxx.XRefTargets{xRefTargets_key = Just xrefs} <- targets ]
        let ranges = fromToRanges sources
            declXRefs = zipXRefSourceAndTargets ranges locations
            defnXRefs = zipXRefSourcesAndDefinitions (fst declToDefMap)
                          ranges locations
        indirectXRefs <- catMaybes <$> mapM (mkIndirectXRef fileId)
          [ indirect
          | Cxx.XRefTargets { xRefTargets_key = Just xrefs} <- targets
          , Cxx.XRefTarget_indirect indirect <- xrefs -- initial indirect xrefs
          ]
        return (defnXRefs ++ declXRefs ++ indirectXRefs,
          snd declLocMap || snd declToDefMap)
    where
      fromToRanges :: [Cxx.From] -> [[Src.ByteSpan]]
      fromToRanges =
        map (map Range.rangeToByteSpan . Range.fromToSpansAndSpellings)
  
  -- | Extract depth=1 "indirect" xref "via" targets, and make them into
  -- first-class xrefs results. Build xref source span from `via` emit target
  -- entity/location from target of "via"
  mkIndirectXRef
    :: Glean.IdOf Src.File -- ^ we only want indirect xrefs whose use is here
    -> Cxx.XRefIndirectTarget
    -> Glean.RepoHaxl u w (Maybe XRef)
  mkIndirectXRef fileId viaFact = do
    (Cxx.XRefIndirectTarget_key via target) <- Glean.keyOf viaFact
    mXRefTarget <- cxxXRefTargetToLocation mempty target
    case mXRefTarget of
      Nothing -> return Nothing -- filter unknowns
      Just (targetEntity, targetLocation) -> do -- now get the source from XRefVia
        mSourceRange <- getXRefViaRange via
        case mSourceRange of
          Just sourceRange -- validate the via use is in this file only
            | Glean.getId (Src.range_file sourceRange) == fileId -> do
              let xrefLocation = Code.XRefLocation {
                    Code.xRefLocation_target = targetLocation,
                    Code.xRefLocation_source = Code.RangeSpan_range sourceRange
                  }
              return $ Just (xrefLocation, targetEntity)
          _ -> return Nothing
  
  -- Find the use span of an indirect xref: the `via` xref source range
  getXRefViaRange :: Cxx.XRefVia -> Glean.RepoHaxl u w (Maybe Src.Range)
  getXRefViaRange via = case via of
    Cxx.XRefVia_usingDeclaration decl -> do
      Cxx.UsingDeclaration_key _name range <- Glean.keyOf decl
      return (Just range)
  {-
  -- TODO: until we verify how these behave, disable them:
  -- e.g "using namespace" is potentially a UPND source due to the import of
  -- many unrelated xrefs
    Cxx.XRefVia_usingDirective decl -> do
      Cxx.UsingDirective_key _name range <- Glean.keyOf decl
      return (Just range)
    Cxx.XRefVia_macro use -> do
      Pp.Use_key _m _d _e source _name <- Glean.keyOf use
      return (Just source) -- entire term including args is xref use site?
  -}
    _ -> pure Nothing
  
  -- Laboriously stitch the unzipped xref source and target into
  -- XRefLocation and Entities again.
  zipXRefSourceAndTargets
    :: [[Src.ByteSpan]]
    -> [[Maybe (Code.Entity, Code.Location)]]
    -> [XRef]
  zipXRefSourceAndTargets sources targets =
    [ (xrefFromLocationAndSpan targetLocation span, entity)
    | (spans, xrefs) <- zip sources targets
    , span <- spans
    , Just (entity, targetLocation) <- xrefs -- filter unknown xref targets
    ]
  
  -- Laboriously stitch the unzipped xref source to target _definition_
  -- by looking it up in the decl to def map
  zipXRefSourcesAndDefinitions
    :: DeclToDefMap
    -> [[Src.ByteSpan]]
    -> [[Maybe (Code.Entity, Code.Location)]]
    -> [XRef]
  zipXRefSourcesAndDefinitions declToDefMap sources targets = catMaybes
    [ case entity of
        Cxx.Entity_decl decl -> do
          (entity, targetLocation) <- Map.lookup decl declToDefMap
          return (xrefFromLocationAndSpan targetLocation span, entity)
        Cxx.Entity_objcSelectorSlot (Cxx.ObjcSelectorSlotEntity
            (Cxx.ObjcMethodEntity_decl decl) idx) -> do
          (Code.Entity_cxx (Cxx.Entity_defn (Cxx.Definition_objcMethod defn)), _)
            <- Map.lookup (Cxx.Declaration_objcMethod decl) declToDefMap
          (entity, location) <- objcSelectorSlotLocation $
            Cxx.ObjcSelectorSlotEntity (Cxx.ObjcMethodEntity_defn defn) idx
          return (xrefFromLocationAndSpan location span, entity)
        _ -> Nothing
    | (spans, xrefs) <- zip sources targets -- find just the decls
    , span <- spans
    , Just (Code.Entity_cxx entity, _) <- xrefs
    ]
  
  xrefFromLocationAndSpan :: Code.Location -> Src.ByteSpan -> Code.XRefLocation
  xrefFromLocationAndSpan location span =
    Code.XRefLocation {
      Code.xRefLocation_target = location,
      Code.xRefLocation_source = Code.RangeSpan_span span
    }
  
  -- | Map of decl to their def entity
  type DeclToDefMap = Map Cxx.Declaration (Code.Entity, Code.Location)
  
  -- The standard variable xrefs. Takes the first cxx.FileXRefs result for the
  -- FileXRefMap fact. There may be additional facts (more xrefs in other traces)
  variableXRefs
    :: Glean.IdOf Cxx.FileXRefs
    -> Glean.RepoHaxl u w (Maybe ([Cxx.From], [Cxx.XRefTargets]))
  variableXRefs = fetchDataRecursive . cxxFileEntityXMapVariableXRefLocations
  
  -- | Build lookup table of definitions found from declToDef calls
  -- on the exteranl xrefs in the file
  externalDeclToDefXRefs
    :: Maybe Int
    -> Glean.IdOf Cxx.FileXRefs
    -> Glean.RepoHaxl u w (DeclToDefMap, Bool)
  externalDeclToDefXRefs mlimit xrefId = do
    (defRefs, truncated) <- searchRecursiveWithLimit mlimit $
      cxxFileEntityXMapVariableXRefDeclToDefs xrefId
    return $ (,truncated) (Map.fromList $
      map (\(decl, entity, loc) -> (decl, (entity, loc))) defRefs)
  
  -- | Map of external xref decl to location
  type DeclLocationMap = Map Cxx.Declaration Code.Location
  
  -- | Build lookup table of external xref target decls to their location
  -- We need all results, so just assume we don't want to exceed the limit.
  -- The value here should actually be the length of the trace `targets*spans`?
  externalDeclToLocations
    :: Maybe Int
    -> Glean.IdOf Cxx.FileXRefs
    -> Glean.RepoHaxl u w (DeclLocationMap, Bool)
  externalDeclToLocations mlimit xrefId = do
    (rows, truncated) <- searchRecursiveWithLimit mlimit
      (cxxFileEntityXMapVariableXRefDeclLocations xrefId)
    return (Map.fromList rows, truncated)
  
  -- and the pp #define and #include occurences
  ppXRefs
    :: Maybe Int
    -> Glean.IdOf Cxx.Trace
    -> Glean.RepoHaxl u w ([XRef], Bool)
  ppXRefs mlimit traceId = searchRecursiveWithLimit mlimit $
    ppEntityTraceXRefLocations traceId
  
  -- and the underlying definitions of any decls that have them
  declToDefXRefs
    :: Maybe Int
    -> Glean.IdOf Cxx.Trace
    -> Glean.RepoHaxl u w ([XRef], Bool)
  declToDefXRefs mlimit traceId = searchRecursiveWithLimit mlimit $
    cxxFileEntityTraceDeclToDefXRefLocations traceId
  
  objcSelectorSlotLocation
    :: Cxx.ObjcSelectorSlotEntity -> Maybe (Code.Entity, Code.Location)
  objcSelectorSlotLocation slot@(Cxx.ObjcSelectorSlotEntity method idx) = do
    decl <- case method of
      Cxx.ObjcMethodEntity_decl decl -> Just decl
      Cxx.ObjcMethodEntity_defn Cxx.ObjcMethodDefinition{
        objcMethodDefinition_key = Just decl} -> Just decl
      _ -> Nothing
    Cxx.ObjcMethodDeclaration_key{
      objcMethodDeclaration_key_selector = Cxx.ObjcSelector{
        objcSelector_key = Just selector
      },
      objcMethodDeclaration_key_locations = locations
    } <- Glean.getFactKey decl
    let index = fromIntegral $ Glean.fromNat idx
    name <- atMay selector index
    Src.FileLocation{..} <- atMay locations index
    return (
      Code.Entity_cxx (Cxx.Entity_objcSelectorSlot slot),
      Code.Location {
        Code.location_name = name,
        Code.location_file = fileLocation_file,
        Code.location_location = Code.RangeSpan_span fileLocation_span,
        Code.location_destination = Nothing
      })
  
  -- Basically the body of CxxFileEntityTraceFixedXRefLocations
  -- But split between client and server calls.
  --
  -- TODO: this could be replaced with a single bulk fetch and Decl
  -- index like we do for DeclToDef externals , for the declarations and enums
  --
  cxxXRefTargetToLocation
    :: DeclLocationMap
    -> Cxx.XRefTarget
    -> Glean.RepoHaxl u w (Maybe (Code.Entity, Code.Location))
  cxxXRefTargetToLocation declLocMap (Cxx.XRefTarget_declaration decl) = do
    -- try the cache first
    mloc <- case Map.lookup decl declLocMap of
      Just location -> return $ Just location -- done
      Nothing -> do -- maybe it was indirect, fetch it
         mlocation <- fetchDataRecursive $ cxxDeclarationLocation (toAngle decl)
         case mlocation of
            Nothing -> return Nothing
            Just (range, name, file, span) -> do
              let location = Code.Location {
                  Code.location_name = name,
                  Code.location_file = Src.range_file range,
                  Code.location_location = Code.RangeSpan_range range,
                  Code.location_destination = Just Src.FileLocation {
                    Src.fileLocation_file = file,
                    Src.fileLocation_span = span
                  }
                }
              return $ Just location
  
    case mloc of
      Nothing -> return Nothing
      Just location -> do
        let entity = Code.Entity_cxx (Cxx.Entity_decl decl)
        return $ Just (entity, location)
  
  -- Indirect xrefs: we resolve it fully to its target
  cxxXRefTargetToLocation declLocMap (Cxx.XRefTarget_indirect indirect) = do
    Cxx.XRefIndirectTarget_key _via target <- Glean.keyOf indirect
    cxxXRefTargetToLocation declLocMap target  -- n.b recurse to find target
  
  cxxXRefTargetToLocation _ (Cxx.XRefTarget_enumerator enumerator) = do
    Cxx.Enumerator_key name _decl range <- Glean.keyOf enumerator
    nameStr <- Glean.keyOf name
    let entity = Code.Entity_cxx (Cxx.Entity_enumerator enumerator)
    let location = Code.Location {
        Code.location_name = nameStr,
        Code.location_file = Src.range_file range,
        Code.location_location = Code.RangeSpan_range range,
        Code.location_destination = Nothing
      }
    return $ Just (entity, location)
  
  cxxXRefTargetToLocation _ (Cxx.XRefTarget_objcSelectorSlot slot) =
    return $ objcSelectorSlotLocation $
      Cxx.ObjcSelectorSlotEntity (Cxx.ObjcMethodEntity_decl method) idx
    where
      (Cxx.ObjcSelectorSlot method idx) = slot
  
  {-
  XRefTarget_objcSelector ObjcSelector
  XRefTarget_unknown Loc
  -}
  cxxXRefTargetToLocation _ _ = return Nothing
  
  --
  -- Take the ids of the declaration and pptrace facts of the file
  --
  -- > fbsource> cxx1.Trace { file = "fbcode/folly/Synchronized.h" }
  -- > { "id": 431259564, "key": { "tuplefield0": { "id":...
  -- > 1 results, 1 facts, 0.43ms, 240632 bytes, 1253 compiled bytes
  --
  -- N.B. this is a predicate query, not a data query
  --
  cxxFileTrace :: Glean.IdOf Src.File -> Angle Cxx.Trace
  cxxFileTrace fileId =
    predicate @Cxx.Trace (
      rec $
        field @"file" (asPredicate (factId fileId))
      end
    )
  
  --
  -- Same idea as for cxx1.Trace, for xrefs. There are multiple "traces"
  -- of xref maps, so pick the first one, then key off it.
  --
  cxxFileXRefs :: Glean.IdOf Src.File -> Angle Cxx.FileXRefs
  cxxFileXRefs fileId =
    predicate @Cxx.FileXRefs (
      rec $
        field @"xmap" (
          rec $ field @"file" (asPredicate (factId fileId)) end
        )
      end
    )
  
  --
  -- Find file entities for a specific trace associated with a file
  --
  cxxPpResolveTraceLocations
    :: Glean.IdOf Cxx.Trace
    -> Angle (Code.Location, Code.Entity)
  cxxPpResolveTraceLocations traceId =
    vars $ \(location :: Angle Code.Location)
            (entity :: Angle Code.Entity)
            (cxx_entity :: Angle Cxx.Entity)
            (pp_entity :: Angle Pp.Entity) ->
      tuple (location,entity) `where_` [
        wild .=
          [
            wild .= predicate @Code.CxxResolveTraceLocation (
              rec $
                field @"trace" (asPredicate (factId traceId)) $
                field @"location" location $
                field @"entity" cxx_entity
              end),
            entity .= sig (alt @"cxx" cxx_entity)
          ] `or_` [
            wild .= predicate @Code.PpResolveTraceLocation (
              rec $
                field @"trace" (asPredicate (factId traceId)) $
                field @"location" location $
                field @"entity" pp_entity
              end),
            entity .= sig (alt @"pp" pp_entity)
          ]
      ]
  
  -- Spelling XRefs associated with a file
  cxxFileEntitySpellingXRefLocations
    :: Glean.IdOf Src.File
    -> Angle XRef
  cxxFileEntitySpellingXRefLocations fileId =
    vars $ \(xref :: Angle Code.XRefLocation) (entity :: Angle Cxx.Entity) ->
      tuple (xref, sig (alt @"cxx" entity) :: Angle Code.Entity) `where_` [
        wild .= predicate @Code.CxxFileEntitySpellingXRefLocations (
          rec $
            field @"file" (asPredicate (factId fileId)) $
            field @"xref" xref $
            field @"entity" entity
          end)
        ]
  
  -- Fixed XRefs associated with a file and xmap /xref set
  cxxFileEntityXMapFixedXRefLocations
    :: Glean.IdOf Cxx.FileXRefs
    -> Angle XRef
  cxxFileEntityXMapFixedXRefLocations xrefId =
    vars $ \(xref :: Angle Code.XRefLocation) (entity :: Angle Cxx.Entity) ->
      tuple (xref, sig (alt @"cxx" entity) :: Angle Code.Entity) `where_` [
        wild .= predicate @Code.CxxFileEntityXMapFixedXRefLocations (
          rec $
            field @"trace" (asPredicate (factId xrefId)) $
            field @"xref" xref $
            field @"entity" entity
          end)
        ]
  
  --
  -- "Variable" xrefs associated with a file and an xref map
  -- These get processed on the client, here, into Entity and XRefLocations
  --
  cxxFileEntityXMapVariableXRefLocations
    :: Glean.IdOf Cxx.FileXRefs
    -> Angle ([Cxx.From], [Cxx.XRefTargets])
  cxxFileEntityXMapVariableXRefLocations xrefId =
    vars $ \(froms :: Angle [Cxx.From]) (targets :: Angle [Cxx.XRefTargets]) ->
      tuple (froms, targets) `where_` [
        factId xrefId .= predicate @Cxx.FileXRefs (
          rec $
            field @"xmap" (rec $ field @"froms" froms end) $
            field @"targets" targets
          end
        )
      ]
  
  --
  -- Defn xref targets computed from file Decls
  -- N.B. won't contain indirect decls. Just the first level Decls
  --
  cxxFileEntityTraceDeclToDefXRefLocations
    :: Glean.IdOf Cxx.Trace
    -> Angle XRef
  cxxFileEntityTraceDeclToDefXRefLocations traceId =
    vars $ \(xref :: Angle Code.XRefLocation) (entity :: Angle Cxx.Entity) ->
      tuple (xref, sig (alt @"cxx" entity) :: Angle Code.Entity) `where_` [
        wild .= predicate @Code.CxxFileEntityTraceDeclToDefXRefLocations (
          rec $
            field @"trace" (asPredicate (factId traceId)) $
            field @"xref" xref $
            field @"entity" entity
          end)
        ]
  
  --
  -- Get location information for a Cxx Declaration
  --
  cxxDeclarationLocation :: Angle Cxx.Declaration
    -> Angle (Src.Range, Text, Src.File, Src.ByteSpan)
  cxxDeclarationLocation decl =
    vars $ \(source :: Angle Src.Range)
            (name :: Angle Text)
            (file :: Angle Src.File)
            (span :: Angle Src.ByteSpan) ->
    tuple (source, name, file, span) `where_` [
      wild .= predicate @Cxx.DeclarationLocationNameSpan (
        rec $
          field @"decl" decl $
          field @"source" source $
          field @"name" name $
          field @"file" (asPredicate file) $
          field @"span" span
        end)
      ]
  
  --
  -- decl xref target locations computed from external xrefs, bulk fetch
  --
  cxxFileEntityXMapVariableXRefDeclLocations
    :: Glean.IdOf Cxx.FileXRefs
    -> Angle (Cxx.Declaration, Code.Location)
  cxxFileEntityXMapVariableXRefDeclLocations xrefId =
    vars $ \(decl :: Angle Cxx.Declaration)
        (location :: Angle Code.Location) ->
      tuple (decl, location) `where_` [
        wild .= predicate @Code.CxxFileEntityXMapVariableXRefDeclLocations (
          rec $
            field @"trace" (asPredicate (factId xrefId)) $
            field @"source" decl $
            field @"location" location
          end)
        ]
  
  --
  -- Defn xref targets computed from external decls
  -- Returns pairs of each decl fact and the corresponding xref defn target
  --
  cxxFileEntityXMapVariableXRefDeclToDefs
    :: Glean.IdOf Cxx.FileXRefs
    -> Angle (Cxx.Declaration, Code.Entity, Code.Location)
  cxxFileEntityXMapVariableXRefDeclToDefs xrefId =
    vars $ \(decl :: Angle Cxx.Declaration) (entity :: Angle Cxx.Entity)
        (location :: Angle Code.Location) ->
      tuple (decl, sig (alt @"cxx" entity) :: Angle Code.Entity, location)
      `where_` [
        wild .= predicate @Code.CxxFileEntityXMapVariableXRefDeclToDefs (
          rec $
            field @"trace" (asPredicate (factId xrefId)) $
            field @"source" decl $
            field @"entity" entity $ -- todo this needs to be a location
            field @"location" location
          end)
        ]
  
  -- C preprocessor #define and #include uses associated with a cxx1.Trace
  ppEntityTraceXRefLocations
    :: Glean.IdOf Cxx.Trace
    -> Angle XRef
  ppEntityTraceXRefLocations traceId =
    vars $ \(xref :: Angle Code.XRefLocation) (entity :: Angle Pp.Entity) ->
      tuple (xref, sig (alt @"pp" entity) :: Angle Code.Entity) `where_` [
        wild .= predicate @Code.PpEntityTraceXRefLocations (
          rec $
            field @"trace" (asPredicate (factId traceId)) $
            field @"xref" xref $
            field @"entity" entity
          end)
        ]
  
  --
  -- | Breadth first, find at most @n@ #include src.file targets for #include
  -- xrefs in this file. Recursively visiting each #include file to gather more
  --
  fileIncludeLocationsForCxx
    :: Int
    -> Maybe Int
    -> Src.File
    -> Glean.RepoHaxl u w (Map Src.File [(Src.File, Src.Range)])
  fileIncludeLocationsForCxx depth mlimit file = go depth Map.empty [file]
    where
      go 0 acc _ = pure acc
      go _ acc [] = pure acc -- no more #includes to process
      go n !acc files = do
        res <- mapM fetch files -- ensure haxl can do this concurrently
        let seen = Map.fromList res `Map.union` acc
            candidates = uniq (map fst (concatMap snd res))
            new = filter (`Map.notMember` seen) candidates
        go (n-1) seen new
  
      fetch f = do -- named for haxl magic concurrency
        rs <- fileIncludeLocations mlimit (Glean.getId f)
        return (f,rs)
  
  fileIncludeLocations
    :: Maybe Int
    -> Glean.IdOf Src.File
    -> Glean.RepoHaxl u w [(Src.File, Src.Range)]
  fileIncludeLocations mlimit fileId = do
    traces <- getPPTraces traceMaxLimit fileId
    let mquery = case traces of
          [] -> Nothing -- no pp trace events for this file
          [fact] -> Just (asPredicate (factId fact))
          facts -> Just $ foldr1 (.|) (map (asPredicate . factId) facts)
    case mquery of
      Nothing -> return []
      Just traceQ ->
        fst <$> searchRecursiveWithLimit mlimit (ppXRefFileLocations traceQ)
    where
      -- arbitrary number of fact traces to be reasonable. More than 1. But
      -- don't overwhelm things. c.f. PPTrace { file = folly/Optional.h }
      -- also the query gets quite large
      traceMaxLimit = 10
  
  usrHashToDeclaration
    :: Text -> Glean.RepoHaxl u w (Maybe (Code.Location, Code.Entity))
  usrHashToDeclaration usrhash =
    fetchDataRecursive (usrHashToDeclarationAngle usrhash)
  
  -- | Resolve USR hash to its definition site
  usrHashToDeclarationAngle :: Text -> Angle (Code.Location, Code.Entity)
  usrHashToDeclarationAngle usrhash =
    vars $ \(decl :: Angle Cxx.Declaration) (loc :: Angle Code.Location)
        (defn :: Angle Cxx.Definition) ->
      tuple (loc, sig (alt @"cxx" (alt @"defn" defn)) :: Angle Code.Entity)
        `where_` [
          wild .= predicate @Cxx.USRToDeclaration (
            rec $
              field @"hash" (string usrhash) $
              field @"declaration" decl
            end),
          wild .= predicate @Cxx.DeclToDef (
            rec $
              field @"decl" decl $
              field @"defn" defn
            end),
          wild .= predicate @Code.CxxEntityLocation (
            rec $
              field @"entity"  (alt @"defn" defn) $
              field @"location" loc
            end)
        ]
  
  usrToDeclaration
    :: Text -> Glean.RepoHaxl u w (Maybe (Code.Location, Code.Entity))
  usrToDeclaration usr = fetchDataRecursive (usrToDeclarationAngle usr)
  
  -- | Resolve USR to its definition site
  usrToDeclarationAngle :: Text -> Angle (Code.Location, Code.Entity)
  usrToDeclarationAngle usr =
    vars $ \(scipEnt :: Angle Scip.Entity) (loc :: Angle Code.Location) ->
      tuple
        ( loc, sig (alt @"scip" scipEnt) :: Angle Code.Entity)
        `where_` [
          scipEnt .= alt @"swift"
            (rec $ field @"defn" (rec $ field @"symbol" (string usr) end) end),
          wild .= predicate @Scip.ScipEntityLocation (
            rec $
              field @"entity" scipEnt $
              field @"location" loc
            end)
        ]
  
  --
  -- #include resolution acceleration. We can quickly return target filepaths
  -- for all or most #includes in a file.
  --
  -- N.B. we want more than 1, but not all, as some are large (>50k facts)
  --
  getPPTraces
    :: Int
    -> Glean.IdOf Src.File
    -> Glean.RepoHaxl u w [Glean.IdOf Cxx.PPTrace]
  getPPTraces n fileId = fmap (Glean.getId . fst) <$>
    searchWithLimit (Just n) (factIdQuery (ppFileTrace fileId))
  
  --
  -- find the PPTrace facts when we only care about C pre-processor events
  --
  ppFileTrace :: Glean.IdOf Src.File -> Angle Cxx.PPTrace
  ppFileTrace fileId =
    predicate @Cxx.PPTrace (
      rec $
        field @"file" (asPredicate (factId fileId))
      end
    )
  
  -- | Given a PPTrace expression, find C preprocessor #include filepath targets
  ppXRefFileLocations :: Angle Cxx.PPTrace_key -> Angle (Src.File, Src.Range)
  ppXRefFileLocations ppTraceQ =
    vars $ \(filepath :: Angle Src.File) (range :: Angle Src.Range) ->
      tuple (filepath, range) `where_` [
        wild .= predicate @Code.PpIncludeXRefLocations (
          rec $
            field @"trace" ppTraceQ $
            field @"range" range $
            field @"target" (asPredicate filepath)
          end)
        ]
  
  --
  -- "On the economy of bandwidth through content elision
  --     in the Glean database system"
  --
  -- This is a little trick to non-recursivley pull something that might
  -- have a large payload, but we know we only need the fact id
  --
  fetchFactIdOnly
    :: (Glean.Predicate p, QueryType p)
    => Angle p -> Glean.RepoHaxl u w (Maybe (Glean.IdOf p))
  fetchFactIdOnly p = fmap (Glean.getId . fst) <$> fetchData (factIdQuery p)
  
  -- | Used to avoid recursive expansion when we just need a fact id
  factIdQuery :: Type t => Angle t -> Angle (t, ())
  factIdQuery p = var $ \r ->
    tuple (r, sig unit) `where_` [ r .= p ]
  
  usrHashToXRefs
    :: Maybe Int
      -> Text
      -> Glean.RepoHaxl u w [(Src.File, Code.RangeSpan) ]
  usrHashToXRefs n usrhash = searchWithLimit n (usrToXref usrhash)
  
  -- | Return all reference locations of the symbol that corresponds to USR hash.
  --
  -- Equivalent to a findReferenceRanges but accepts a USR instead of a glean
  -- symbol id
  usrToXref :: Text -> Angle ( Src.File, Code.RangeSpan)
  usrToXref usrhash =
    vars $ \(reffile :: Angle Src.File) (rangespan :: Angle Code.RangeSpan)
            (decl :: Angle Cxx.Declaration) ->
      tuple (reffile, rangespan) `where_` [
        wild .= predicate @Cxx.USRToDeclaration (
          rec $
            field @"hash" (string usrhash) $
            field @"declaration" decl
          end),
        wild .= predicate @Code.EntityReferences (
        rec $
            field @"target" (alt @"cxx" (alt @"decl" decl)) $
            field @"file" (asPredicate reffile) $
            field @"range" rangespan
          end
        )
      ]
  