chore: remove save tactic (#7047)

This PR removes the `save` and `checkpoint` tactics that have been
superseded by incremental elaboration
This commit is contained in:
Sebastian Ullrich
2025-02-12 10:19:30 +01:00
committed by GitHub
parent f61e2989a2
commit f7e207a824
11 changed files with 5 additions and 201 deletions

View File

@@ -1014,31 +1014,6 @@ example : ∀ x : Nat, x = x := by unhygienic
-/
macro "unhygienic " t:tacticSeq : tactic => `(tactic| set_option tactic.hygienic false in $t)
/--
`checkpoint tac` acts the same as `tac`, but it caches the input and output of `tac`,
and if the file is re-elaborated and the input matches, the tactic is not re-run and
its effects are reapplied to the state. This is useful for improving responsiveness
when working on a long tactic proof, by wrapping expensive tactics with `checkpoint`.
See the `save` tactic, which may be more convenient to use.
(TODO: do this automatically and transparently so that users don't have to use
this combinator explicitly.)
-/
syntax (name := checkpoint) "checkpoint " tacticSeq : tactic
/--
`save` is defined to be the same as `skip`, but the elaborator has
special handling for occurrences of `save` in tactic scripts and will transform
`by tac1; save; tac2` to `by (checkpoint tac1); tac2`, meaning that the effect of `tac1`
will be cached and replayed. This is useful for improving responsiveness
when working on a long tactic proof, by using `save` after expensive tactics.
(TODO: do this automatically and transparently so that users don't have to use
this combinator explicitly.)
-/
macro (name := save) "save" : tactic => `(tactic| skip)
/--
The tactic `sleep ms` sleeps for `ms` milliseconds and does nothing.
It is used for debugging purposes only.

View File

@@ -95,7 +95,6 @@ structure Context where
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
@@ -619,8 +618,7 @@ private def mkTermContext (ctx : Context) (s : State) : CommandElabM Term.Contex
return {
macroStack := ctx.macroStack
sectionVars := sectionVars
isNoncomputableSection := scope.isNoncomputable
tacticCache? := ctx.tacticCache? }
isNoncomputableSection := scope.isNoncomputable }
/--
Lift the `TermElabM` monadic action `x` into a `CommandElabM` monadic action.
@@ -759,7 +757,6 @@ private def liftCommandElabMCore (cmd : CommandElabM α) (throwOnError : Bool) :
currRecDepth := ctx.currRecDepth
currMacroScope := ctx.currMacroScope
ref := ctx.ref
tacticCache? := none
snap? := none
cancelTk? := ctx.cancelTk?
suppressElabErrors := ctx.suppressElabErrors

View File

@@ -33,7 +33,6 @@ def setCommandState (commandState : Command.State) : FrontendM Unit :=
cmdPos := s.cmdPos
fileName := ctx.inputCtx.fileName
fileMap := ctx.inputCtx.fileMap
tacticCache? := none
snap? := none
cancelTk? := none
}

View File

@@ -22,7 +22,6 @@ import Lean.Elab.Tactic.Conv
import Lean.Elab.Tactic.Delta
import Lean.Elab.Tactic.Meta
import Lean.Elab.Tactic.Unfold
import Lean.Elab.Tactic.Cache
import Lean.Elab.Tactic.Calc
import Lean.Elab.Tactic.Congr
import Lean.Elab.Tactic.Guard

View File

@@ -135,70 +135,6 @@ where
@[builtin_tactic paren, builtin_incremental] def evalParen : Tactic :=
Term.withNarrowedArgTacticReuse 1 evalTactic
def isCheckpointableTactic (arg : Syntax) : TacticM Bool := do
-- TODO: make it parametric
let kind := arg.getKind
return kind == ``Lean.Parser.Tactic.save
/--
Takes a `sepByIndent tactic "; "`, and inserts `checkpoint` blocks for `save` tactics.
Input:
```
a
b
save
c
d
save
e
```
Output:
```
checkpoint
a
b
save
checkpoint
c
d
save
e
```
-/
-- Note that we need to preserve the separators to show the right goals after semicolons.
def addCheckpoints (stx : Syntax) : TacticM Syntax := do
if !( stx.getSepArgs.anyM isCheckpointableTactic) then return stx
-- do not checkpoint checkpointable tactic by itself to prevent infinite recursion
-- TODO: rethink approach if we add non-trivial checkpointable tactics
if stx.getNumArgs <= 2 then return stx
let mut currentCheckpointBlock := #[]
let mut output := #[]
-- `+ 1` to account for optional trailing separator
for i in [:(stx.getArgs.size + 1) / 2] do
let tac := stx[2*i]
let sep? := stx.getArgs[2*i+1]?
if ( isCheckpointableTactic tac) then
let checkpoint : Syntax :=
mkNode ``checkpoint #[
mkAtomFrom tac "checkpoint",
mkNode ``tacticSeq #[
mkNode ``tacticSeq1Indented #[
-- HACK: null node is not a valid tactic, but prevents infinite loop
mkNullNode (currentCheckpointBlock.push (mkNullNode #[tac]))
]
]
]
currentCheckpointBlock := #[]
output := output.push checkpoint
if let some sep := sep? then output := output.push sep
else
currentCheckpointBlock := currentCheckpointBlock.push tac
if let some sep := sep? then currentCheckpointBlock := currentCheckpointBlock.push sep
output := output ++ currentCheckpointBlock
return stx.setArgs output
@[builtin_tactic tacticSeq1Indented, builtin_incremental]
def evalTacticSeq1Indented : Tactic :=
Term.withNarrowedArgTacticReuse (argIdx := 0) evalSepTactics

View File

@@ -1,81 +0,0 @@
/-
Copyright (c) 2022 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Lean.Elab.Tactic.Basic
namespace Lean.Elab.Tactic
open Meta
private def equivMVarDecl (d₁ d₂ : MetavarDecl) : Bool :=
d₁.type == d₂.type
-- The following additional check does not seem to be necessary
/-
&&
d₁.lctx.decls.size == d₂.lctx.decls.size &&
Id.run do
for i in [:d₁.lctx.decls.size] do
match d₁.lctx.decls[i], d₂.lctx.decls[i] with
| some localDecl₁, some localDecl₂ => if localDecl₁.type != localDecl₂.type then return false
| none, none => pure ()
| _, _ => return false
return true
-/
register_builtin_option tactic.dbg_cache : Bool := {
defValue := false
group := "tactic"
descr := "enable tactic cache debug messages (remark: they are sent to the standard error)"
}
private def dbg_cache (msg : String) : TacticM Unit := do
if tactic.dbg_cache.get ( getOptions) then
dbg_trace msg
private def dbg_cache' (cacheRef : IO.Ref Cache) (pos : String.Pos) (mvarId : MVarId) (msg : String) : TacticM Unit := do
if tactic.dbg_cache.get ( getOptions) then
let {line, column} := ( getFileMap).toPosition pos
dbg_trace "{msg}, line: {line}, column: {column}, contains entry: {(← cacheRef.get).pre.find? { mvarId, pos } |>.isSome}"
private def findCache? (cacheRef : IO.Ref Cache) (mvarId : MVarId) (stx : Syntax) (pos : String.Pos) : TacticM (Option Snapshot) := do
let some s := ( cacheRef.get).pre.find? { mvarId, pos } | do dbg_cache "cache key not found"; return none
let mvarDecl mvarId.getDecl
let some mvarDeclOld := s.meta.mctx.findDecl? mvarId | return none
if equivMVarDecl mvarDecl mvarDeclOld then
if stx == s.stx then
return some s
else
dbg_cache "syntax is different"
return none
else
dbg_cache "cached state is not compatible"
return none
@[builtin_tactic checkpoint] def evalCheckpoint : Tactic := fun stx =>
focus do
let mvarId getMainGoal
let some cacheRef := ( readThe Term.Context).tacticCache? | evalTactic stx[1]
let some pos := stx.getPos? | evalTactic stx[1]
match ( findCache? cacheRef mvarId stx[1] pos) with
| some s =>
cacheRef.modify fun { pre, post } => { pre, post := post.insert { mvarId, pos } s }
set s.core
set s.meta
set s.term
set s.tactic
dbg_cache' cacheRef pos mvarId "cache hit"
| none =>
evalTactic stx[1]
let s := {
stx := stx[1]
core := ( getThe Core.State)
meta := ( getThe Meta.State)
term := ( getThe Term.State)
tactic := ( get)
}
dbg_cache' cacheRef pos mvarId "cache miss"
cacheRef.modify fun { pre, post } => { pre, post := post.insert { mvarId, pos } s }
end Lean.Elab.Tactic

View File

@@ -306,8 +306,6 @@ structure Context where
ignoreTCFailures : Bool := false
/-- `true` when elaborating patterns. It affects how we elaborate named holes. -/
inPattern : Bool := false
/-- Cache for the `save` tactic. It is only `some` in the LSP server. -/
tacticCache? : Option (IO.Ref Tactic.Cache) := none
/--
Snapshot for incremental processing of current tactic, if any.

View File

@@ -542,7 +542,6 @@ where
elabSnap := default
finishedSnap := .pure { diagnostics := .empty, cmdState }
reportSnap := default
tacticCache := ( IO.mkRef {})
nextCmdSnap? := none
}
return
@@ -557,7 +556,6 @@ where
-- progress
let initRange? := getNiceCommandStartPos? stx |>.map fun pos => pos, pos
let finishedSnap := { range? := initRange?, task := finishedPromise.result! }
let tacticCache old?.map (·.tacticCache) |>.getDM (IO.mkRef {})
let minimalSnapshots := internal.cmdlineSnapshots.get cmdState.scopes.head!.opts
let next? if Parser.isTerminalCommand stx then pure none
@@ -571,14 +569,14 @@ where
else
(stx, parserState)
prom.resolve {
diagnostics, finishedSnap, tacticCache, nextCmdSnap?
diagnostics, finishedSnap, nextCmdSnap?
stx := stx', parserState := parserState'
elabSnap := { range? := stx.getRange?, task := elabPromise.result! }
reportSnap := { range? := initRange?, task := reportPromise.result! }
}
let cmdState doElab stx cmdState beginPos
{ old? := old?.map fun old => old.stx, old.elabSnap, new := elabPromise }
finishedPromise tacticCache ctx
finishedPromise ctx
let traceTask
if ( isTracingEnabledForCore `Elab.snapshotTree cmdState.scopes.head!.opts) then
-- We want to trace all of `CommandParsedSnapshot` but `traceTask` is part of it, so let's
@@ -611,24 +609,15 @@ where
parseCmd none parserState cmdState next (sync := false) ctx
doElab (stx : Syntax) (cmdState : Command.State) (beginPos : String.Pos)
(snap : SnapshotBundle DynamicSnapshot) (finishedPromise : IO.Promise CommandFinishedSnapshot)
(tacticCache : IO.Ref Tactic.Cache) :
(snap : SnapshotBundle DynamicSnapshot) (finishedPromise : IO.Promise CommandFinishedSnapshot) :
LeanProcessingM Command.State := do
let ctx read
let scope := cmdState.scopes.head!
-- reset per-command state
let cmdStateRef IO.mkRef { cmdState with
messages := .empty, traceState := {}, snapshotTasks := #[] }
/-
The same snapshot may be executed by different tasks. So, to make sure `elabCommandTopLevel`
has exclusive access to the cache, we create a fresh reference here. Before this change, the
following `tacticCache.modify` would reset the tactic post cache while another snapshot was
still using it.
-/
let tacticCacheNew IO.mkRef ( tacticCache.get)
let cmdCtx : Elab.Command.Context := { ctx with
cmdPos := beginPos
tacticCache? := some tacticCacheNew
snap? := if internal.cmdlineSnapshots.get scope.opts then none else snap
cancelTk? := some ctx.newCancelTk
}
@@ -638,8 +627,6 @@ where
withLoggingExceptions
(getResetInfoTrees *> Elab.Command.elabCommandTopLevel stx)
cmdCtx cmdStateRef
let postNew := ( tacticCacheNew.get).post
tacticCache.modify fun _ => { pre := postNew, post := {} }
let cmdState cmdStateRef.get
let mut messages := cmdState.messages
if !output.isEmpty then

View File

@@ -48,8 +48,6 @@ structure CommandParsedSnapshot extends Snapshot where
finishedSnap : SnapshotTask CommandFinishedSnapshot
/-- Additional, untyped snapshots used for reporting, not reuse. -/
reportSnap : SnapshotTask SnapshotTree
/-- Cache for `save`; to be replaced with incrementality. -/
tacticCache : IO.Ref Tactic.Cache
/-- Next command, unless this is a terminal command. -/
nextCmdSnap? : Option (SnapshotTask CommandParsedSnapshot)
deriving Nonempty

View File

@@ -21,9 +21,6 @@ namespace Lean.Server.Snapshots
open Elab
/- For `Inhabited Snapshot` -/
builtin_initialize dummyTacticCache : IO.Ref Tactic.Cache IO.mkRef {}
/-- What Lean knows about the world after the header and each command. -/
structure Snapshot where
stx : Syntax
@@ -56,7 +53,6 @@ def runCommandElabM (snap : Snapshot) (meta : DocumentMeta) (c : CommandElabM α
cmdPos := snap.stx.getPos? |>.getD 0,
fileName := meta.uri,
fileMap := meta.text,
tacticCache? := none
snap? := none
cancelTk? := none
}

View File

@@ -299,7 +299,7 @@ def mkCtx [BEq tp] [Hashable tp]
let fileMap := coreCtx.fileMap
let env := coreState.env
let maxRecDepth := coreCtx.maxRecDepth
let cmdCtx : Command.Context := { fileName, fileMap, tacticCache? := none }
let cmdCtx : Command.Context := { fileName, fileMap }
let cmdState : Command.State := { env, maxRecDepth }
let addVars (p : LocalContext × LocalInstances × Array (Array val))
(q : tp × CoreM Command) :