/- Copyright (c) 2023 Kim Morrison. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Kim Morrison -/ prelude import Lean.CoreM import Lean.AddDecl import Lean.Util.FoldConsts /-! # `Lean.Environment.replay` `replay env constantMap` will "replay" all the constants in `constantMap : HashMap Name ConstantInfo` into `env`, sending each declaration to the kernel for checking. `replay` does not send constructors or recursors in `constantMap` to the kernel, but rather checks that they are identical to constructors or recursors generated in the environment after replaying any inductive definitions occurring in `constantMap`. `replay` can be used either as: * a verifier for an `Environment`, by sending everything to the kernel, or * a mechanism to safely transfer constants from one `Environment` to another. -/ namespace Lean.Environment namespace Replay structure Context where newConstants : Std.HashMap Name ConstantInfo structure State where env : Environment remaining : NameSet := {} pending : NameSet := {} postponedConstructors : NameSet := {} postponedRecursors : NameSet := {} abbrev M := ReaderT Context <| StateRefT State IO /-- Check if a `Name` still needs processing. If so, move it from `remaining` to `pending`. -/ def isTodo (name : Name) : M Bool := do let r := (← get).remaining if r.contains name then modify fun s => { s with remaining := s.remaining.erase name, pending := s.pending.insert name } return true else return false /-- Use the current `Environment` to throw a `Kernel.Exception`. -/ def throwKernelException (ex : Kernel.Exception) : M Unit := do throw <| .userError <| (← ex.toMessageData {} |>.toString) /-- Add a declaration, possibly throwing a `Kernel.Exception`. -/ def addDecl (d : Declaration) : M Unit := do match (← get).env.addDeclCore 0 d (cancelTk? := none) with | .ok env => modify fun s => { s with env := env } | .error ex => throwKernelException ex mutual /-- Check if a `Name` still needs to be processed (i.e. is in `remaining`). If so, recursively replay any constants it refers to, to ensure we add declarations in the right order. The construct the `Declaration` from its stored `ConstantInfo`, and add it to the environment. -/ partial def replayConstant (name : Name) : M Unit := do if ← isTodo name then let some ci := (← read).newConstants[name]? | unreachable! replayConstants ci.getUsedConstantsAsSet -- Check that this name is still pending: a mutual block may have taken care of it. if (← get).pending.contains name then match ci with | .defnInfo info => addDecl (Declaration.defnDecl info) | .thmInfo info => addDecl (Declaration.thmDecl info) | .axiomInfo info => addDecl (Declaration.axiomDecl info) | .opaqueInfo info => addDecl (Declaration.opaqueDecl info) | .inductInfo info => let lparams := info.levelParams let nparams := info.numParams let all ← info.all.mapM fun n => do pure <| ((← read).newConstants[n]!) for o in all do modify fun s => { s with remaining := s.remaining.erase o.name, pending := s.pending.erase o.name } let ctorInfo ← all.mapM fun ci => do pure (ci, ← ci.inductiveVal!.ctors.mapM fun n => do pure ((← read).newConstants[n]!)) -- Make sure we are really finished with the constructors. for (_, ctors) in ctorInfo do for ctor in ctors do replayConstants ctor.getUsedConstantsAsSet let types : List InductiveType := ctorInfo.map fun ⟨ci, ctors⟩ => { name := ci.name type := ci.type ctors := ctors.map fun ci => { name := ci.name, type := ci.type } } addDecl (Declaration.inductDecl lparams nparams types false) -- We postpone checking constructors, -- and at the end make sure they are identical -- to the constructors generated when we replay the inductives. | .ctorInfo info => modify fun s => { s with postponedConstructors := s.postponedConstructors.insert info.name } -- Similarly we postpone checking recursors. | .recInfo info => modify fun s => { s with postponedRecursors := s.postponedRecursors.insert info.name } | .quotInfo _ => addDecl (Declaration.quotDecl) modify fun s => { s with pending := s.pending.erase name } /-- Replay a set of constants one at a time. -/ partial def replayConstants (names : NameSet) : M Unit := do for n in names do replayConstant n end /-- Check that all postponed constructors are identical to those generated when we replayed the inductives. -/ def checkPostponedConstructors : M Unit := do for ctor in (← get).postponedConstructors do match (← get).env.find? ctor, (← read).newConstants[ctor]? with | some (.ctorInfo info), some (.ctorInfo info') => if ! (info == info') then throw <| IO.userError s!"Invalid constructor {ctor}" | _, _ => throw <| IO.userError s!"No such constructor {ctor}" /-- Check that all postponed recursors are identical to those generated when we replayed the inductives. -/ def checkPostponedRecursors : M Unit := do for ctor in (← get).postponedRecursors do match (← get).env.find? ctor, (← read).newConstants[ctor]? with | some (.recInfo info), some (.recInfo info') => if ! (info == info') then throw <| IO.userError s!"Invalid recursor {ctor}" | _, _ => throw <| IO.userError s!"No such recursor {ctor}" end Replay open Replay /-- "Replay" some constants into an `Environment`, sending them to the kernel for checking. Throws a `IO.userError` if the kernel rejects a constant, or if there are malformed recursors or constructors for inductive types. -/ def replay (newConstants : Std.HashMap Name ConstantInfo) (env : Environment) : IO Environment := do let mut remaining : NameSet := ∅ for (n, ci) in newConstants.toList do -- We skip unsafe constants, and also partial constants. -- Later we may want to handle partial constants. if !ci.isUnsafe && !ci.isPartial then remaining := remaining.insert n let (_, s) ← StateRefT'.run (s := { env, remaining }) do ReaderT.run (r := { newConstants }) do for n in remaining do replayConstant n checkPostponedConstructors checkPostponedRecursors return s.env