lean4-htt/src/Lean/Elab/Command.lean
Sebastian Ullrich f3cb8a6c2d
fix: interrupt exception was swallowed by some tryCatchRuntimeEx uses (#4569)
This appears to have been a semantic merge conflict between #3940 and
#4129. The effect on the language server is that if two edits are
sufficiently close in time to create an interrupt, some elaboration
steps like `simp` may accidentally catch the exception when it is
triggered during their execution, which makes incrementality assume that
elaboration of the body was successful, which can lead to incorrect
reuse, presenting the interrupted state to the user with symptoms such
as "uses sorry" without accompanying errors and incorrect lints.
2024-06-27 10:03:22 +00:00

704 lines
29 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) 2019 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura, Gabriel Ebner
-/
prelude
import Lean.Meta.Diagnostics
import Lean.Elab.Binders
import Lean.Elab.SyntheticMVars
import Lean.Elab.SetOption
import Lean.Language.Basic
namespace Lean.Elab.Command
structure Scope where
header : String
opts : Options := {}
currNamespace : Name := Name.anonymous
openDecls : List OpenDecl := []
levelNames : List Name := []
/-- section variables -/
varDecls : Array (TSyntax ``Parser.Term.bracketedBinder) := #[]
/-- Globally unique internal identifiers for the `varDecls` -/
varUIds : Array Name := #[]
/-- noncomputable sections automatically add the `noncomputable` modifier to any declaration we cannot generate code for. -/
isNoncomputable : Bool := false
deriving Inhabited
structure State where
env : Environment
messages : MessageLog := {}
scopes : List Scope := [{ header := "" }]
nextMacroScope : Nat := firstFrontendMacroScope + 1
maxRecDepth : Nat
ngen : NameGenerator := {}
infoState : InfoState := {}
traceState : TraceState := {}
deriving Nonempty
structure Context where
fileName : String
fileMap : FileMap
currRecDepth : Nat := 0
cmdPos : String.Pos := 0
macroStack : MacroStack := []
currMacroScope : MacroScope := firstFrontendMacroScope
ref : Syntax := Syntax.missing
tacticCache? : Option (IO.Ref Tactic.Cache)
/--
Snapshot for incremental reuse and reporting of command elaboration. Currently only used for
(mutual) defs and contained tactics, in which case the `DynamicSnapshot` is a
`HeadersParsedSnapshot`.
Definitely resolved in `Language.Lean.process.doElab`.
Invariant: if the bundle's `old?` is set, the context and state at the beginning of current and
old elaboration are identical.
-/
snap? : Option (Language.SnapshotBundle Language.DynamicSnapshot)
/-- Cancellation token forwarded to `Core.cancelTk?`. -/
cancelTk? : Option IO.CancelToken
/--
If set (when `showPartialSyntaxErrors` is not set and parsing failed), suppresses most elaboration
errors; see also `logMessage` below.
-/
suppressElabErrors : Bool := false
abbrev CommandElabCoreM (ε) := ReaderT Context $ StateRefT State $ EIO ε
abbrev CommandElabM := CommandElabCoreM Exception
abbrev CommandElab := Syntax → CommandElabM Unit
structure Linter where
run : Syntax → CommandElabM Unit
name : Name := by exact decl_name%
/-
Make the compiler generate specialized `pure`/`bind` so we do not have to optimize through the
whole monad stack at every use site. May eventually be covered by `deriving`.
Remark: see comment at TermElabM
-/
@[always_inline]
instance : Monad CommandElabM := let i := inferInstanceAs (Monad CommandElabM); { pure := i.pure, bind := i.bind }
/--
Like `Core.tryCatchRuntimeEx`; runtime errors are generally used to abort term elaboration, so we do
want to catch and process them at the command level.
-/
@[inline] protected def tryCatch (x : CommandElabM α) (h : Exception → CommandElabM α) :
CommandElabM α := do
try
x
catch ex =>
if ex.isInterrupt then
throw ex
else
h ex
instance : MonadExceptOf Exception CommandElabM where
throw := throw
tryCatch := Command.tryCatch
def mkState (env : Environment) (messages : MessageLog := {}) (opts : Options := {}) : State := {
env := env
messages := messages
scopes := [{ header := "", opts := opts }]
maxRecDepth := maxRecDepth.get opts
}
/- Linters should be loadable as plugins, so store in a global IO ref instead of an attribute managed by the
environment (which only contains `import`ed objects). -/
builtin_initialize lintersRef : IO.Ref (Array Linter) ← IO.mkRef #[]
builtin_initialize registerTraceClass `Elab.lint
def addLinter (l : Linter) : IO Unit := do
let ls ← lintersRef.get
lintersRef.set (ls.push l)
instance : MonadInfoTree CommandElabM where
getInfoState := return (← get).infoState
modifyInfoState f := modify fun s => { s with infoState := f s.infoState }
instance : MonadEnv CommandElabM where
getEnv := do pure (← get).env
modifyEnv f := modify fun s => { s with env := f s.env }
@[always_inline]
instance : MonadOptions CommandElabM where
getOptions := do pure (← get).scopes.head!.opts
protected def getRef : CommandElabM Syntax :=
return (← read).ref
instance : AddMessageContext CommandElabM where
addMessageContext := addMessageContextPartial
instance : MonadRef CommandElabM where
getRef := Command.getRef
withRef ref x := withReader (fun ctx => { ctx with ref := ref }) x
instance : MonadTrace CommandElabM where
getTraceState := return (← get).traceState
modifyTraceState f := modify fun s => { s with traceState := f s.traceState }
instance : AddErrorMessageContext CommandElabM where
add ref msg := do
let ctx ← read
let ref := getBetterRef ref ctx.macroStack
let msg ← addMessageContext msg
let msg ← addMacroStack msg ctx.macroStack
return (ref, msg)
def mkMessageAux (ctx : Context) (ref : Syntax) (msgData : MessageData) (severity : MessageSeverity) : Message :=
let pos := ref.getPos?.getD ctx.cmdPos
let endPos := ref.getTailPos?.getD pos
mkMessageCore ctx.fileName ctx.fileMap msgData severity pos endPos
private def addTraceAsMessagesCore (ctx : Context) (log : MessageLog) (traceState : TraceState) : MessageLog := Id.run do
if traceState.traces.isEmpty then return log
let mut traces : HashMap (String.Pos × String.Pos) (Array MessageData) := ∅
for traceElem in traceState.traces do
let ref := replaceRef traceElem.ref ctx.ref
let pos := ref.getPos?.getD 0
let endPos := ref.getTailPos?.getD pos
traces := traces.insert (pos, endPos) <| traces.findD (pos, endPos) #[] |>.push traceElem.msg
let mut log := log
let traces' := traces.toArray.qsort fun ((a, _), _) ((b, _), _) => a < b
for ((pos, endPos), traceMsg) in traces' do
let data := .tagged `_traceMsg <| .joinSep traceMsg.toList "\n"
log := log.add <| mkMessageCore ctx.fileName ctx.fileMap data .information pos endPos
return log
private def addTraceAsMessages : CommandElabM Unit := do
let ctx ← read
-- do not add trace messages if `trace.profiler.output` is set as it would be redundant and
-- pretty printing the trace messages is expensive
if trace.profiler.output.get? (← getOptions) |>.isNone then
modify fun s => { s with
messages := addTraceAsMessagesCore ctx s.messages s.traceState
traceState.traces := {}
}
private def runCore (x : CoreM α) : CommandElabM α := do
let s ← get
let ctx ← read
let heartbeats ← IO.getNumHeartbeats
let env := Kernel.resetDiag s.env
let scope := s.scopes.head!
let coreCtx : Core.Context := {
fileName := ctx.fileName
fileMap := ctx.fileMap
currRecDepth := ctx.currRecDepth
maxRecDepth := s.maxRecDepth
ref := ctx.ref
currNamespace := scope.currNamespace
openDecls := scope.openDecls
initHeartbeats := heartbeats
currMacroScope := ctx.currMacroScope
options := scope.opts
cancelTk? := ctx.cancelTk?
suppressElabErrors := ctx.suppressElabErrors }
let x : EIO _ _ := x.run coreCtx {
env
ngen := s.ngen
nextMacroScope := s.nextMacroScope
infoState.enabled := s.infoState.enabled
traceState := s.traceState
}
let (ea, coreS) ← liftM x
modify fun s => { s with
env := coreS.env
nextMacroScope := coreS.nextMacroScope
ngen := coreS.ngen
infoState.trees := s.infoState.trees.append coreS.infoState.trees
traceState.traces := coreS.traceState.traces.map fun t => { t with ref := replaceRef t.ref ctx.ref }
messages := s.messages ++ coreS.messages
}
return ea
def liftCoreM (x : CoreM α) : CommandElabM α := do
MonadExcept.ofExcept (← runCore (observing x))
private def ioErrorToMessage (ctx : Context) (ref : Syntax) (err : IO.Error) : Message :=
let ref := getBetterRef ref ctx.macroStack
mkMessageAux ctx ref (toString err) MessageSeverity.error
@[inline] def liftIO {α} (x : IO α) : CommandElabM α := do
let ctx ← read
IO.toEIO (fun (ex : IO.Error) => Exception.error ctx.ref ex.toString) x
instance : MonadLiftT IO CommandElabM where
monadLift := liftIO
def getScope : CommandElabM Scope := do pure (← get).scopes.head!
instance : MonadResolveName CommandElabM where
getCurrNamespace := return (← getScope).currNamespace
getOpenDecls := return (← getScope).openDecls
instance : MonadLog CommandElabM where
getRef := getRef
getFileMap := return (← read).fileMap
getFileName := return (← read).fileName
hasErrors := return (← get).messages.hasErrors
logMessage msg := do
if (← read).suppressElabErrors then
-- discard elaboration errors on parse error
-- NOTE: unlike `CoreM`'s `logMessage`, we do not currently have any command-level errors that
-- we want to allowlist
return
let currNamespace ← getCurrNamespace
let openDecls ← getOpenDecls
let msg := { msg with data := MessageData.withNamingContext { currNamespace := currNamespace, openDecls := openDecls } msg.data }
modify fun s => { s with messages := s.messages.add msg }
def runLinters (stx : Syntax) : CommandElabM Unit := do
profileitM Exception "linting" (← getOptions) do
withTraceNode `Elab.lint (fun _ => return m!"running linters") do
let linters ← lintersRef.get
unless linters.isEmpty do
for linter in linters do
withTraceNode `Elab.lint (fun _ => return m!"running linter: {linter.name}")
(tag := linter.name.toString) do
let savedState ← get
try
linter.run stx
catch ex =>
logException ex
finally
modify fun s => { savedState with messages := s.messages }
protected def getCurrMacroScope : CommandElabM Nat := do pure (← read).currMacroScope
protected def getMainModule : CommandElabM Name := do pure (← getEnv).mainModule
protected def withFreshMacroScope {α} (x : CommandElabM α) : CommandElabM α := do
let fresh ← modifyGet (fun st => (st.nextMacroScope, { st with nextMacroScope := st.nextMacroScope + 1 }))
withReader (fun ctx => { ctx with currMacroScope := fresh }) x
instance : MonadQuotation CommandElabM where
getCurrMacroScope := Command.getCurrMacroScope
getMainModule := Command.getMainModule
withFreshMacroScope := Command.withFreshMacroScope
unsafe def mkCommandElabAttributeUnsafe (ref : Name) : IO (KeyedDeclsAttribute CommandElab) :=
mkElabAttribute CommandElab `builtin_command_elab `command_elab `Lean.Parser.Command `Lean.Elab.Command.CommandElab "command" ref
@[implemented_by mkCommandElabAttributeUnsafe]
opaque mkCommandElabAttribute (ref : Name) : IO (KeyedDeclsAttribute CommandElab)
builtin_initialize commandElabAttribute : KeyedDeclsAttribute CommandElab ← mkCommandElabAttribute decl_name%
private def mkInfoTree (elaborator : Name) (stx : Syntax) (trees : PersistentArray InfoTree) : CommandElabM InfoTree := do
let ctx ← read
let s ← get
let scope := s.scopes.head!
let tree := InfoTree.node (Info.ofCommandInfo { elaborator, stx }) trees
let ctx := PartialContextInfo.commandCtx {
env := s.env, fileMap := ctx.fileMap, mctx := {}, currNamespace := scope.currNamespace,
openDecls := scope.openDecls, options := scope.opts, ngen := s.ngen
}
return InfoTree.context ctx tree
/--
Disables incremental command reuse *and* reporting for `act` if `cond` is true by setting
`Context.snap?` to `none`.
-/
def withoutCommandIncrementality (cond : Bool) (act : CommandElabM α) : CommandElabM α := do
let opts ← getOptions
withReader (fun ctx => { ctx with snap? := ctx.snap?.filter fun snap => Id.run do
if let some old := snap.old? then
if cond && opts.getBool `trace.Elab.reuse then
dbg_trace "reuse stopped: guard failed at {old.stx}"
return !cond
}) act
private def elabCommandUsing (s : State) (stx : Syntax) : List (KeyedDeclsAttribute.AttributeEntry CommandElab) → CommandElabM Unit
| [] => withInfoTreeContext (mkInfoTree := mkInfoTree `no_elab stx) <| throwError "unexpected syntax{indentD stx}"
| (elabFn::elabFns) =>
catchInternalId unsupportedSyntaxExceptionId
(do
-- prevent unsupported commands from accidentally accessing `Context.snap?` (e.g. by nested
-- supported commands)
withoutCommandIncrementality (!(← isIncrementalElab elabFn.declName)) do
withInfoTreeContext (mkInfoTree := mkInfoTree elabFn.declName stx) do
elabFn.value stx)
(fun _ => do set s; elabCommandUsing s stx elabFns)
/-- Elaborate `x` with `stx` on the macro stack -/
def withMacroExpansion (beforeStx afterStx : Syntax) (x : CommandElabM α) : CommandElabM α :=
withInfoContext (mkInfo := pure <| .ofMacroExpansionInfo { stx := beforeStx, output := afterStx, lctx := .empty }) do
withReader (fun ctx => { ctx with macroStack := { before := beforeStx, after := afterStx } :: ctx.macroStack }) x
instance : MonadMacroAdapter CommandElabM where
getCurrMacroScope := getCurrMacroScope
getNextMacroScope := return (← get).nextMacroScope
setNextMacroScope next := modify fun s => { s with nextMacroScope := next }
instance : MonadRecDepth CommandElabM where
withRecDepth d x := withReader (fun ctx => { ctx with currRecDepth := d }) x
getRecDepth := return (← read).currRecDepth
getMaxRecDepth := return (← get).maxRecDepth
builtin_initialize registerTraceClass `Elab.command
open Language in
/-- Snapshot after macro expansion of a command. -/
structure MacroExpandedSnapshot extends Snapshot where
/-- The declaration name of the macro. -/
macroDecl : Name
/-- The expanded syntax tree. -/
newStx : Syntax
/-- `State.nextMacroScope` after expansion. -/
newNextMacroScope : Nat
/-- Whether any traces were present after expansion. -/
hasTraces : Bool
/--
Follow-up elaboration snapshots, one per command if `newStx` is a sequence of commands.
-/
next : Array (SnapshotTask DynamicSnapshot)
deriving TypeName
open Language in
instance : ToSnapshotTree MacroExpandedSnapshot where
toSnapshotTree s := ⟨s.toSnapshot, s.next.map (·.map (sync := true) toSnapshotTree)⟩
partial def elabCommand (stx : Syntax) : CommandElabM Unit := do
withLogging <| withRef stx <| withIncRecDepth <| withFreshMacroScope do
match stx with
| Syntax.node _ k args =>
if k == nullKind then
-- list of commands => elaborate in order
-- The parser will only ever return a single command at a time, but syntax quotations can return multiple ones
-- Incrementality is currently limited to the common case where the sequence is the direct
-- output of a macro, see below.
withoutCommandIncrementality true do
args.forM elabCommand
else withTraceNode `Elab.command (fun _ => return stx) (tag :=
-- special case: show actual declaration kind for `declaration` commands
(if stx.isOfKind ``Parser.Command.declaration then stx[1] else stx).getKind.toString) do
let s ← get
match (← liftMacroM <| expandMacroImpl? s.env stx) with
| some (decl, stxNew?) =>
withInfoTreeContext (mkInfoTree := mkInfoTree decl stx) do
let stxNew ← liftMacroM <| liftExcept stxNew?
withMacroExpansion stx stxNew do
-- Support incrementality; see also Note [Incremental Macros]
if let some snap := (←read).snap? then
-- Unpack nested commands; see `MacroExpandedSnapshot.next`
let cmds := if stxNew.isOfKind nullKind then stxNew.getArgs else #[stxNew]
let nextMacroScope := (← get).nextMacroScope
let hasTraces := (← getTraceState).traces.size > 0
let oldSnap? := do
let oldSnap ← snap.old?
let oldSnap ← oldSnap.val.get.toTyped? MacroExpandedSnapshot
guard <| oldSnap.macroDecl == decl && oldSnap.newNextMacroScope == nextMacroScope
-- check absence of traces; see Note [Incremental Macros]
guard <| !oldSnap.hasTraces && !hasTraces
return oldSnap
let oldCmds? := oldSnap?.map fun old =>
if old.newStx.isOfKind nullKind then old.newStx.getArgs else #[old.newStx]
Language.withAlwaysResolvedPromises cmds.size fun cmdPromises => do
snap.new.resolve <| .ofTyped {
diagnostics := .empty
macroDecl := decl
newStx := stxNew
newNextMacroScope := nextMacroScope
hasTraces
next := cmdPromises.zipWith cmds fun cmdPromise cmd =>
{ range? := cmd.getRange?, task := cmdPromise.result }
: MacroExpandedSnapshot
}
-- After the first command whose syntax tree changed, we must disable
-- incremental reuse
let mut reusedCmds := true
let opts ← getOptions
-- For each command, associate it with new promise and old snapshot, if any, and
-- elaborate recursively
for cmd in cmds, cmdPromise in cmdPromises, i in [0:cmds.size] do
let oldCmd? := oldCmds?.bind (·[i]?)
withReader ({ · with snap? := some {
new := cmdPromise
old? := do
guard reusedCmds
let old ← oldSnap?
return { stx := (← oldCmd?), val := (← old.next[i]?) }
} }) do
elabCommand cmd
-- Resolve promise for commands not supporting incrementality; waiting for
-- `withAlwaysResolvedPromises` to do this could block reporting by later
-- commands
cmdPromise.resolve default
reusedCmds := reusedCmds && oldCmd?.any (·.eqWithInfoAndTraceReuse opts cmd)
else
elabCommand stxNew
| _ =>
match commandElabAttribute.getEntries s.env k with
| [] =>
withInfoTreeContext (mkInfoTree := mkInfoTree `no_elab stx) <|
throwError "elaboration function for '{k}' has not been implemented"
| elabFns => elabCommandUsing s stx elabFns
| _ =>
withInfoTreeContext (mkInfoTree := mkInfoTree `no_elab stx) <|
throwError "unexpected command"
builtin_initialize registerTraceClass `Elab.input
/-- Option for showing elaboration errors from partial syntax errors. -/
register_builtin_option showPartialSyntaxErrors : Bool := {
defValue := false
descr := "show elaboration errors from partial syntax trees (i.e. after parser recovery)"
}
builtin_initialize
registerTraceClass `Elab.info
registerTraceClass `Elab.snapshotTree
/--
`elabCommand` wrapper that should be used for the initial invocation, not for recursive calls after
macro expansion etc.
-/
def elabCommandTopLevel (stx : Syntax) : CommandElabM Unit := withRef stx do profileitM Exception "elaboration" (← getOptions) do
withReader ({ · with suppressElabErrors :=
stx.hasMissing && !showPartialSyntaxErrors.get (← getOptions) }) do
let initMsgs ← modifyGet fun st => (st.messages, { st with messages := {} })
let initInfoTrees ← getResetInfoTrees
try
-- We should *not* factor out `elabCommand`'s `withLogging` to here since it would make its error
-- recovery more coarse. In particular, If `c` in `set_option ... in $c` fails, the remaining
-- `end` command of the `in` macro would be skipped and the option would be leaked to the outside!
elabCommand stx
withLogging do
runLinters stx
finally
-- note the order: first process current messages & info trees, then add back old messages & trees,
-- then convert new traces to messages
let mut msgs := (← get).messages
for tree in (← getInfoTrees) do
trace[Elab.info] (← tree.format)
if let some snap := (← read).snap? then
-- We can assume that the root command snapshot is not involved in parallelism yet, so this
-- should be true iff the command supports incrementality
if (← IO.hasFinished snap.new.result) then
trace[Elab.snapshotTree]
Language.ToSnapshotTree.toSnapshotTree snap.new.result.get |>.format
modify fun st => { st with
messages := initMsgs ++ msgs
infoState := { st.infoState with trees := initInfoTrees ++ st.infoState.trees }
}
addTraceAsMessages
/-- Adapt a syntax transformation to a regular, command-producing elaborator. -/
def adaptExpander (exp : Syntax → CommandElabM Syntax) : CommandElab := fun stx => do
let stx' ← exp stx
withMacroExpansion stx stx' <| elabCommand stx'
private def getVarDecls (s : State) : Array Syntax :=
s.scopes.head!.varDecls
instance {α} : Inhabited (CommandElabM α) where
default := throw default
private def mkMetaContext : Meta.Context := {
config := { foApprox := true, ctxApprox := true, quasiPatternApprox := true }
}
open Lean.Parser.Term in
/-- Return identifier names in the given bracketed binder. -/
def getBracketedBinderIds : Syntax → Array Name
| `(bracketedBinderF|($ids* $[: $ty?]? $(_annot?)?)) => ids.map Syntax.getId
| `(bracketedBinderF|{$ids* $[: $ty?]?}) => ids.map Syntax.getId
| `(bracketedBinderF|[$id : $_]) => #[id.getId]
| `(bracketedBinderF|[$_]) => #[Name.anonymous]
| _ => #[]
private def mkTermContext (ctx : Context) (s : State) : Term.Context := Id.run do
let scope := s.scopes.head!
let mut sectionVars := {}
for id in scope.varDecls.concatMap getBracketedBinderIds, uid in scope.varUIds do
sectionVars := sectionVars.insert id uid
{ macroStack := ctx.macroStack
sectionVars := sectionVars
isNoncomputableSection := scope.isNoncomputable
tacticCache? := ctx.tacticCache? }
/--
Lift the `TermElabM` monadic action `x` into a `CommandElabM` monadic action.
Note that `x` is executed with an empty message log. Thus, `x` cannot modify/view messages produced by
previous commands.
If you need to access the free variables corresponding to the ones declared using the `variable` command,
consider using `runTermElabM`.
Recall that `TermElabM` actions can automatically lift `MetaM` and `CoreM` actions.
Example:
```
import Lean
open Lean Elab Command Meta
def printExpr (e : Expr) : MetaM Unit := do
IO.println s!"{← ppExpr e} : {← ppExpr (← inferType e)}"
#eval
liftTermElabM do
printExpr (mkConst ``Nat)
```
-/
def liftTermElabM (x : TermElabM α) : CommandElabM α := do
let ctx ← read
let s ← get
-- dbg_trace "heartbeats: {heartbeats}"
let scope := s.scopes.head!
-- We execute `x` with an empty message log. Thus, `x` cannot modify/view messages produced by previous commands.
-- This is useful for implementing `runTermElabM` where we use `Term.resetMessageLog`
let x : TermElabM _ := withSaveInfoContext x
-- make sure `observing` below also catches runtime exceptions (like we do by default in
-- `CommandElabM`)
let _ := MonadAlwaysExcept.except (m := TermElabM)
let x : MetaM _ := (observing (try x finally Meta.reportDiag)).run (mkTermContext ctx s) { levelNames := scope.levelNames }
let x : CoreM _ := x.run mkMetaContext {}
let ((ea, _), _) ← runCore x
MonadExcept.ofExcept ea
/--
Execute the monadic action `elabFn xs` as a `CommandElabM` monadic action, where `xs` are free variables
corresponding to all active scoped variables declared using the `variable` command.
This method is similar to `liftTermElabM`, but it elaborates all scoped variables declared using the `variable`
command.
Example:
```
import Lean
open Lean Elab Command Meta
variable {α : Type u} {f : αα}
variable (n : Nat)
#eval
runTermElabM fun xs => do
for x in xs do
IO.println s!"{← ppExpr x} : {← ppExpr (← inferType x)}"
```
-/
def runTermElabM (elabFn : Array Expr → TermElabM α) : CommandElabM α := do
let scope ← getScope
liftTermElabM <|
Term.withAutoBoundImplicit <|
Term.elabBinders scope.varDecls fun xs => do
-- We need to synthesize postponed terms because this is a checkpoint for the auto-bound implicit feature
-- If we don't use this checkpoint here, then auto-bound implicits in the postponed terms will not be handled correctly.
Term.synthesizeSyntheticMVarsNoPostponing
let mut sectionFVars := {}
for uid in scope.varUIds, x in xs do
sectionFVars := sectionFVars.insert uid x
withReader ({ · with sectionFVars := sectionFVars }) do
-- We don't want to store messages produced when elaborating `(getVarDecls s)` because they have already been saved when we elaborated the `variable`(s) command.
-- So, we use `Core.resetMessageLog`.
Core.resetMessageLog
let someType := mkSort levelZero
Term.addAutoBoundImplicits' xs someType fun xs _ =>
Term.withoutAutoBoundImplicit <| elabFn xs
/--
Catches and logs exceptions occurring in `x`. Unlike `try catch` in `CommandElabM`, this function
catches interrupt exceptions as well and thus is intended for use at the top level of elaboration.
Interrupt and abort exceptions are caught but not logged.
-/
@[inline] def withLoggingExceptions (x : CommandElabM Unit) : CommandElabCoreM Empty Unit := fun ctx ref =>
EIO.catchExceptions (withLogging x ctx ref) (fun _ => pure ())
private def liftAttrM {α} (x : AttrM α) : CommandElabM α := do
liftCoreM x
def getScopes : CommandElabM (List Scope) := do
pure (← get).scopes
def modifyScope (f : Scope → Scope) : CommandElabM Unit :=
modify fun s => { s with
scopes := match s.scopes with
| h::t => f h :: t
| [] => unreachable!
}
def withScope (f : Scope → Scope) (x : CommandElabM α) : CommandElabM α := do
match (← get).scopes with
| [] => x
| h :: t =>
try
modify fun s => { s with scopes := f h :: t }
x
finally
modify fun s => { s with scopes := h :: t }
def getLevelNames : CommandElabM (List Name) :=
return (← getScope).levelNames
def addUnivLevel (idStx : Syntax) : CommandElabM Unit := withRef idStx do
let id := idStx.getId
let levelNames ← getLevelNames
if levelNames.elem id then
throwAlreadyDeclaredUniverseLevel id
else
modifyScope fun scope => { scope with levelNames := id :: scope.levelNames }
def expandDeclId (declId : Syntax) (modifiers : Modifiers) : CommandElabM ExpandDeclIdResult := do
let currNamespace ← getCurrNamespace
let currLevelNames ← getLevelNames
let r ← Elab.expandDeclId currNamespace currLevelNames declId modifiers
for id in (← getScope).varDecls.concatMap getBracketedBinderIds do
if id == r.shortName then
throwError "invalid declaration name '{r.shortName}', there is a section variable with the same name"
return r
end Elab.Command
open Elab Command MonadRecDepth
/--
Lifts an action in `CommandElabM` into `CoreM`, updating the traces and the environment.
Commands that modify the processing of subsequent commands,
such as `open` and `namespace` commands,
only have an effect for the remainder of the `CommandElabM` computation passed here,
and do not affect subsequent commands.
-/
def liftCommandElabM (cmd : CommandElabM α) : CoreM α := do
let (a, commandState) ←
cmd.run {
fileName := ← getFileName
fileMap := ← getFileMap
ref := ← getRef
tacticCache? := none
snap? := none
cancelTk? := (← read).cancelTk?
} |>.run {
env := ← getEnv
maxRecDepth := ← getMaxRecDepth
scopes := [{ header := "", opts := ← getOptions }]
}
modify fun coreState => { coreState with
traceState.traces := coreState.traceState.traces ++ commandState.traceState.traces
env := commandState.env
}
if let some err := commandState.messages.toArray.find? (·.severity matches .error) then
throwError err.data
pure a
/--
Given a command elaborator `cmd`, returns a new command elaborator that
first evaluates any local `set_option ... in ...` clauses and then invokes `cmd` on what remains.
-/
partial def withSetOptionIn (cmd : CommandElab) : CommandElab := fun stx => do
if stx.getKind == ``Lean.Parser.Command.in &&
stx[0].getKind == ``Lean.Parser.Command.set_option then
let opts ← Elab.elabSetOption stx[0][1] stx[0][3]
Command.withScope (fun scope => { scope with opts }) do
withSetOptionIn cmd stx[2]
else
cmd stx
export Elab.Command (Linter addLinter)
end Lean