lean4-htt/src/Lean/Server/References.lean
Sebastian Ullrich a9145d3312
fix: do not block in snapshot reporter when creating ilean update (#9784)
This PR ensures the editor progress bar better reflects the actual
progress of parallel elaboration.
2025-08-12 16:08:59 +00:00

796 lines
31 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/-
Copyright (c) 2021 Joscha Mennicken. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Joscha Mennicken
-/
module
prelude
public import Lean.Data.Lsp.Internal
public import Lean.Data.Lsp.Extra
public import Lean.Server.Utils
public import Lean.Elab.Import
public import Std.Data.TreeSet.Basic
public section
/-! # Representing collected and deduplicated definitions and usages -/
set_option linter.missingDocs true
namespace Lean.Server
open Lsp Lean.Elab Std
/-- Converts an `Import` to its LSP-internal representation. -/
def ImportInfo.ofImport (i : Import) : ImportInfo where
module := i.module.toString
isAll := i.importAll
isPrivate := ! i.isExported
isMeta := i.isMeta
/-- Collects `ImportInfo` for all import statements in `headerStx`. -/
def collectImports (headerStx : HeaderSyntax) : Array ImportInfo :=
headerToImports headerStx (includeInit := false) |>.map ImportInfo.ofImport
/--
Global reference. Used by the language server to figure out which identifiers refer to which
other identifiers across the whole project.
-/
structure Reference where
/-- Identifier of this reference. -/
ident : RefIdent
/-- Identifiers that are logically identical to this reference. -/
aliases : Array RefIdent := #[]
/-- Range where this reference occurs. -/
range : Lsp.Range
/-- Syntax of this reference. -/
stx : Syntax
/-- `ContextInfo` at the point of elaboration of this reference. -/
ci : ContextInfo
/-- Additional `InfoTree` information for this reference. -/
info : Info
/-- Whether this reference declares `ident`. -/
isBinder : Bool
/-- Definition and usages of an identifier within a single module. -/
structure RefInfo where
/--
Definition `Reference` of the identifier.
Is equal to `none` if e.g. the definition is outside of the module where this `RefInfo` is used.
-/
definition : Option Reference
/-- All usage `Reference`s of the identifier in a single module. -/
usages : Array Reference
namespace RefInfo
/-- No definition, no usages. -/
def empty : RefInfo := ⟨none, #[]⟩
/--
Adds `ref` to `i`.
If `i` has no `definition` and `ref` is a declaration, it becomes the `definition`.
If `i` already has a `definition` and `ref` is also a declaration, it is not added to `i`.
Otherwise, `ref` is added to `i.usages`.
-/
def addRef (i : RefInfo) (ref : Reference) : RefInfo :=
match i, ref with
| { definition := none, .. }, { isBinder := true, .. } =>
{ i with definition := ref }
| { definition := some .., .. }, { isBinder := true, .. } =>
i
| { usages, .. }, { isBinder := false, .. } =>
{ i with usages := usages.push ref }
/-- Converts `i` to a JSON-serializable `Lsp.RefInfo`. -/
def toLspRefInfo (i : RefInfo) : BaseIO Lsp.RefInfo := do
let refToRefInfoLocation (ref : Reference) : BaseIO RefInfo.Location := do
let parentDeclName? := ref.ci.parentDecl?
let .ok parentDeclRanges? ← EIO.toBaseIO <| ref.ci.runCoreM do
let some parentDeclName := parentDeclName?
| return none
-- Use `local` as it avoids unnecessary blocking, which is especially important when called
-- from the snapshot reporter. Specifically, if `ref` is from a tactic of an async theorem,
-- `parentDeclName` will not be available in the current environment and we would block only
-- to return `none` in the end anyway. At the end of elaboration, we rerun this function on
-- the full info tree with the main environment, so the access will succeed immediately.
return declRangeExt.find? (asyncMode := .local) (← getEnv) parentDeclName
-- we only use `CoreM` to get access to a `MonadEnv`, but these are currently all `IO`
| unreachable!
return {
range := ref.range
parentDecl? := do
let parentDeclName ← parentDeclName?
let parentDeclRange := (← parentDeclRanges?).range.toLspRange
let parentDeclSelectionRange := (← parentDeclRanges?).selectionRange.toLspRange
return ⟨parentDeclName.toString, parentDeclRange, parentDeclSelectionRange⟩
}
let definition? ← i.definition.mapM refToRefInfoLocation
let usages ← i.usages.mapM refToRefInfoLocation
return {
definition? := definition?
usages := usages
}
end RefInfo
/-- All references from within a module for all identifiers used in a single module. -/
abbrev ModuleRefs := Std.TreeMap RefIdent RefInfo
namespace ModuleRefs
/-- Adds `ref` to the `RefInfo` corresponding to `ref.ident` in `self`. See `RefInfo.addRef`. -/
def addRef (self : ModuleRefs) (ref : Reference) : ModuleRefs :=
let refInfo := self.getD ref.ident RefInfo.empty
self.insert ref.ident (refInfo.addRef ref)
/-- Converts `refs` to a JSON-serializable `Lsp.ModuleRefs`. -/
def toLspModuleRefs (refs : ModuleRefs) : BaseIO Lsp.ModuleRefs := do
let mut refs' := ∅
for (k, v) in refs do
refs' := refs'.insert k (← v.toLspRefInfo)
return refs'
end ModuleRefs
end Lean.Server
namespace Lean.Lsp.RefInfo
open Server
/-- No definition, no usages -/
def empty : RefInfo := ⟨ none, #[] ⟩
/-- Combines the `usages` of `a` and `b` and prefers the `definition?` of `b` over that of `a`. -/
def merge (a : RefInfo) (b : RefInfo) : RefInfo where
definition? := b.definition?.orElse fun _ => a.definition?
usages := a.usages.append b.usages
/--
Finds the first definition or usage in `self` where the `RefInfo.Location.range`
contains the given `pos`. The `includeStop` parameter can be used to toggle between closed-interval
and half-open-interval behavior for the range. Closed-interval behavior matches the expectation of
VSCode when selecting an identifier at a cursor position (see #767).
-/
def findReferenceLocation?
(self : RefInfo)
(pos : Lsp.Position)
(includeStop : Bool := false)
: Option Location := do
if let some loc := self.definition? then
if contains loc.range pos then
return loc
for loc in self.usages do
if contains loc.range pos then
return loc
none
where
contains (range : Lsp.Range) (pos : Lsp.Position) : Bool :=
range.start <= pos && (if includeStop then pos <= range.end else pos < range.end)
/-- Checks whether any of the ranges in `self.definition?` or `self.usages` contains `pos`. -/
def contains (self : RefInfo) (pos : Lsp.Position) (includeStop := false) : Bool := Id.run do
(self.findReferenceLocation? pos includeStop).isSome
end Lean.Lsp.RefInfo
namespace Lean.Lsp.ModuleRefs
open Server
/--
Find all identifiers in `self` with a reference in this module that contains `pos` in its range.
-/
def findAt
(self : ModuleRefs)
(pos : Lsp.Position)
(includeStop := false)
: Array RefIdent := Id.run do
let mut result := #[]
for (ident, info) in self do
if info.contains pos includeStop then
result := result.push ident
result
/-- Finds the first range in `self` that contains `pos`. -/
def findRange? (self : ModuleRefs) (pos : Lsp.Position) (includeStop := false) : Option Range := do
for (_, info) in self do
if let some loc := info.findReferenceLocation? pos includeStop then
return loc.range
none
end Lean.Lsp.ModuleRefs
namespace Lean.Server
open IO
open Lsp
open Elab
/-- Content of individual `.ilean` files -/
structure Ilean where
/-- Version number of the ilean format. -/
version : Nat := 4
/-- Name of the module that this ilean data has been collected for. -/
module : Name
/-- Direct imports of the module. -/
directImports : Array Lsp.ImportInfo
/-- All references of this module. -/
references : Lsp.ModuleRefs
deriving FromJson, ToJson
namespace Ilean
/-- Reads and parses the .ilean file at `path`. -/
def load (path : System.FilePath) : IO Ilean := do
let content ← FS.readFile path
match Json.parse content >>= fromJson? with
| Except.ok ilean => pure ilean
| Except.error msg => throwServerError s!"Failed to load ilean at {path}: {msg}"
end Ilean
/-! # Collecting and deduplicating definitions and usages -/
/-- Gets the name of the module that contains `declName`. -/
def getModuleContainingDecl? (env : Environment) (declName : Name) : Option Name := do
let some modIdx := env.getModuleIdxFor? declName
| env.header.mainModule
env.allImportedModuleNames[modIdx]?
/--
Determines the `RefIdent` for the `Info` `i` of an identifier in `module` and
whether it is a declaration.
-/
def identOf (ci : ContextInfo) (i : Info) : Option (RefIdent × Bool) := do
match i with
| Info.ofTermInfo ti => match ti.expr with
| Expr.const n .. =>
some (RefIdent.const (← getModuleContainingDecl? ci.env n).toString n.toString, ti.isBinder)
| Expr.fvar id =>
some (RefIdent.fvar ci.env.header.mainModule.toString id.name.toString, ti.isBinder)
| _ => none
| Info.ofFieldInfo fi =>
some (RefIdent.const (← getModuleContainingDecl? ci.env fi.projName).toString fi.projName.toString, false)
| Info.ofOptionInfo oi =>
some (RefIdent.const (← getModuleContainingDecl? ci.env oi.declName).toString oi.declName.toString, false)
| _ => none
/-- Finds all references in `trees`. -/
def findReferences (text : FileMap) (trees : Array InfoTree) : Array Reference :=
Id.run <| StateT.run' (s := #[]) do
for tree in trees do
tree.visitM' (postNode := fun ci info _ => do
let some (ident, isBinder) := identOf ci info
| return
let some range := info.range?
| return
if info.stx.getHeadInfo matches .original .. then -- we are not interested in canonical syntax here
modify (·.push { ident, range := range.toLspRange text, stx := info.stx, ci, info, isBinder }))
get
/--
There are several different identifiers that should be considered equal for the purpose of finding
all references of an identifier:
- `FVarId`s of a function parameter in the function's signature and body
- Chains of helper definitions like those created for do-reassignment `x := e`
- Overlapping definitions like those defined by `where` declarations that define both an FVar
(for local usage) and a constant (for non-local usage)
- Identifiers connected by `FVarAliasInfo` such as variables before and after `match` generalization
In the first three cases that are not explicitly denoted as aliases with an `FVarAliasInfo`, the
corresponding `Reference`s have the exact same range.
This function finds all definitions that have the exact same range as another definition or usage
and collapses them into a single identifier. It also collapses identifiers connected by
an `FVarAliasInfo`.
When collapsing identifiers, it prefers using a `RefIdent.const name` over a `RefIdent.fvar id` for
all identifiers that are being collapsed into one.
-/
partial def combineIdents (trees : Array InfoTree) (refs : Array Reference) : Array Reference := Id.run do
-- Deduplicate definitions based on their exact range
let mut posMap : Std.HashMap Lsp.Range RefIdent := ∅
for ref in refs do
if let { ident, range, isBinder := true, .. } := ref then
posMap := posMap.insert range ident
let idMap := useConstRepresentatives <| buildIdMap posMap
let mut refs' := #[]
for ref in refs do
let id := ref.ident
if idMap.contains id then
refs' := refs'.push { ref with ident := findCanonicalRepresentative idMap id, aliases := #[id] }
else if !idMap.contains id then
refs' := refs'.push ref
refs'
where
useConstRepresentatives (idMap : Std.HashMap RefIdent RefIdent)
: Std.HashMap RefIdent RefIdent := Id.run do
let insertIntoClass classesById id :=
let representative := findCanonicalRepresentative idMap id
let «class» := classesById.getD representative ∅
let classesById := classesById.erase representative -- make `«class»` referentially unique
let «class» := «class».insert id
classesById.insert representative «class»
-- collect equivalence classes
let mut classesById : Std.HashMap RefIdent (Std.HashSet RefIdent) := ∅
for ⟨id, baseId⟩ in idMap do
classesById := insertIntoClass classesById id
classesById := insertIntoClass classesById baseId
let mut r := ∅
for ⟨currentRepresentative, «class»⟩ in classesById do
-- find best representative (ideally a const if available)
let mut bestRepresentative := currentRepresentative
for id in «class» do
bestRepresentative :=
match bestRepresentative, id with
| .fvar ma a, .fvar .. => .fvar ma a
| .fvar .., .const mb b => .const mb b
| .const ma a, .fvar .. => .const ma a
| .const ma a, .const .. => .const ma a
-- compress `idMap` so that all identifiers in a class point to the best representative
for id in «class» do
if id != bestRepresentative then
r := r.insert id bestRepresentative
return r
findCanonicalRepresentative (idMap : Std.HashMap RefIdent RefIdent) (id : RefIdent) : RefIdent := Id.run do
let mut canonicalRepresentative := id
while h : idMap.contains canonicalRepresentative do
canonicalRepresentative := idMap[canonicalRepresentative]
return canonicalRepresentative
buildIdMap posMap := Id.run <| StateT.run' (s := ∅) do
-- map fvar defs to overlapping fvar defs/uses
for ref in refs do
let baseId := ref.ident
if let some id := posMap[ref.range]? then
insertIdMap id baseId
-- apply `FVarAliasInfo`
trees.forM (·.visitM' (postNode := fun ci info _ => do
if let .ofFVarAliasInfo ai := info then
-- FVars can only be aliases of FVars of the same file / module
let mod := ci.env.header.mainModule
insertIdMap (.fvar mod.toString ai.id.name.toString) (.fvar mod.toString ai.baseId.name.toString)))
get
-- poor man's union-find; see also `findCanonicalBinder`
insertIdMap id baseId := do
let idMap ← get
let id := findCanonicalRepresentative idMap id
let baseId := findCanonicalRepresentative idMap baseId
if baseId != id then
modify (·.insert id baseId)
/--
Groups `refs` by identifier and range s.t. references with the same identifier and range
are added to the `aliases` of the representative of the group.
Yields to separate groups for declaration and usages if `allowSimultaneousBinderUse` is set.
-/
def dedupReferences (refs : Array Reference) (allowSimultaneousBinderUse := false) : Array Reference := Id.run do
let mut refsByIdAndRange : Std.HashMap (RefIdent × Option Bool × Lsp.Range) Reference := ∅
for ref in refs do
let isBinder := if allowSimultaneousBinderUse then some ref.isBinder else none
let key := (ref.ident, isBinder, ref.range)
refsByIdAndRange := match refsByIdAndRange[key]? with
| some ref' => refsByIdAndRange.insert key { ref' with aliases := ref'.aliases ++ ref.aliases }
| none => refsByIdAndRange.insert key ref
let dedupedRefs := refsByIdAndRange.fold (init := #[]) fun refs _ ref => refs.push ref
return dedupedRefs.qsort (·.range < ·.range)
/--
Finds all references in `trees` and deduplicates the result.
See `dedupReferences` and `combineIdents`.
-/
def findModuleRefs (text : FileMap) (trees : Array InfoTree) (localVars : Bool := true)
(allowSimultaneousBinderUse := false) : ModuleRefs := Id.run do
let mut refs :=
dedupReferences (allowSimultaneousBinderUse := allowSimultaneousBinderUse) <|
combineIdents trees <|
findReferences text trees
if !localVars then
refs := refs.filter fun
| { ident := RefIdent.fvar .., .. } => false
| _ => true
refs.foldl (init := Std.TreeMap.empty) fun m ref => m.addRef ref
/-! # Collecting and maintaining reference info from different sources -/
/-- Represents a direct import of a module in the references data structure. -/
structure ModuleImport where
/-- Module name of the module that is imported. -/
module : Name
/-- URI of the module that is imported. -/
uri : DocumentUri
/-- Whether the `all` flag is set on this import. -/
isAll : Bool
/-- Whether the `private` flag is set on this import. -/
isPrivate : Bool
/-- Kind of `meta` annotation on this import. -/
metaKind : LeanImportMetaKind
deriving Inhabited
/--
Reduces `identicalImports` with the same module name by merging their flags.
Yields `none` if `identicalImports` is empty or `identicalImports` contains an import that
has a name or uri that is not identical to the others.
-/
def ModuleImport.collapseIdenticalImports? (identicalImports : Array ModuleImport) : Option ModuleImport := do
let mut acc ← identicalImports[0]?
for h:i in 1...identicalImports.size do
let «import» := identicalImports[i]
guard <| acc.module == «import».module
guard <| acc.uri == «import».uri
acc := { acc with
isAll := acc.isAll || «import».isAll
isPrivate := acc.isPrivate && «import».isPrivate
metaKind := collapseMetaKinds acc.metaKind «import».metaKind
}
return acc
where
collapseMetaKinds : LeanImportMetaKind → LeanImportMetaKind → LeanImportMetaKind
| .full, _ => .full
| _, .full => .full
| .nonMeta, .meta => .full
| .meta, .nonMeta => .full
| .meta, .meta => .meta
| .nonMeta, .nonMeta => .nonMeta
/--
Index that allows efficiently looking up the imports of a module by module name.
Since the same module can be imported multiple times with different attributes,
each module name maps to an array of imports.
-/
abbrev ModuleImportIndex := Std.TreeMap Name (Array ModuleImport) Name.quickCmp
/--
Direct imports of a module, containing an ordered representation and an index for fast lookups.
-/
structure DirectImports where
/-- Imports as they occurred in the module. -/
ordered : Array ModuleImport
/--
Index that allows efficiently looking up the imports of a module by module name.
Since the same module can be imported multiple times with different attributes,
each module name maps to an array of imports.
-/
index : ModuleImportIndex
instance : EmptyCollection DirectImports where
emptyCollection := { ordered := #[], index := ∅ }
/--
Converts a list of LSP module imports to the module imports of the references data structure.
Removes all imports for which we cannot resolve the corresponding `DocumentUri`.
-/
def DirectImports.convertImportInfos (infos : Array Lsp.ImportInfo) : IO DirectImports := do
let ordered ← infos.filterMapM fun i => do
let module := i.module.toName
let some uri ← documentUriFromModule? module
| return none
return some {
uri
module
isAll := i.isAll
isPrivate := i.isPrivate
metaKind := if i.isMeta then .meta else .nonMeta
}
let index := ordered.groupByKey (·.module)
|>.toArray
|> Std.TreeMap.ofArray (cmp := Name.quickCmp)
return { ordered, index }
/-- Reference information from a loaded ILean file. -/
structure LoadedILean where
/-- URI of the module of this ILean. -/
moduleUri : DocumentUri
/-- Path to the ILean file. -/
ileanPath : System.FilePath
/-- Direct imports of the module of this ILean. -/
directImports : DirectImports
/-- Reference information from this ILean. -/
refs : Lsp.ModuleRefs
/-- Paths and module references for every module name. Loaded from `.ilean` files. -/
abbrev ILeanMap := Std.TreeMap Name LoadedILean Name.quickCmp
/--
Transient reference information from a file worker.
We track this information so that we have up-to-date reference information before a file has been
built.
-/
structure TransientWorkerILean where
/-- URI of the module that the file worker is associated with. -/
moduleUri : DocumentUri
/-- Document version for which these references have been collected. -/
version : Nat
/-- Direct imports of the module that the file worker is associated with. -/
directImports : DirectImports
/-- References provided by the worker. -/
refs : Lsp.ModuleRefs
/--
Document versions and module references for every module name. Loaded from the current state
in a file worker.
-/
abbrev WorkerRefMap := Std.TreeMap Name TransientWorkerILean Name.quickCmp
/-- References from ilean files and current ilean information from file workers. -/
structure References where
/-- References loaded from ilean files -/
ileans : ILeanMap
/-- References from workers, overriding the corresponding ilean files -/
workers : WorkerRefMap
deriving Inhabited
namespace References
/-- No ilean files, no information from workers. -/
def empty : References := { ileans := ∅, workers := ∅ }
/-- Adds the contents of an ilean file `ilean` at `path` to `self`. -/
def addIlean
(self : References)
(path : System.FilePath)
(ilean : Ilean)
: IO References := do
let some moduleUri ← documentUriFromModule? ilean.module
| return self
let directImports ← DirectImports.convertImportInfos ilean.directImports
return { self with
ileans := self.ileans.insert ilean.module {
moduleUri
ileanPath := path
directImports
refs := ilean.references
}
}
/-- Removes the ilean file data at `path` from `self`. -/
def removeIlean (self : References) (path : System.FilePath) : References :=
let namesToRemove := self.ileans.filter (fun _ { ileanPath, .. } => ileanPath == path)
namesToRemove.foldl (init := self) fun self name _ =>
{ self with ileans := self.ileans.erase name }
/--
Replaces the direct imports of a worker for the module `name` in `self` with
a new set of direct imports.
-/
def updateWorkerImports
(self : References)
(name : Name)
(moduleUri : DocumentUri)
(version : Nat)
(directImports : Array ImportInfo)
: IO References := do
let directImports ← DirectImports.convertImportInfos directImports
let some { version := currVersion, .. } := self.workers[name]?
| return { self with workers := self.workers.insert name { moduleUri, version, directImports, refs := ∅} }
match compare version currVersion with
| .lt => return self
| .gt => return { self with workers := self.workers.insert name { moduleUri, version, directImports, refs := ∅} }
| .eq =>
let refs := self.workers.get? name |>.map (·.refs) |>.getD ∅
return { self with
workers := self.workers.insert name { moduleUri, version, directImports, refs }
}
/--
Updates the worker references in `self` with the `refs` of the worker managing the module `name`.
Replaces the current references with `refs` if `version` is newer than the current version managed
in `refs` and otherwise merges the reference data if `version` is equal to the current version.
-/
def updateWorkerRefs
(self : References)
(name : Name)
(moduleUri : DocumentUri)
(version : Nat)
(refs : Lsp.ModuleRefs)
: IO References := do
let some { version := currVersion, .. } := self.workers[name]?
| return { self with workers := self.workers.insert name { moduleUri, version, directImports := ∅, refs } }
match compare version currVersion with
| .lt => return self
| .gt => return { self with workers := self.workers.insert name { moduleUri, version, directImports := ∅, refs } }
| .eq =>
let current := self.workers.getD name { moduleUri, version, directImports := ∅, refs := Std.TreeMap.empty }
let mergedRefs := refs.foldl (init := current.refs) fun m ident info =>
m.getD ident Lsp.RefInfo.empty |>.merge info |> m.insert ident
return { self with
workers := self.workers.insert name { moduleUri, version, directImports := current.directImports, refs := mergedRefs }
}
/--
Replaces the worker references in `self` with the `refs` of the worker managing the module `name`
if `version` is newer than the current version managed in `refs`.
-/
def finalizeWorkerRefs
(self : References)
(name : Name)
(moduleUri : DocumentUri)
(version : Nat)
(refs : Lsp.ModuleRefs)
: IO References := do
let some { version := currVersion, .. } := self.workers[name]?
| return { self with workers := self.workers.insert name { moduleUri, version, directImports := ∅, refs } }
match compare version currVersion with
| .lt => return self
| .gt => return { self with workers := self.workers.insert name { moduleUri, version, directImports := ∅, refs} }
| .eq =>
let directImports := self.workers.get? name |>.map (·.directImports) |>.getD ∅
return { self with workers := self.workers.insert name { moduleUri, version, directImports, refs } }
/-- Erases all worker references in `self` for the worker managing `name`. -/
def removeWorkerRefs (self : References) (name : Name) : References :=
{ self with workers := self.workers.erase name }
/--
Map from each module to all of its references.
The current references in a file worker take precedence over those in .ilean files.
-/
abbrev AllRefsMap := Std.TreeMap Name (DocumentUri × Lsp.ModuleRefs) Name.quickCmp
/-- Yields a map from all modules to all of their references. -/
def allRefs (self : References) : AllRefsMap :=
let ileanRefs := self.ileans.foldl (init := ∅) fun m name { moduleUri, refs, .. } => m.insert name (moduleUri, refs)
self.workers.foldl (init := ileanRefs) fun m name { moduleUri, refs, ..} => m.insert name (moduleUri, refs)
/--
Map from each module to all of its direct imports.
The current references in a file worker take precedence over those in .ilean files.
-/
abbrev AllDirectImportsMap := Std.TreeMap Name (DocumentUri × DirectImports) Name.quickCmp
/-- Yields a map from all modules to all of their direct imports. -/
def allDirectImports (self : References) : AllDirectImportsMap := Id.run do
let mut allDirectImports := ∅
for (name, ilean) in self.ileans do
allDirectImports := allDirectImports.insert name (ilean.moduleUri, ilean.directImports)
for (name, worker) in self.workers do
allDirectImports := allDirectImports.insert name (worker.moduleUri, worker.directImports)
return allDirectImports
/--
Gets the references for `mod`.
The current references in a file worker take precedence over those in .ilean files.
-/
def getModuleRefs? (self : References) (mod : Name) : Option (DocumentUri × Lsp.ModuleRefs) := do
if let some worker := self.workers[mod]? then
return (worker.moduleUri, worker.refs)
if let some ilean := self.ileans[mod]? then
return (ilean.moduleUri, ilean.refs)
none
/--
Gets the direct imports of `mod`.
The current imports in a file worker take precedence over those in .ilean files.
-/
def getDirectImports? (self : References) (mod : Name) : Option DirectImports := do
if let some worker := self.workers[mod]? then
return worker.directImports
if let some ilean := self.ileans[mod]? then
return ilean.directImports
none
/--
Yields all references in `self` for `ident`, as well as the `DocumentUri` that each
reference occurs in.
-/
def allRefsFor
(self : References)
(ident : RefIdent)
: Array (DocumentUri × Name × Lsp.RefInfo) := Id.run do
let refsToCheck := match ident with
| RefIdent.const .. => self.allRefs.toArray
| RefIdent.fvar identModule .. =>
let identModuleName := identModule.toName
match self.getModuleRefs? identModuleName with
| none => #[]
| some (moduleUri, refs) => #[(identModuleName, moduleUri, refs)]
let mut result := #[]
for (module, moduleUri, refs) in refsToCheck do
let some info := refs.get? ident
| continue
result := result.push (moduleUri, module, info)
return result
/-- Yields all references in `module` at `pos`. -/
def findAt (self : References) (module : Name) (pos : Lsp.Position) (includeStop := false) : Array RefIdent := Id.run do
if let some (_, refs) := self.getModuleRefs? module then
return refs.findAt pos includeStop
#[]
/-- Yields the first reference in `module` at `pos`. -/
def findRange? (self : References) (module : Name) (pos : Lsp.Position) (includeStop := false) : Option Range := do
let (_, refs) ← self.getModuleRefs? module
refs.findRange? pos includeStop
/-- Location and parent declaration of a reference. -/
structure DocumentRefInfo where
/-- Location of the reference. -/
location : Location
/-- Module name of the reference. -/
module : Name
/-- Parent declaration of the reference. -/
parentInfo? : Option RefInfo.ParentDecl
/-- Yields locations and parent declaration for all references referring to `ident`. -/
def referringTo
(self : References)
(ident : RefIdent)
(includeDefinition : Bool := true)
: Array DocumentRefInfo := Id.run do
let mut result := #[]
for (moduleUri, module, info) in self.allRefsFor ident do
if includeDefinition then
if let some ⟨range, parentDeclInfo?⟩ := info.definition? then
result := result.push ⟨⟨moduleUri, range⟩, module, parentDeclInfo?⟩
for ⟨range, parentDeclInfo?⟩ in info.usages do
result := result.push ⟨⟨moduleUri, range⟩, module, parentDeclInfo?⟩
return result
/-- Yields the definition location of `ident`. -/
def definitionOf?
(self : References)
(ident : RefIdent)
: Option DocumentRefInfo := Id.run do
for (moduleUri, module, info) in self.allRefsFor ident do
let some ⟨definitionRange, definitionParentDeclInfo?⟩ := info.definition?
| continue
return some ⟨⟨moduleUri, definitionRange⟩, module, definitionParentDeclInfo?⟩
return none
/-- A match in `References.definitionsMatching`. -/
structure MatchedDefinition (α : Type) where
/-- Result of `filterMapMod`. -/
mod : Name
/-- URI for `mod`. -/
modUri : DocumentUri
/-- Result of `filterMapIdent`. -/
ident : α
/-- Definition range of matched identifier. -/
range : Range
/-- Yields all definitions matching the given `filter`. -/
def definitionsMatching
(self : References)
(filterMapIdent : Name → Option α)
(cancelTk? : Option CancelToken := none)
: BaseIO (Array (MatchedDefinition α)) := do
let mut result := #[]
for (module, moduleUri, refs) in self.allRefs do
if let some cancelTk := cancelTk? then
if ← cancelTk.isSet then
return result
for (ident, info) in refs do
let (RefIdent.const _ nameString, some ⟨definitionRange, _⟩) := (ident, info.definition?)
| continue
let some v := filterMapIdent nameString.toName
| continue
result := result.push ⟨module, moduleUri, v, definitionRange⟩
return result
/-- Yields all imports that import the given `requestedMod`. -/
def importedBy (self : References) (requestedMod : Name) : Array ModuleImport := Id.run do
let mut result := #[]
for (importedByModule, importedByModuleUri, directImports) in self.allDirectImports do
let some importsOfRequestedMod := directImports.index.get? requestedMod
| continue
let importOfRequestedMod := ModuleImport.collapseIdenticalImports? importsOfRequestedMod |>.get!
result := result.push {
module := importedByModule
uri := importedByModuleUri
isAll := importOfRequestedMod.isAll
isPrivate := importOfRequestedMod.isPrivate
metaKind := importOfRequestedMod.metaKind
}
return result
end References
end Lean.Server