As [reported on Zulip](https://leanprover.zulipchat.com/#narrow/stream/341532-lean4-dev/topic/Find.20references.20broken.20in.20lean.20core/near/437051935). The `mainModuleName` was being set incorrectly when browsing lean core sources, resulting in failure of cross-file server requests like "Find References". Because the `srcSearchPath` is generated asynchronously, we store it as a `Task Name` which is resolved some time before the header is finished parsing. (I don't think the `.get` here will ever block, because the srcSearchPath will be ready by the time the initial command snap is requested.) --------- Co-authored-by: Sebastian Ullrich <sebasti@nullri.ch>
175 lines
7.7 KiB
Text
175 lines
7.7 KiB
Text
/-
|
||
Copyright (c) 2019 Microsoft Corporation. All rights reserved.
|
||
Released under Apache 2.0 license as described in the file LICENSE.
|
||
Authors: Leonardo de Moura, Sebastian Ullrich
|
||
-/
|
||
prelude
|
||
import Lean.Language.Lean
|
||
import Lean.Util.Profile
|
||
import Lean.Server.References
|
||
import Lean.Util.Profiler
|
||
|
||
namespace Lean.Elab.Frontend
|
||
|
||
structure State where
|
||
commandState : Command.State
|
||
parserState : Parser.ModuleParserState
|
||
cmdPos : String.Pos
|
||
commands : Array Syntax := #[]
|
||
|
||
structure Context where
|
||
inputCtx : Parser.InputContext
|
||
|
||
abbrev FrontendM := ReaderT Context $ StateRefT State IO
|
||
|
||
def setCommandState (commandState : Command.State) : FrontendM Unit :=
|
||
modify fun s => { s with commandState := commandState }
|
||
|
||
@[inline] def runCommandElabM (x : Command.CommandElabM α) : FrontendM α := do
|
||
let ctx ← read
|
||
let s ← get
|
||
let cmdCtx : Command.Context := {
|
||
cmdPos := s.cmdPos
|
||
fileName := ctx.inputCtx.fileName
|
||
fileMap := ctx.inputCtx.fileMap
|
||
tacticCache? := none
|
||
snap? := none
|
||
}
|
||
match (← liftM <| EIO.toIO' <| (x cmdCtx).run s.commandState) with
|
||
| Except.error e => throw <| IO.Error.userError s!"unexpected internal error: {← e.toMessageData.toString}"
|
||
| Except.ok (a, sNew) => setCommandState sNew; return a
|
||
|
||
def elabCommandAtFrontend (stx : Syntax) : FrontendM Unit := do
|
||
runCommandElabM do
|
||
let initMsgs ← modifyGet fun st => (st.messages, { st with messages := {} })
|
||
Command.elabCommandTopLevel stx
|
||
let mut msgs := (← get).messages
|
||
-- `stx.hasMissing` should imply `initMsgs.hasErrors`, but the latter should be cheaper to check
|
||
-- in general
|
||
if !Language.Lean.showPartialSyntaxErrors.get (← getOptions) && initMsgs.hasErrors &&
|
||
stx.hasMissing then
|
||
-- discard elaboration errors, except for a few important and unlikely misleading ones, on
|
||
-- parse error
|
||
msgs := ⟨msgs.msgs.filter fun msg =>
|
||
msg.data.hasTag (fun tag => tag == `Elab.synthPlaceholder ||
|
||
tag == `Tactic.unsolvedGoals || (`_traceMsg).isSuffixOf tag)⟩
|
||
modify ({ · with messages := initMsgs ++ msgs })
|
||
|
||
def updateCmdPos : FrontendM Unit := do
|
||
modify fun s => { s with cmdPos := s.parserState.pos }
|
||
|
||
def getParserState : FrontendM Parser.ModuleParserState := do pure (← get).parserState
|
||
def getCommandState : FrontendM Command.State := do pure (← get).commandState
|
||
def setParserState (ps : Parser.ModuleParserState) : FrontendM Unit := modify fun s => { s with parserState := ps }
|
||
def setMessages (msgs : MessageLog) : FrontendM Unit := modify fun s => { s with commandState := { s.commandState with messages := msgs } }
|
||
def getInputContext : FrontendM Parser.InputContext := do pure (← read).inputCtx
|
||
|
||
def processCommand : FrontendM Bool := do
|
||
updateCmdPos
|
||
let cmdState ← getCommandState
|
||
let ictx ← getInputContext
|
||
let pstate ← getParserState
|
||
let scope := cmdState.scopes.head!
|
||
let pmctx := { env := cmdState.env, options := scope.opts, currNamespace := scope.currNamespace, openDecls := scope.openDecls }
|
||
match profileit "parsing" scope.opts fun _ => Parser.parseCommand ictx pmctx pstate cmdState.messages with
|
||
| (cmd, ps, messages) =>
|
||
modify fun s => { s with commands := s.commands.push cmd }
|
||
setParserState ps
|
||
setMessages messages
|
||
elabCommandAtFrontend cmd
|
||
pure (Parser.isTerminalCommand cmd)
|
||
|
||
partial def processCommands : FrontendM Unit := do
|
||
let done ← processCommand
|
||
unless done do
|
||
processCommands
|
||
|
||
end Frontend
|
||
|
||
open Frontend
|
||
|
||
def IO.processCommands (inputCtx : Parser.InputContext) (parserState : Parser.ModuleParserState) (commandState : Command.State) : IO State := do
|
||
let (_, s) ← (Frontend.processCommands.run { inputCtx := inputCtx }).run { commandState := commandState, parserState := parserState, cmdPos := parserState.pos }
|
||
pure s
|
||
|
||
def process (input : String) (env : Environment) (opts : Options) (fileName : Option String := none) : IO (Environment × MessageLog) := do
|
||
let fileName := fileName.getD "<input>"
|
||
let inputCtx := Parser.mkInputContext input fileName
|
||
let s ← IO.processCommands inputCtx { : Parser.ModuleParserState } (Command.mkState env {} opts)
|
||
pure (s.commandState.env, s.commandState.messages)
|
||
|
||
builtin_initialize
|
||
registerTraceClass `Elab.info
|
||
|
||
@[export lean_run_frontend]
|
||
def runFrontend
|
||
(input : String)
|
||
(opts : Options)
|
||
(fileName : String)
|
||
(mainModuleName : Name)
|
||
(trustLevel : UInt32 := 0)
|
||
(ileanFileName? : Option String := none)
|
||
(jsonOutput : Bool := false)
|
||
: IO (Environment × Bool) := do
|
||
let startTime := (← IO.monoNanosNow).toFloat / 1000000000
|
||
let inputCtx := Parser.mkInputContext input fileName
|
||
-- TODO: replace with `#lang` processing
|
||
if /- Lean #lang? -/ true then
|
||
-- Temporarily keep alive old cmdline driver for the Lean language so that we don't pay the
|
||
-- overhead of passing the environment between snapshots until we actually make good use of it
|
||
-- outside the server
|
||
let (header, parserState, messages) ← Parser.parseHeader inputCtx
|
||
-- allow `env` to be leaked, which would live until the end of the process anyway
|
||
let (env, messages) ← processHeader (leakEnv := true) header opts messages inputCtx trustLevel
|
||
let env := env.setMainModule mainModuleName
|
||
let mut commandState := Command.mkState env messages opts
|
||
let elabStartTime := (← IO.monoNanosNow).toFloat / 1000000000
|
||
|
||
if ileanFileName?.isSome then
|
||
-- Collect InfoTrees so we can later extract and export their info to the ilean file
|
||
commandState := { commandState with infoState.enabled := true }
|
||
|
||
let s ← IO.processCommands inputCtx parserState commandState
|
||
Language.reportMessages s.commandState.messages opts jsonOutput
|
||
|
||
if let some ileanFileName := ileanFileName? then
|
||
let trees := s.commandState.infoState.trees.toArray
|
||
let references ←
|
||
Lean.Server.findModuleRefs inputCtx.fileMap trees (localVars := false) |>.toLspModuleRefs
|
||
let ilean := { module := mainModuleName, references : Lean.Server.Ilean }
|
||
IO.FS.writeFile ileanFileName $ Json.compress $ toJson ilean
|
||
|
||
if let some out := trace.profiler.output.get? opts then
|
||
let traceState := s.commandState.traceState
|
||
-- importing does not happen in an elaboration monad, add now
|
||
let traceState := { traceState with
|
||
traces := #[{
|
||
ref := .missing,
|
||
msg := .trace { cls := `Import, startTime, stopTime := elabStartTime }
|
||
(.ofFormat "importing") #[]
|
||
}].toPArray' ++ traceState.traces
|
||
}
|
||
let profile ← Firefox.Profile.export mainModuleName.toString startTime traceState opts
|
||
IO.FS.writeFile ⟨out⟩ <| Json.compress <| toJson profile
|
||
|
||
return (s.commandState.env, !s.commandState.messages.hasErrors)
|
||
|
||
let ctx := { inputCtx with }
|
||
let processor := Language.Lean.process
|
||
let snap ← processor (fun _ => pure <| .ok { mainModuleName, opts, trustLevel }) none ctx
|
||
let snaps := Language.toSnapshotTree snap
|
||
snaps.runAndReport opts jsonOutput
|
||
if let some ileanFileName := ileanFileName? then
|
||
let trees := snaps.getAll.concatMap (match ·.infoTree? with | some t => #[t] | _ => #[])
|
||
let references := Lean.Server.findModuleRefs inputCtx.fileMap trees (localVars := false)
|
||
let ilean := { module := mainModuleName, references := ← references.toLspModuleRefs : Lean.Server.Ilean }
|
||
IO.FS.writeFile ileanFileName $ Json.compress $ toJson ilean
|
||
|
||
let hasErrors := snaps.getAll.any (·.diagnostics.msgLog.hasErrors)
|
||
-- TODO: remove default when reworking cmdline interface in Lean; currently the only case
|
||
-- where we use the environment despite errors in the file is `--stats`
|
||
let env := Language.Lean.waitForFinalEnv? snap |>.getD (← mkEmptyEnvironment)
|
||
pure (env, !hasErrors)
|
||
|
||
|
||
end Lean.Elab
|