lean4-htt/src/Lean/Server/References.lean
2022-01-31 21:36:37 +01:00

259 lines
9.5 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
-/
import Init.System.IO
import Lean.Data.Json
import Lean.Data.Lsp
import Lean.Server.Utils
import Lean.Server.InfoUtils
import Lean.Server.Snapshots
/- Representing collected and deduplicated definitions and usages -/
namespace Lean.Server
open Lsp
structure Reference where
ident : RefIdent
range : Lsp.Range
isDeclaration : Bool
deriving BEq
end Lean.Server
namespace Lean.Lsp.RefInfo
open Server
def empty : RefInfo := ⟨ none, #[] ⟩
def merge (a : RefInfo) (b : RefInfo) : RefInfo :=
{
definition := b.definition.orElse fun _ => a.definition
usages := a.usages.append b.usages
}
def addRef : RefInfo → Reference → RefInfo
| i@{ definition := none, .. }, { range, isDeclaration := true, .. } =>
{ i with definition := range }
| i@{ usages, .. }, { range, isDeclaration := false, .. } =>
{ i with usages := usages.push range }
| i, _ => i
def contains (self : RefInfo) (pos : Lsp.Position) : Bool := Id.run do
if let some range := self.definition then
if contains range pos then
return true
for range in self.usages do
if contains range pos then
return true
false
where
contains (range : Lsp.Range) (pos : Lsp.Position) : Bool :=
range.start <= pos && pos < range.end
end Lean.Lsp.RefInfo
namespace Lean.Lsp.ModuleRefs
open Server
def addRef (self : ModuleRefs) (ref : Reference) : ModuleRefs :=
let refInfo := self.findD ref.ident RefInfo.empty
self.insert ref.ident (refInfo.addRef ref)
def findAt? (self : ModuleRefs) (pos : Lsp.Position) : Option RefIdent := Id.run do
for (ident, info) in self.toList do
if info.contains pos then
return some ident
none
end Lean.Lsp.ModuleRefs
namespace Lean.Server
open IO
open Std
open Lsp
open Elab
/-- Content of individual `.ilean` files -/
structure Ilean where
version : Nat := 1
module : Name
references : ModuleRefs
deriving FromJson, ToJson
namespace Ilean
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 -/
def identOf : Info → Option (RefIdent × Bool)
| Info.ofTermInfo ti => match ti.expr with
| Expr.const n .. => some (RefIdent.const n, ti.isBinder)
| Expr.fvar id .. => some (RefIdent.fvar id, ti.isBinder)
| _ => none
| Info.ofFieldInfo fi => some (RefIdent.const fi.projName, false)
| _ => none
def findReferences (text : FileMap) (trees : Array InfoTree) : Array Reference := Id.run do
let mut refs := #[]
for tree in trees do
refs := refs.appendList <| tree.deepestNodes fun _ info _ => Id.run do
if let some (ident, isDeclaration) := identOf info then
if let some range := info.range? then
return some { ident, range := range.toLspRange text, isDeclaration }
return none
refs
/--
The `FVarId`s of a function parameter in the function's signature and body
differ. However, they have `TermInfo` nodes with `binder := true` in the exact
same position.
This function changes every such group to use a single `FVarId` and gets rid of
duplicate definitions.
-/
def combineFvars (refs : Array Reference) : Array Reference := Id.run do
-- Deduplicate definitions based on their exact range
let mut posMap : HashMap Lsp.Range FVarId := HashMap.empty
-- Map all `FVarId`s of a group to the first definition's id
let mut idMap : HashMap FVarId FVarId := HashMap.empty
for ref in refs do
if let { ident := RefIdent.fvar id, range, isDeclaration := true } := ref then
if let some baseId := posMap.find? range then
idMap := idMap.insert id baseId
else
posMap := posMap.insert range id
idMap := idMap.insert id id
refs.foldl (init := #[]) fun refs ref => match ref with
| { ident := RefIdent.fvar id, range, isDeclaration := true } => Id.run do
-- Since deduplication works via definitions, we know that a definition
-- for the base id exists.
if let some baseId := idMap.find? id then
if id == baseId then -- Only keep the base definition
return refs.push ref
return refs
| { ident := ident@(RefIdent.fvar _), range, isDeclaration := false } =>
refs.push { ident := applyIdMap idMap ident, range, isDeclaration := false }
| _ => refs.push ref
where
applyIdMap : HashMap FVarId FVarId → RefIdent → RefIdent
| m, RefIdent.fvar id => RefIdent.fvar <| m.findD id id
| _, ident => ident
def findModuleRefs (text : FileMap) (trees : Array InfoTree) (localVars : Bool := true)
: ModuleRefs := Id.run do
let mut refs := combineFvars <| findReferences text trees
if !localVars then
refs := refs.filter fun
| { ident := RefIdent.fvar _, .. } => false
| _ => true
refs.foldl (init := HashMap.empty) fun m ref => m.addRef ref
/- Collecting and maintaining reference info from different sources -/
structure References where
/-- References loaded from ilean files -/
ileans : HashMap Name (System.FilePath × ModuleRefs)
/-- References from workers, overriding the corresponding ilean files -/
workers : HashMap Name (Nat × ModuleRefs)
namespace References
def empty : References := { ileans := HashMap.empty, workers := HashMap.empty }
def addIlean (self : References) (path : System.FilePath) (ilean : Ilean) : References :=
{ self with ileans := self.ileans.insert ilean.module (path, ilean.references) }
def removeIlean (self : References) (path : System.FilePath) : References :=
let namesToRemove := self.ileans.toList.filter (fun (_, p, _) => p == path)
|>.map (fun (n, _, _) => n)
namesToRemove.foldl (init := self) fun self name =>
{ self with ileans := self.ileans.erase name }
def updateWorkerRefs (self : References) (name : Name) (version : Nat) (refs : ModuleRefs) : References := Id.run do
if let some (currVersion, _) := self.workers.find? name then
if version > currVersion then
return { self with workers := self.workers.insert name (version, refs) }
if version == currVersion then
let current := self.workers.findD name (version, HashMap.empty)
let merged := refs.fold (init := current.snd) fun m ident info =>
m.findD ident RefInfo.empty |>.merge info |> m.insert ident
return { self with workers := self.workers.insert name (version, merged) }
return self
def finalizeWorkerRefs (self : References) (name : Name) (version : Nat) (refs : ModuleRefs) : References := Id.run do
if let some (currVersion, _) := self.workers.find? name then
if version < currVersion then
return self
return { self with workers := self.workers.insert name (version, refs) }
def removeWorkerRefs (self : References) (name : Name) : References :=
{ self with workers := self.workers.erase name }
def allRefs (self : References) : HashMap Name ModuleRefs :=
let ileanRefs := self.ileans.toList.foldl (init := HashMap.empty) fun m (name, _, refs) => m.insert name refs
self.workers.toList.foldl (init := ileanRefs) fun m (name, _, refs) => m.insert name refs
def findAt? (self : References) (module : Name) (pos : Lsp.Position) : Option RefIdent := Id.run do
if let some refs := self.allRefs.find? module then
return refs.findAt? pos
none
def referringTo (self : References) (ident : RefIdent) (srcSearchPath : SearchPath)
(includeDefinition : Bool := true) : IO (Array Location) := do
let mut result := #[]
for (module, refs) in self.allRefs.toList do
if let some info := refs.find? ident then
if let some path ← srcSearchPath.findModuleWithExt "lean" module then
-- Resolve symlinks (such as `src` in the build dir) so that files are
-- opened in the right folder
let uri := DocumentUri.ofPath <| ← IO.FS.realPath path
if includeDefinition then
if let some range := info.definition then
result := result.push ⟨uri, range⟩
for range in info.usages do
result := result.push ⟨uri, range⟩
result
def definitionOf? (self : References) (ident : RefIdent) (srcSearchPath : SearchPath)
: IO (Option Location) := do
for (module, refs) in self.allRefs.toList do
if let some info := refs.find? ident then
if let some definition := info.definition then
if let some path ← srcSearchPath.findModuleWithExt "lean" module then
-- Resolve symlinks (such as `src` in the build dir) so that files are
-- opened in the right folder
let uri := DocumentUri.ofPath <| ← IO.FS.realPath path
return some ⟨uri, definition⟩
return none
def definitionsMatching (self : References) (srcSearchPath : SearchPath) (filter : Name → Option α)
(maxAmount? : Option Nat := none) : IO $ Array (α × Location) := do
let mut result := #[]
for (module, refs) in self.allRefs.toList do
if let some path ← srcSearchPath.findModuleWithExt "lean" module then
let uri := DocumentUri.ofPath <| ← IO.FS.realPath path
for (ident, info) in refs.toList do
if let (RefIdent.const name, some definition) := (ident, info.definition) then
if let some a := filter name then
result := result.push (a, ⟨uri, definition⟩)
if let some maxAmount := maxAmount? then
if result.size >= maxAmount then
return result
return result
end References
end Lean.Server