Skip to content

Commit 16d0005

Browse files
authored
feat: finish? produces finish only suggestion (#11034)
This PR adds a new suggestion to `finish?`. It now generates the `grind` tactic script as before, and a `finish only` tactic. Example: ```lean /-- info: Try these: [apply] ⏎ instantiate only [findIdx, insert, = mem_indices_of_mem] instantiate only [= getElem?_neg, = getElem?_pos] cases #1bba · instantiate only [findIdx] · instantiate only instantiate only [= HashMap.mem_insert, = HashMap.getElem_insert] [apply] finish only [findIdx, insert, = mem_indices_of_mem, = getElem?_neg, = getElem?_pos, = HashMap.mem_insert, = HashMap.getElem_insert, #1bba] -/ example (m : IndexMap α β) (a : α) (b : β) : (m.insert a b).findIdx a = if h : a ∈ m then m.findIdx a else m.size := by grind => finish? ```
1 parent 377f149 commit 16d0005

File tree

8 files changed

+158
-30
lines changed

8 files changed

+158
-30
lines changed

src/Init/Grind/Interactive.lean

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,12 +127,12 @@ syntax (name := casesTrace) "cases?" grindFilter : grind
127127
syntax (name := done) "done" : grind
128128

129129
/-- `finish` tries to close the current goal using `grind`'s default strategy -/
130-
syntax (name := finish) "finish" ppSpace configItem*
131-
(&" only")? (" [" withoutPosition(grindParam,*) "]")? : grind
130+
syntax (name := finish) "finish" (ppSpace configItem)*
131+
(ppSpace &"only")? (" [" withoutPosition(grindParam,*) "]")? : grind
132132

133133
/-- `finish?` tries to close the current goal using `grind`'s default strategy and suggests a tactic script. -/
134-
syntax (name := finishTrace) "finish?" ppSpace configItem*
135-
(&" only")? (" [" withoutPosition(grindParam,*) "]")? : grind
134+
syntax (name := finishTrace) "finish?" (ppSpace configItem)*
135+
(ppSpace &"only")? (" [" withoutPosition(grindParam,*) "]")? : grind
136136

137137
/--
138138
The `have` tactic is for adding opaque definitions and hypotheses to the local context of the main goal.

src/Lean/Elab/Tactic/Grind/Trace.lean

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@ import Lean.Meta.Tactic.TryThis
1313
import Lean.Meta.Tactic.Grind.Action
1414
import Lean.Meta.Tactic.Grind.EMatchAction
1515
import Lean.Meta.Tactic.Grind.Split
16+
import Lean.Meta.Tactic.Grind.CollectParams
1617
namespace Lean.Elab.Tactic.Grind
1718
open Meta
1819
open Meta.Grind
1920

2021
def withTracing (x : GrindTacticM α) : GrindTacticM α := do
2122
withReader (fun ctx => { ctx with ctx.config.trace := true }) x
2223

23-
def mkFinish (maxIterations : Nat) : IO Action := do
24+
def mkFinishAction (maxIterations : Nat) : IO Action := do
2425
let solvers ← Solvers.mkAction
2526
let step : Action := Action.done <|> solvers <|> Action.instantiate <|> Action.splitNext <|> Action.mbtc
2627
return Action.checkTactic (warnOnly := true) >> step.loop maxIterations
@@ -32,18 +33,30 @@ def maxIterations := 1000 -- **TODO**: Add option
3233
withConfigItems configItems do
3334
let params := params?.getD {}
3435
withParams (← read).params params only.isSome do
35-
let a ← mkFinish maxIterations
36+
let a ← mkFinishAction maxIterations
3637
let goal ← getMainGoal
38+
let params := (← read).params
3739
withTracing do
38-
match (← liftGrindM <| a.run goal) with
39-
| .closed seq =>
40+
let solved ← liftGrindM do
41+
let saved ← saveState
42+
match (← a.run goal) with
43+
| .closed seq =>
44+
let finishTac ← mkFinishTactic seq
45+
let seq := Action.mkGrindSeq seq
46+
if (← Action.checkSeqAt saved goal [finishTac]) then
47+
Tactic.TryThis.addSuggestions stx #[
48+
{ suggestion := .tsyntax seq },
49+
{ suggestion := .tsyntax finishTac }
50+
]
51+
else
52+
Tactic.TryThis.addSuggestion stx { suggestion := .tsyntax seq }
53+
return true
54+
| .stuck gs =>
55+
let goal :: _ := gs | throwError "`finish?` failed, but resulting goal is not available"
56+
let result ← mkResult params (some goal)
57+
throwError "`finish?` failed\n{← result.toMessageData}"
58+
return false
59+
if solved then
4060
replaceMainGoal []
41-
let seq := Action.mkGrindSeq seq
42-
Tactic.TryThis.addSuggestion stx { suggestion := .tsyntax seq }
43-
| .stuck gs =>
44-
let goal :: _ := gs | throwError "`finish?` failed, but resulting goal is not available"
45-
let params := (← read).params
46-
let result ← liftGrindM do mkResult params (some goal)
47-
throwError "`finish?` failed\n{← result.toMessageData}"
4861

4962
end Lean.Elab.Tactic.Grind

src/Lean/Meta/Tactic/Grind.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public import Lean.Meta.Tactic.Grind.Action
4545
public import Lean.Meta.Tactic.Grind.EMatchTheoremParam
4646
public import Lean.Meta.Tactic.Grind.EMatchAction
4747
public import Lean.Meta.Tactic.Grind.Filter
48+
public import Lean.Meta.Tactic.Grind.CollectParams
4849
public section
4950
namespace Lean
5051

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/-
2+
Copyright (c) 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Leonardo de Moura
5+
-/
6+
module
7+
prelude
8+
public import Lean.Meta.Tactic.Grind.Types
9+
namespace Lean.Meta.Grind
10+
/-!
11+
Given an auto-generated `grind` tactic script, collect params for
12+
single shot `finish` or top-level `grind` tactic.
13+
-/
14+
abbrev TParam := TSyntax ``Parser.Tactic.grindParam
15+
abbrev TAnchor := TSyntax ``Parser.Tactic.anchor
16+
17+
namespace Collector
18+
19+
structure State where
20+
params : Array TParam := #[]
21+
anchors : Array TAnchor := #[]
22+
23+
abbrev Collect := StateRefT State CoreM
24+
25+
def pushParam (p : TParam) : Collect Unit := do
26+
if (← get).params.contains p then
27+
return ()
28+
modify fun s => { s with params := s.params.push p }
29+
30+
def pushAnchor (a : TAnchor) : Collect Unit := do
31+
if (← get).anchors.contains a then
32+
return ()
33+
modify fun s => { s with anchors := s.anchors.push a }
34+
35+
def collectInstantiateParams (params : Syntax.TSepArray `Lean.Parser.Tactic.Grind.thm ",") : Collect Unit := do
36+
for p in params.getElems do
37+
match p with
38+
| `(Lean.Parser.Tactic.Grind.thm| $lemma:grindLemma) =>
39+
let p ← `(Parser.Tactic.grindParam| $lemma:grindLemma)
40+
pushParam p
41+
| `(Lean.Parser.Tactic.Grind.thm| $lemma:grindLemmaMin) =>
42+
let p ← `(Parser.Tactic.grindParam| $lemma:grindLemmaMin)
43+
pushParam p
44+
| `(Lean.Parser.Tactic.Grind.thm| $a:anchor) =>
45+
pushAnchor a
46+
| _ => pure ()
47+
48+
partial def collect (tac : TGrind) : Collect Unit := do
49+
match tac with
50+
| `(grind| next => $$seq;*)
51+
| `(grind| · $$seq;*) =>
52+
for step in seq.getElems do
53+
match step with
54+
| `(Parser.Tactic.Grind.grindStep| $tac:grind $[| $_:grind_filter]?) => collect tac
55+
| _ => return ()
56+
| `(grind| $tac₁:grind <;> $tac₂:grind) => collect tac₁; collect tac₂
57+
| `(grind| cases $a:anchor) => pushAnchor a
58+
| `(grind| use [$params,*]) =>
59+
collectInstantiateParams params
60+
| `(grind| instantiate $[only]? $[approx]? $[[$params?,*]]?) =>
61+
let some params := params? | return ()
62+
collectInstantiateParams params
63+
| _ => return ()
64+
65+
def main (seq : List TGrind) : Collect Unit :=
66+
seq.forM collect
67+
68+
end Collector
69+
70+
def collectParams (seq : List TGrind) : CoreM (Array TParam) := do
71+
let (_, s) ← Collector.main seq |>.run {}
72+
let anchors ← s.anchors.mapM fun anchor =>
73+
`(Parser.Tactic.grindParam| $anchor:anchor)
74+
return s.params ++ anchors
75+
76+
/--
77+
Given a `grind` tactic sequence, extracts parameters and builds an terminal `finish only` tactic.
78+
-/
79+
public def mkFinishTactic (seq : List TGrind) : CoreM TGrind := do
80+
let params ← collectParams seq
81+
`(grind| finish only [$params,*])
82+
83+
end Lean.Meta.Grind

tests/lean/run/grind_10885.lean

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
example {a b : Nat} (ha : 1 ≤ a) : (a - 1 + 1) * b = a * b := by grind
22

33
/--
4-
info: Try this:
4+
info: Try these:
55
[apply]
66
mbtc
77
cases #9501
8+
[apply] finish only [#9501]
89
-/
910
#guard_msgs in
1011
example {a b : Nat} (ha : 1 ≤ a) : (a - 1 + 1) * b = a * b := by

tests/lean/run/grind_finish_trace.lean

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
open Lean Grind
22

33
/--
4-
info: Try this:
4+
info: Try these:
55
[apply] cases #c4b6 <;> cases #4c68 <;> ring
6+
[apply] finish only [#c4b6, #4c68]
67
-/
78
#guard_msgs in
89
example {α : Type} [CommRing α] (a b c d e : α) :
@@ -14,11 +15,12 @@ example {α : Type} [CommRing α] (a b c d e : α) :
1415

1516

1617
/--
17-
info: Try this:
18+
info: Try these:
1819
[apply]
1920
cases #b0f4
2021
· cases #50fc
2122
· cases #50fc <;> lia
23+
[apply] finish only [#b0f4, #50fc]
2224
-/
2325
#guard_msgs in
2426
example (p : Nat → Prop) (x y z w : Int) :
@@ -29,8 +31,9 @@ example (p : Nat → Prop) (x y z w : Int) :
2931
grind => finish?
3032

3133
/--
32-
info: Try this:
34+
info: Try these:
3335
[apply] cases #5c4b <;> cases #896f <;> ac
36+
[apply] finish only [#5c4b, #896f]
3437
-/
3538
#guard_msgs in
3639
example {α : Type} (op : α → α → α) [Std.Associative op] [Std.Commutative op] (a b c d e : α) :
@@ -41,10 +44,11 @@ example {α : Type} (op : α → α → α) [Std.Associative op] [Std.Commutativ
4144
grind => finish?
4245

4346
/--
44-
info: Try this:
47+
info: Try these:
4548
[apply]
4649
instantiate only [= Array.getElem_set]
4750
instantiate only [= Array.getElem_set]
51+
[apply] finish only [= Array.getElem_set]
4852
-/
4953
#guard_msgs in
5054
example (as bs cs : Array α) (v₁ v₂ : α)
@@ -93,10 +97,11 @@ example (as : Array α) (lo hi i j : Nat) (h₁ : lo ≤ i) (_ : i < j) (_ : j
9397
grind => finish?
9498

9599
/--
96-
info: Try this:
100+
info: Try these:
97101
[apply]
98102
instantiate only [= getMsbD_setWidth']
99103
cases #aa9d
104+
[apply] finish only [= getMsbD_setWidth', #aa9d]
100105
-/
101106
#guard_msgs in
102107
open BitVec in
@@ -112,43 +117,52 @@ example (ge : m ≥ n) (x : BitVec n) (i : Nat) :
112117
cases #aa9d
113118

114119
/--
115-
info: Try this:
120+
info: Try these:
116121
[apply] cases #9942 <;>
117122
instantiate only [= BitVec.getElem_and] <;> instantiate only [= BitVec.getElem_or] <;> cases #cfbc
123+
[apply] finish only [= BitVec.getElem_and, = BitVec.getElem_or, #9942, #cfbc]
118124
-/
119125
#guard_msgs in
120126
example (x y : BitVec 64) : (x ||| y) &&& x = x := by
121127
grind => finish?
122128

129+
set_option trace.Meta.debug true in
130+
example (x y : BitVec 64) : (x ||| y) &&& x = x := by
131+
grind => finish?
132+
133+
123134
macro_rules | `(tactic| get_elem_tactic_extensible) => `(tactic| grind)
124135

125136
/--
126-
info: Try this:
137+
info: Try these:
127138
[apply]
128139
instantiate only [= Array.getElem_set]
129140
ring
141+
[apply] finish only [= Array.getElem_set]
130142
-/
131143
#guard_msgs in
132144
example (a : Array (BitVec 64)) (i : Nat) (v : BitVec 64)
133145
: (_ : i < a.size) → (_ : i + 1 < a.size) → (a.set i v)[i+1] + a[i+1] = 2*a[i+1] := by
134146
grind => finish?
135147

136148
/--
137-
info: Try this:
149+
info: Try these:
138150
[apply]
139151
mbtc
140152
cases #a6c8
153+
[apply] finish only [#a6c8]
141154
-/
142155
#guard_msgs in
143156
example (f : Nat → Nat) (x : Nat)
144157
: x ≠ 0 → x ≤ 1 → f x = 2 → f 1 = 2 := by
145158
grind => finish?
146159

147160
/--
148-
info: Try this:
161+
info: Try these:
149162
[apply]
150163
mbtc
151164
cases #beb4
165+
[apply] finish only [#beb4]
152166
-/
153167
#guard_msgs in
154168
example (f : Int → Int → Int) (x y : Int)

tests/lean/run/grind_indexmap_trace.lean

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,9 @@ If the key is not present, the map is unchanged.
131131
attribute [local grind] getIdx findIdx insert
132132

133133
/--
134-
info: Try this:
134+
info: Try these:
135135
[apply] instantiate only [getIdx, findIdx, = getElem_def]
136+
[apply] finish only [getIdx, findIdx, = getElem_def]
136137
-/
137138
#guard_msgs in
138139
example (m : IndexMap α β) (a : α) (h : a ∈ m) :
@@ -144,7 +145,7 @@ example (m : IndexMap α β) (a : α) (h : a ∈ m) :
144145
grind => instantiate only [getIdx, findIdx, = getElem_def]
145146

146147
/--
147-
info: Try this:
148+
info: Try these:
148149
[apply]
149150
instantiate only [= mem_indices_of_mem, insert]
150151
instantiate only [=_ HashMap.contains_iff_mem, = getElem?_neg, = getElem?_pos]
@@ -162,14 +163,16 @@ info: Try this:
162163
· instantiate only
163164
· instantiate only
164165
instantiate only [= HashMap.contains_insert]
166+
[apply] finish only [= mem_indices_of_mem, insert, =_ HashMap.contains_iff_mem, = getElem?_neg, = getElem?_pos,
167+
= HashMap.contains_insert, #4ed2, #ffdf, #95a0, #2688]
165168
-/
166169
#guard_msgs in
167170
example (m : IndexMap α β) (a a' : α) (b : β) :
168171
a' ∈ m.insert a b ↔ a' = a ∨ a' ∈ m := by
169172
grind => finish?
170173

171174
/--
172-
info: Try this:
175+
info: Try these:
173176
[apply]
174177
instantiate only [= mem_indices_of_mem, insert]
175178
instantiate only [=_ HashMap.contains_iff_mem, = getElem?_neg, = getElem?_pos]
@@ -187,6 +190,8 @@ info: Try this:
187190
· instantiate only
188191
· instantiate only
189192
instantiate only [= HashMap.contains_insert]
193+
[apply] finish only [= mem_indices_of_mem, insert, =_ HashMap.contains_iff_mem, = getElem?_neg, = getElem?_pos,
194+
= HashMap.contains_insert, #4ed2, #ffdf, #95a0, #2688]
190195
-/
191196
#guard_msgs in
192197
example (m : IndexMap α β) (a a' : α) (b : β) :
@@ -221,7 +226,7 @@ example (m : IndexMap α β) (a a' : α) (b : β) :
221226
instantiate only [= HashMap.contains_insert]
222227

223228
/--
224-
info: Try this:
229+
info: Try these:
225230
[apply]
226231
instantiate only [= mem_indices_of_mem, insert, = getElem_def]
227232
instantiate only [= getElem?_neg, = getElem?_pos]
@@ -240,6 +245,9 @@ info: Try this:
240245
instantiate only [WF']
241246
· instantiate only
242247
instantiate only [= HashMap.mem_insert, = HashMap.getElem_insert, = Array.getElem_push]
248+
[apply] finish only [= mem_indices_of_mem, insert, = getElem_def, = getElem?_neg, = getElem?_pos, = Array.getElem_set,
249+
size, = HashMap.mem_insert, = HashMap.getElem_insert, = Array.getElem_push, usr getElem_indices_lt, =_ WF, WF',
250+
#f590, #ffdf]
243251
-/
244252
#guard_msgs in
245253
example (m : IndexMap α β) (a a' : α) (b : β) (h : a' ∈ m.insert a b) :
@@ -275,20 +283,26 @@ example (m : IndexMap α β) (a a' : α) (b : β) (h : a' ∈ m.insert a b) :
275283
instantiate only [= HashMap.mem_insert, = HashMap.getElem_insert, = Array.getElem_push]
276284

277285
/--
278-
info: Try this:
286+
info: Try these:
279287
[apply]
280288
instantiate only [findIdx, insert, = mem_indices_of_mem]
281289
instantiate only [= getElem?_neg, = getElem?_pos]
282290
cases #1bba
283291
· instantiate only [findIdx]
284292
· instantiate only
285293
instantiate only [= HashMap.mem_insert, = HashMap.getElem_insert]
294+
[apply] finish only [findIdx, insert, = mem_indices_of_mem, = getElem?_neg, = getElem?_pos, = HashMap.mem_insert,
295+
= HashMap.getElem_insert, #1bba]
286296
-/
287297
#guard_msgs in
288298
example (m : IndexMap α β) (a : α) (b : β) :
289299
(m.insert a b).findIdx a = if h : a ∈ m then m.findIdx a else m.size := by
290300
grind => finish?
291301

302+
example (m : IndexMap α β) (a : α) (b : β) :
303+
(m.insert a b).findIdx a = if h : a ∈ m then m.findIdx a else m.size := by
304+
grind => finish?
305+
292306
example (m : IndexMap α β) (a : α) (b : β) :
293307
(m.insert a b).findIdx a = if h : a ∈ m then m.findIdx a else m.size := by
294308
grind =>

0 commit comments

Comments
 (0)