333 lines
10 KiB
Text
333 lines
10 KiB
Text
/-
|
||
Copyright (c) 2022 Sebastian Ullrich. All rights reserved.
|
||
Released under Apache 2.0 license as described in the file LICENSE.
|
||
|
||
Authors: Sebastian Ullrich, Lars König, Wojciech Nawrocki
|
||
-/
|
||
module
|
||
|
||
prelude
|
||
public import Lean.Data.Json.FromToJson.Basic
|
||
public import Lean.Util.Path
|
||
public import Lean.Server.Utils
|
||
public import Lean.Data.Lsp.Internal
|
||
public import Lean.Util.CollectFVars
|
||
public import Lean.Util.ForEachExpr
|
||
meta import Lean.Parser.Module
|
||
|
||
public section
|
||
|
||
namespace Lean.Server
|
||
|
||
open Lsp
|
||
open Elab
|
||
|
||
inductive GoToKind
|
||
| declaration | definition | type
|
||
deriving BEq, ToJson, FromJson
|
||
|
||
def GoToKind.determineTargetExprs (kind : GoToKind) (ti : TermInfo) : MetaM (Array Expr) := do
|
||
match kind with
|
||
| .type =>
|
||
let type ← instantiateMVars <| ← Meta.inferType ti.expr
|
||
let (_, targetExprs) ← StateT.run (s := #[]) <| type.forEach fun e =>
|
||
match e with
|
||
| .fvar ..
|
||
| .const .. =>
|
||
modify (·.push e)
|
||
| _ =>
|
||
pure ()
|
||
return targetExprs
|
||
| _ =>
|
||
return #[← instantiateMVars ti.expr]
|
||
|
||
def getInstanceProjectionArg? (e : Expr) : MetaM (Option Expr) := do
|
||
let env ← getEnv
|
||
let .const n _ := e.getAppFn'
|
||
| return none
|
||
let some projInfo := env.getProjectionFnInfo? n
|
||
| return none
|
||
let instIdx := projInfo.numParams
|
||
let appArgs := e.getAppArgs
|
||
return appArgs[instIdx]?
|
||
|
||
def isInstanceProjection (e : Expr) : MetaM Bool := do
|
||
return (← getInstanceProjectionArg? e).isSome
|
||
|
||
/-- Checks whether `ti1` is an instance projection info that subsumes `ti2`. -/
|
||
def isInstanceProjectionInfoFor (kind : GoToKind) (ti1 ti2 : TermInfo) : MetaM Bool := do
|
||
if kind == .type then
|
||
return false
|
||
let some startPos1 := ti1.stx.getPos? (canonicalOnly := true)
|
||
| return false
|
||
let some startPos2 := ti2.stx.getPos? (canonicalOnly := true)
|
||
| return false
|
||
if startPos1 != startPos2 then
|
||
return false
|
||
let e1 ← instantiateMVars ti1.expr
|
||
let e2 ← instantiateMVars ti2.expr
|
||
if ! (← isInstanceProjection e1) || (← isInstanceProjection e2) then
|
||
return false
|
||
return e1.getAppFn' == e2.getAppFn'
|
||
|
||
structure GoToContext where
|
||
doc : DocumentMeta
|
||
kind : GoToKind
|
||
infoTree? : Option InfoTree
|
||
originInfo? : Option Info
|
||
children : PersistentArray InfoTree
|
||
|
||
abbrev GoToM α := ReaderT GoToContext MetaM α
|
||
|
||
def GoToM.run (ctx : GoToContext) (ci : ContextInfo) (lctx : LocalContext) (act : GoToM α) :
|
||
IO α :=
|
||
ci.runMetaM lctx <| ReaderT.run act ctx
|
||
|
||
def locationLinksFromDecl (declName : Name) : GoToM (Array LeanLocationLink) := do
|
||
let ctx ← read
|
||
-- Potentially this name is a builtin that has not been imported yet:
|
||
if ! (← getEnv).contains declName then
|
||
return #[]
|
||
let some (declModName, declModUri) ← declMod?
|
||
| return #[]
|
||
let some ranges ← findDeclarationRanges? declName
|
||
| return #[]
|
||
let originSelectionRange? := do
|
||
let i ← ctx.originInfo?
|
||
let r ← i.range?
|
||
return r.toLspRange ctx.doc.text
|
||
let ll : LeanLocationLink := {
|
||
originSelectionRange?
|
||
targetUri := declModUri
|
||
targetRange := ranges.range.toLspRange
|
||
targetSelectionRange := ranges.selectionRange.toLspRange
|
||
ident? := some ⟨declModName, declName.eraseMacroScopes⟩
|
||
isDefault := false
|
||
}
|
||
return #[ll]
|
||
where
|
||
declMod? : GoToM (Option (Name × DocumentUri)) := do
|
||
let ctx ← read
|
||
let declMod? ← findModuleOf? declName
|
||
match declMod? with
|
||
| some declModName =>
|
||
let some declModUri ← documentUriFromModule? declModName
|
||
| return none
|
||
return some (declModName, declModUri)
|
||
| none =>
|
||
return some (ctx.doc.mod, ctx.doc.uri)
|
||
|
||
def locationLinksFromBinder (id : FVarId) : GoToM (Array LeanLocationLink) := do
|
||
let ctx ← read
|
||
let doc := ctx.doc
|
||
let some binderInfo ← binderInfo?
|
||
| return #[]
|
||
let some binderInfoRange := binderInfo.range?
|
||
| return #[]
|
||
let binderInfoRange := binderInfoRange.toLspRange doc.text
|
||
let originSelectionRange? := do
|
||
let i ← ctx.originInfo?
|
||
let r ← i.range?
|
||
return r.toLspRange doc.text
|
||
let ll : LeanLocationLink := {
|
||
originSelectionRange?
|
||
targetUri := doc.uri
|
||
targetRange := binderInfoRange
|
||
targetSelectionRange := binderInfoRange
|
||
ident? := none
|
||
isDefault := false
|
||
}
|
||
return #[ll]
|
||
where
|
||
binderInfo? : GoToM (Option Info) := do
|
||
let ctx ← read
|
||
let some infoTree := ctx.infoTree?
|
||
| return none
|
||
return infoTree.findInfo? fun
|
||
| .ofTermInfo { isBinder := true, expr := .fvar id' .., .. } => id' == id
|
||
| _ => false
|
||
|
||
def locationLinksFromImport (i : CommandInfo) : GoToM (Array LeanLocationLink) := do
|
||
let ctx ← read
|
||
let `(Parser.Module.import| $[public]? $[meta]? import $[all]? $mod) := i.stx
|
||
| return #[]
|
||
let some modUri ← documentUriFromModule? mod.getId
|
||
| return #[]
|
||
let range := { start := ⟨0, 0⟩, «end» := ⟨0, 0⟩ : Range }
|
||
let originSelectionRange? := do
|
||
let r ← mod.raw.getRange? (canonicalOnly := true)
|
||
return r.toLspRange ctx.doc.text
|
||
let ll : LeanLocationLink := {
|
||
originSelectionRange?
|
||
targetUri := modUri
|
||
targetRange := range
|
||
targetSelectionRange := range
|
||
ident? := none
|
||
isDefault := false
|
||
}
|
||
return #[ll]
|
||
|
||
def locationLinksDefault : GoToM (Array LeanLocationLink) := do
|
||
-- If other location link resolutions fail, we try to show the elaborator or parser
|
||
let names ← defaultDeclNames
|
||
let mut ll := #[]
|
||
for name in names do
|
||
ll := ll ++ (← locationLinksFromDecl name)
|
||
return ll.map ({· with isDefault := true})
|
||
where
|
||
defaultDeclNames : GoToM (Array Name) := do
|
||
let ctx ← read
|
||
let env ← getEnv
|
||
let some originInfo := ctx.originInfo?
|
||
| return #[]
|
||
let some ei := originInfo.toElabInfo?
|
||
| return #[]
|
||
let e := ei.elaborator
|
||
if e == `Delab then
|
||
return #[]
|
||
if e == `Lean.Elab.Term.elabApp || e == `Lean.Elab.Term.elabIdent then
|
||
return #[]
|
||
let mut names := #[]
|
||
if env.contains e then
|
||
names := names.push e
|
||
if ctx.kind == .declaration && env.contains ei.stx.getKind then
|
||
names := names.push ei.stx.getKind
|
||
return names
|
||
|
||
def locationLinksFromErrorNameInfo (eni : ErrorNameInfo) : GoToM (Array LeanLocationLink) := do
|
||
let ctx ← read
|
||
let some explan := getErrorExplanationRaw? (← getEnv) eni.errorName
|
||
| return #[]
|
||
let some loc := explan.declLoc?
|
||
| return #[]
|
||
let some modUri ← documentUriFromModule? loc.module
|
||
| return #[]
|
||
let range := loc.range.toLspRange
|
||
let originSelectionRange? := do
|
||
let r ← eni.stx.getRange? (canonicalOnly := true)
|
||
return r.toLspRange ctx.doc.text
|
||
let link : LeanLocationLink := {
|
||
originSelectionRange?
|
||
targetUri := modUri
|
||
targetRange := range
|
||
targetSelectionRange := range
|
||
ident? := none
|
||
isDefault := false
|
||
}
|
||
return #[link]
|
||
|
||
def locationLinksFromInstanceProjection (e : Expr) : GoToM (Array LeanLocationLink) := do
|
||
-- Go-to-definition on a projection application of a typeclass
|
||
-- should return all instances generated by TC.
|
||
let .const n _ := e.getAppFn.consumeMData
|
||
| return #[]
|
||
let some instArg ← getInstanceProjectionArg? e
|
||
| return #[]
|
||
let mut results := #[]
|
||
for inst in ← extractInstances instArg do
|
||
results := results.append <| ← locationLinksFromDecl inst
|
||
results := results.append <| ← locationLinksFromDecl n
|
||
return results
|
||
where
|
||
extractInstances (e : Expr) : GoToM (Array Name) := do
|
||
match e with
|
||
| .const declName _ =>
|
||
if ! (← Lean.Meta.isInstance declName) then
|
||
return #[]
|
||
return #[declName]
|
||
| .app fn arg =>
|
||
let fnInstances ← extractInstances fn
|
||
let argInstances ← extractInstances arg
|
||
return argInstances ++ fnInstances
|
||
| .mdata _ e =>
|
||
extractInstances e
|
||
| _ =>
|
||
return #[]
|
||
|
||
def locationLinksFromTermInfo (ti : TermInfo) : GoToM (Array LeanLocationLink) := do
|
||
let ctx ← read
|
||
let mut ll := #[]
|
||
let exprs ← ctx.kind.determineTargetExprs ti
|
||
for expr in exprs do
|
||
let newLL ←
|
||
match expr.consumeMData with
|
||
| Expr.const n .. =>
|
||
locationLinksFromDecl n
|
||
| Expr.fvar id .. =>
|
||
locationLinksFromBinder id
|
||
| _ =>
|
||
locationLinksFromInstanceProjection expr
|
||
ll := ll ++ newLL
|
||
return ll
|
||
|
||
def locationLinksFromDelabTermInfo (dti : DelabTermInfo) : GoToM (Array LeanLocationLink) := do
|
||
let ctx ← read
|
||
let { toTermInfo := ti, location?, .. } := dti
|
||
let some location := location?
|
||
| return ← locationLinksFromTermInfo ti
|
||
let some targetUri ← documentUriFromModule? location.module
|
||
-- If we fail to find a DocumentUri, use the term info so that we have something to jump to.
|
||
| return ← locationLinksFromTermInfo ti
|
||
let range := location.range.toLspRange
|
||
let originSelectionRange? := do
|
||
let r ← Info.ofDelabTermInfo dti |>.range?
|
||
return r.toLspRange ctx.doc.text
|
||
let result : LeanLocationLink := {
|
||
originSelectionRange?
|
||
targetUri
|
||
targetRange := range
|
||
targetSelectionRange := range
|
||
ident? := none
|
||
isDefault := false
|
||
}
|
||
return #[result]
|
||
|
||
def locationLinksFromFieldInfo (fi : FieldInfo) : GoToM (Array LeanLocationLink) := do
|
||
let ctx ← read
|
||
if ctx.kind != .type then
|
||
return ← locationLinksFromDecl fi.projName
|
||
let expr ← instantiateMVars (← Meta.inferType fi.val)
|
||
let some n := expr.getAppFn.constName?
|
||
| return #[]
|
||
locationLinksFromDecl n
|
||
|
||
def locationLinksFromOptionInfo (i : OptionInfo) : GoToM (Array LeanLocationLink) :=
|
||
locationLinksFromDecl i.declName
|
||
|
||
def locationLinksFromCommandInfo (i : CommandInfo) : GoToM (Array LeanLocationLink) := do
|
||
let ctx ← read
|
||
if ! (i matches ⟨`import, _⟩) || ctx.kind == .type then
|
||
return #[]
|
||
locationLinksFromImport i
|
||
|
||
def locationLinksOfInfo (doc : DocumentMeta) (kind : GoToKind) (ictx : InfoWithCtx)
|
||
(infoTree? : Option InfoTree := none) : IO (Array LeanLocationLink) := do
|
||
let ctx : GoToContext := {
|
||
doc
|
||
kind
|
||
infoTree?
|
||
originInfo? := some ictx.info
|
||
children := ictx.children
|
||
}
|
||
GoToM.run ctx ictx.ctx ictx.info.lctx do
|
||
let ll ←
|
||
match ictx.info with
|
||
| .ofTermInfo ti =>
|
||
locationLinksFromTermInfo ti
|
||
| .ofDelabTermInfo dti =>
|
||
locationLinksFromDelabTermInfo dti
|
||
| .ofFieldInfo fi =>
|
||
locationLinksFromFieldInfo fi
|
||
| .ofOptionInfo oi =>
|
||
locationLinksFromOptionInfo oi
|
||
| .ofCommandInfo cci =>
|
||
locationLinksFromCommandInfo cci
|
||
| .ofErrorNameInfo eni =>
|
||
locationLinksFromErrorNameInfo eni
|
||
| _ =>
|
||
pure #[]
|
||
if kind == .declaration || ll.isEmpty then
|
||
return ll ++ (← locationLinksDefault)
|
||
return ll
|
||
|
||
end Lean.Server
|