feat: finish? tactic for grind interactive mode (#10837)
This PR implements the `finish?` tactic for the `grind` interactive
mode. When it successfully closes the goal, it produces a code action
that allows the user to close the goal using explicit grind tactic
steps, i.e., without any search. It also makes explicit which solvers
have been used.
This is just the first version, we will add many "bells and whistles"
later. For example, `instantiate` steps will clearly show which theorems
have been instantiated.
Example:
```lean
/--
info: Try this:
[apply] ⏎
cases #b0f4
next => cases #50fc
next => cases #50fc <;> lia
-/
#guard_msgs in
example (p : Nat → Prop) (x y z w : Int) :
(x = 1 ∨ x = 2) →
(w = 1 ∨ w = 4) →
(y = 1 ∨ (∃ x : Nat, y = 3 - x ∧ p x)) →
(z = 1 ∨ z = 0) → x + y ≤ 6 := by
grind => finish?
```
The anchors in the generated script are based on stable hash codes.
Moreover, users can hover over them to see the exact term used in the
case split. `grind?` will also be implemented using the new framework.
This commit is contained in:
parent
09f22203f8
commit
206eb73cd9
6 changed files with 134 additions and 2 deletions
|
|
@ -100,6 +100,9 @@ syntax (name := done) "done" : grind
|
|||
/-- `finish` tries to close the current goal using `grind`'s default strategy -/
|
||||
syntax (name := finish) "finish" : grind
|
||||
|
||||
/-- `finish?` tries to close the current goal using `grind`'s default strategy and suggests a tactic script. -/
|
||||
syntax (name := finishTrace) "finish?" : grind
|
||||
|
||||
syntax (name := «have») "have" letDecl : grind
|
||||
|
||||
/-- Executes the given tactic block to close the current goal. -/
|
||||
|
|
|
|||
|
|
@ -10,3 +10,4 @@ public import Lean.Elab.Tactic.Grind.Basic
|
|||
public import Lean.Elab.Tactic.Grind.BuiltinTactic
|
||||
public import Lean.Elab.Tactic.Grind.ShowState
|
||||
public import Lean.Elab.Tactic.Grind.Have
|
||||
public import Lean.Elab.Tactic.Grind.Trace
|
||||
|
|
|
|||
42
src/Lean/Elab/Tactic/Grind/Trace.lean
Normal file
42
src/Lean/Elab/Tactic/Grind/Trace.lean
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/-
|
||||
Copyright (c) 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Leonardo de Moura
|
||||
-/
|
||||
module
|
||||
prelude
|
||||
public import Lean.Elab.Tactic.Grind.Basic
|
||||
import Init.Grind.Interactive
|
||||
import Lean.Meta.Tactic.TryThis
|
||||
import Lean.Meta.Tactic.Grind.Action
|
||||
import Lean.Meta.Tactic.Grind.EMatchAction
|
||||
import Lean.Meta.Tactic.Grind.Split
|
||||
namespace Lean.Elab.Tactic.Grind
|
||||
open Meta
|
||||
open Meta.Grind
|
||||
|
||||
def withTracing (x : GrindTacticM α) : GrindTacticM α := do
|
||||
withReader (fun ctx => { ctx with ctx.config.trace := true }) x
|
||||
|
||||
def mkFinish (maxIterations : Nat) : IO Action := do
|
||||
let solvers ← Solvers.mkAction
|
||||
return Action.checkTactic >> (Action.done <|> solvers <|> Action.instantiate <|> Action.splitNext).loop maxIterations
|
||||
|
||||
def maxIterations := 1000 -- **TODO**: Add option
|
||||
|
||||
@[builtin_grind_tactic finishTrace] def evalFinishTrace : GrindTactic := fun stx => do
|
||||
let a ← mkFinish maxIterations
|
||||
let goal ← getMainGoal
|
||||
withTracing do
|
||||
match (← liftGrindM <| a.run goal) with
|
||||
| .closed seq =>
|
||||
replaceMainGoal []
|
||||
let seq := Action.mkGrindSeq seq
|
||||
Tactic.TryThis.addSuggestion stx { suggestion := .tsyntax seq }
|
||||
| .stuck gs =>
|
||||
let goal :: _ := gs | throwError "`finish?` failed, but resulting goal is not available"
|
||||
let params := (← read).params
|
||||
let result ← liftGrindM do mkResult params (some goal)
|
||||
throwError "`finish?` failed\n{← result.toMessageData}"
|
||||
|
||||
end Lean.Elab.Tactic.Grind
|
||||
|
|
@ -43,9 +43,8 @@ public import Lean.Meta.Tactic.Grind.PropagateInj
|
|||
public import Lean.Meta.Tactic.Grind.Order
|
||||
public import Lean.Meta.Tactic.Grind.Anchor
|
||||
public import Lean.Meta.Tactic.Grind.Action
|
||||
|
||||
public import Lean.Meta.Tactic.Grind.EMatchAction
|
||||
public section
|
||||
|
||||
namespace Lean
|
||||
|
||||
/-! Trace options for `grind` users -/
|
||||
|
|
|
|||
25
src/Lean/Meta/Tactic/Grind/EMatchAction.lean
Normal file
25
src/Lean/Meta/Tactic/Grind/EMatchAction.lean
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/-
|
||||
Copyright (c) 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Leonardo de Moura
|
||||
-/
|
||||
module
|
||||
prelude
|
||||
public import Lean.Meta.Tactic.Grind.Action
|
||||
public import Lean.Meta.Tactic.Grind.Intro
|
||||
import Lean.Meta.Tactic.Grind.EMatch
|
||||
public section
|
||||
namespace Lean.Meta.Grind.Action
|
||||
|
||||
def instantiate' : Action := fun goal kna kp => do
|
||||
let (progress, goal') ← GoalM.run goal ematch
|
||||
-- **TODO**: filter relevant instances
|
||||
if progress then
|
||||
concatTactic (← kp goal') `(grind| instantiate)
|
||||
else
|
||||
kna goal
|
||||
|
||||
def instantiate : Action :=
|
||||
instantiate' >> assertAll
|
||||
|
||||
end Lean.Meta.Grind.Action
|
||||
62
tests/lean/run/grind_finish_trace.lean
Normal file
62
tests/lean/run/grind_finish_trace.lean
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
open Lean Grind
|
||||
|
||||
/--
|
||||
info: Try this:
|
||||
[apply] cases #c4b6 <;> ring <;> cases #4c68 <;> ring
|
||||
-/
|
||||
#guard_msgs in
|
||||
example {α : Type} [CommRing α] (a b c d e : α) :
|
||||
(a * a = b * c ∨ a^2 = c * b) →
|
||||
(a^2 = c * b ∨ e^2 = d * c) →
|
||||
(b^2 = d*c ∨ b^2 = c*d) →
|
||||
a*b*(b*a) = c^2*b*d := by
|
||||
grind => finish?
|
||||
|
||||
|
||||
/--
|
||||
info: Try this:
|
||||
[apply] ⏎
|
||||
cases #b0f4
|
||||
next => cases #50fc
|
||||
next => cases #50fc <;> lia
|
||||
-/
|
||||
#guard_msgs in
|
||||
example (p : Nat → Prop) (x y z w : Int) :
|
||||
(x = 1 ∨ x = 2) →
|
||||
(w = 1 ∨ w = 4) →
|
||||
(y = 1 ∨ (∃ x : Nat, y = 3 - x ∧ p x)) →
|
||||
(z = 1 ∨ z = 0) → x + y ≤ 6 := by
|
||||
grind => finish?
|
||||
|
||||
/--
|
||||
info: Try this:
|
||||
[apply] ⏎
|
||||
ac
|
||||
cases #5c4b <;> cases #896f <;> ac
|
||||
-/
|
||||
#guard_msgs in
|
||||
example {α : Type} (op : α → α → α) [Std.Associative op] [Std.Commutative op] (a b c d e : α) :
|
||||
(op a a = op b c ∨ op a a = op c b) →
|
||||
(op a a = op c b ∨ op e e = op d c) →
|
||||
(op b b = op d c ∨ op b b = op c d) →
|
||||
op (op a b) (op b a) = op (op c c) (op b d) := by
|
||||
grind => finish?
|
||||
|
||||
/--
|
||||
info: Try this:
|
||||
[apply] ⏎
|
||||
instantiate
|
||||
instantiate
|
||||
-/
|
||||
#guard_msgs in
|
||||
example (as bs cs : Array α) (v₁ v₂ : α)
|
||||
(i₁ i₂ j : Nat)
|
||||
(h₁ : i₁ < as.size)
|
||||
(h₂ : bs = as.set i₁ v₁)
|
||||
(h₃ : i₂ < bs.size)
|
||||
(h₃ : cs = bs.set i₂ v₂)
|
||||
(h₄ : i₁ ≠ j ∧ i₂ ≠ j)
|
||||
(h₅ : j < cs.size)
|
||||
(h₆ : j < as.size)
|
||||
: cs[j] = as[j] := by
|
||||
grind => finish?
|
||||
Loading…
Add table
Reference in a new issue