Skip to content

Commit 226a90f

Browse files
kim-emclaude
andauthored
feat: exact? +grind and exact? +try? discharger options (#11469)
This PR adds `+grind` and `+try?` options to `exact?` and `apply?` tactics. ## `+grind` option When `+grind` is enabled, `grind` is used as a fallback discharger for subgoals that `solve_by_elim` cannot close. The proof is wrapped in `Grind.Marker` so suggestions display `(by grind)` instead of the complex grind proof term. Example: ```lean axiom foo (x : Nat) : x < 37 → 5 < x → x.log2 < 6 /-- info: Try this: [apply] exact foo x (by grind) (by grind) -/ #guard_msgs in example (x : Nat) (h₁ : x < 30) (h₂ : 8 < x) : x.log2 < 6 := by exact? +grind ``` ## `+try?` option When `+try?` is enabled, `try?` is used as a fallback discharger for subgoals. This is useful when subgoals require induction or other strategies that `try?` can find but `solve_by_elim` and `grind` cannot. Example: ```lean inductive MyList (α : Type _) where | nil : MyList α | cons : α → MyList α → MyList α axiom MyListProp : MyList α → Prop @[grind .] axiom mylist_nil : MyListProp (MyList.nil : MyList α) @[grind .] axiom mylist_cons : ∀ (x : α) (xs : MyList α), MyListProp xs → MyListProp (MyList.cons x xs) axiom qux (xs : MyList α) (p : MyListProp xs) : MyListProp2 xs /-- info: Try this: [apply] exact qux xs (by try?) -/ example (xs : MyList α) : MyListProp2 xs := by exact? +try? ``` 🤖 Prepared with Claude Code --------- Co-authored-by: Claude <[email protected]>
1 parent 519ccf5 commit 226a90f

File tree

8 files changed

+249
-35
lines changed

8 files changed

+249
-35
lines changed

src/Init/Grind/Util.lean

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,4 +122,15 @@ See comment at `alreadyNorm`
122122
theorem em (p : Prop) : alreadyNorm p ∨ alreadyNorm (¬ p) :=
123123
Classical.em p
124124

125+
/--
126+
Marker for grind-solved subproofs in `exact? +grind` suggestions.
127+
When `exact?` uses grind as a discharger, it wraps the proof in this marker
128+
so that the unexpander can replace it with `(by grind)` in the suggestion.
129+
-/
130+
@[inline] def Marker {α : Sort u} (a : α) : α := a
131+
132+
@[app_unexpander Marker]
133+
meta def markerUnexpander : PrettyPrinter.Unexpander := fun _ => do
134+
`(by grind)
135+
125136
end Lean.Grind

src/Init/Tactics.lean

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1690,24 +1690,41 @@ a lemma from the list until it gets stuck.
16901690
syntax (name := applyRules) "apply_rules" optConfig (&" only")? (args)? (using_)? : tactic
16911691
end SolveByElim
16921692

1693+
/--
1694+
Configuration for the `exact?` and `apply?` tactics.
1695+
-/
1696+
structure LibrarySearchConfig where
1697+
/-- If true, use `grind` as a discharger for subgoals that cannot be closed
1698+
by `solve_by_elim` alone. -/
1699+
grind : Bool := false
1700+
/-- If true, use `try?` as a discharger for subgoals that cannot be closed
1701+
by `solve_by_elim` alone. -/
1702+
try? : Bool := false
1703+
16931704
/--
16941705
Searches environment for definitions or theorems that can solve the goal using `exact`
16951706
with conditions resolved by `solve_by_elim`.
16961707
16971708
The optional `using` clause provides identifiers in the local context that must be
16981709
used by `exact?` when closing the goal. This is most useful if there are multiple
16991710
ways to resolve the goal, and one wants to guide which lemma is used.
1711+
1712+
Use `+grind` to enable `grind` as a fallback discharger for subgoals.
1713+
Use `+try?` to enable `try?` as a fallback discharger for subgoals.
17001714
-/
1701-
syntax (name := exact?) "exact?" (" using " (colGt ident),+)? : tactic
1715+
syntax (name := exact?) "exact?" optConfig (" using " (colGt ident),+)? : tactic
17021716

17031717
/--
17041718
Searches environment for definitions or theorems that can refine the goal using `apply`
17051719
with conditions resolved when possible with `solve_by_elim`.
17061720
17071721
The optional `using` clause provides identifiers in the local context that must be
17081722
used when closing the goal.
1723+
1724+
Use `+grind` to enable `grind` as a fallback discharger for subgoals.
1725+
Use `+try?` to enable `try?` as a fallback discharger for subgoals.
17091726
-/
1710-
syntax (name := apply?) "apply?" (" using " (colGt term),+)? : tactic
1727+
syntax (name := apply?) "apply?" optConfig (" using " (colGt term),+)? : tactic
17111728

17121729
/--
17131730
Syntax for excluding some names, e.g. `[-my_lemma, -my_theorem]`.

src/Init/Try.lean

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,18 @@ macro "∎" : tactic => `(tactic| try?)
8282
/-- `∎` (typed as `\qed`) is a macro that expands to `by try? (wrapWithBy := true)` in term mode.
8383
The `wrapWithBy` config option causes suggestions to be prefixed with `by`. -/
8484
macro "∎" : term => `(by try? (wrapWithBy := true))
85+
86+
namespace Lean.Try
87+
88+
/--
89+
Marker for try?-solved subproofs in `exact? +try?` suggestions.
90+
When `exact?` uses try? as a discharger, it wraps the proof in this marker
91+
so that the unexpander can replace it with `(by try?)` in the suggestion.
92+
-/
93+
@[inline] def Marker {α : Sort u} (a : α) : α := a
94+
95+
@[app_unexpander Marker]
96+
meta def markerUnexpander : PrettyPrinter.Unexpander := fun _ => do
97+
`(by try?)
98+
99+
end Lean.Try

src/Lean/Elab/Tactic/LibrarySearch.lean

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ prelude
99
public import Lean.Meta.Tactic.LibrarySearch
1010
public import Lean.Meta.Tactic.TryThis
1111
public import Lean.Elab.Tactic.ElabTerm
12+
public import Lean.Elab.Tactic.Config
1213

1314
public section
1415

@@ -17,22 +18,27 @@ namespace Lean.Elab.LibrarySearch
1718
open Lean Meta LibrarySearch
1819
open Elab Tactic Term TryThis
1920

21+
declare_config_elab elabLibrarySearchConfig Parser.Tactic.LibrarySearchConfig
22+
2023
/--
2124
Implementation of the `exact?` tactic.
2225
2326
* `ref` contains the input syntax and is used for locations in error reporting.
27+
* `config` contains configuration options (e.g., `grind` for using grind as a discharger).
2428
* `required` contains an optional list of terms that should be used in closing the goal.
2529
* `requireClose` indicates if the goal must be closed.
2630
It is `true` for `exact?` and `false` for `apply?`.
2731
-/
28-
def exact? (ref : Syntax) (required : Option (Array (TSyntax `term))) (requireClose : Bool) :
32+
def exact? (ref : Syntax) (config : Parser.Tactic.LibrarySearchConfig)
33+
(required : Option (Array (TSyntax `term))) (requireClose : Bool) :
2934
TacticM Unit := do
3035
let mvar ← getMainGoal
3136
let initialState ← saveState
3237
let (_, goal) ← (← getMainGoal).intros
3338
goal.withContext do
3439
let required := (← (required.getD #[]).mapM getFVarId).toList.map .fvar
35-
let tactic := fun goals => solveByElim required (exfalso := false) goals (maxDepth := 6)
40+
let tactic := fun goals =>
41+
solveByElim required (exfalso := false) goals (maxDepth := 6) (grind := config.grind) (try? := config.try?)
3642
let allowFailure := fun g => do
3743
let g ← g.withContext (instantiateMVars (.mvar g))
3844
return required.all fun e => e.occurs g
@@ -55,16 +61,18 @@ def exact? (ref : Syntax) (required : Option (Array (TSyntax `term))) (requireCl
5561

5662
@[builtin_tactic Lean.Parser.Tactic.exact?]
5763
def evalExact : Tactic := fun stx => do
58-
let `(tactic| exact? $[using $[$required],*]?) := stx
64+
let `(tactic| exact? $cfg:optConfig $[using $[$required],*]?) := stx
5965
| throwUnsupportedSyntax
60-
exact? (← getRef) required true
66+
let config ← elabLibrarySearchConfig cfg
67+
exact? (← getRef) config required true
6168

6269

6370
@[builtin_tactic Lean.Parser.Tactic.apply?]
6471
def evalApply : Tactic := fun stx => do
65-
let `(tactic| apply? $[using $[$required],*]?) := stx
72+
let `(tactic| apply? $cfg:optConfig $[using $[$required],*]?) := stx
6673
| throwUnsupportedSyntax
67-
exact? (← getRef) required false
74+
let config ← elabLibrarySearchConfig cfg
75+
exact? (← getRef) config required false
6876

6977
@[builtin_term_elab Lean.Parser.Syntax.exact?]
7078
def elabExact?Term : TermElab := fun stx expectedType? => do

src/Lean/Meta/Tactic/LibrarySearch.lean

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ module
88
prelude
99
public import Lean.Meta.LazyDiscrTree
1010
public import Lean.Meta.Tactic.SolveByElim
11+
public import Lean.Meta.Tactic.Grind.Main
1112
public import Lean.Util.Heartbeats
13+
import Init.Grind.Util
14+
import Init.Try
15+
import Lean.Elab.Tactic.Basic
1216

1317
public section
1418

@@ -36,16 +40,78 @@ builtin_initialize registerTraceClass `Tactic.librarySearch.lemmas
3640

3741
open SolveByElim
3842

43+
/--
44+
A discharger that tries `grind` on the goal.
45+
The proof is wrapped in `Grind.Marker` so that suggestions display `(by grind)`
46+
instead of the complex grind proof term.
47+
Returns `some []` if grind succeeds, `none` otherwise (leaving the goal as a subgoal).
48+
-/
49+
def grindDischarger (mvarId : MVarId) : MetaM (Option (List MVarId)) := do
50+
try
51+
-- Apply the marker wrapper, creating a subgoal for grind to solve
52+
let type ← mvarId.getType
53+
let u ← getLevel type
54+
let markerExpr := mkApp (.const ``Lean.Grind.Marker [u]) type
55+
let [subgoal] ← mvarId.apply markerExpr
56+
| return none
57+
-- Solve the subgoal with grind
58+
let params ← Grind.mkParams {}
59+
let result ← Grind.main subgoal params
60+
if result.hasFailed then
61+
return none
62+
else
63+
return some []
64+
catch _ =>
65+
return none
66+
67+
/--
68+
A discharger that tries `try?` on the goal.
69+
The proof is wrapped in `Try.Marker` so that suggestions display `(by try?)`
70+
instead of the complex proof term.
71+
Returns `some []` if try? succeeds, `none` otherwise (leaving the goal as a subgoal).
72+
-/
73+
def tryDischarger (mvarId : MVarId) : MetaM (Option (List MVarId)) := do
74+
try
75+
-- Apply the marker wrapper, creating a subgoal for try? to solve
76+
let type ← mvarId.getType
77+
let u ← getLevel type
78+
let markerExpr := mkApp (.const ``Lean.Try.Marker [u]) type
79+
let [subgoal] ← mvarId.apply markerExpr
80+
| return none
81+
-- Run try? via TacticM to solve the subgoal
82+
-- We suppress the "Try this" messages since we're using try? as a discharger
83+
let tacStx ← `(tactic| try?)
84+
let remainingGoals ← Elab.Term.TermElabM.run' <| Elab.Tactic.run subgoal do
85+
-- Suppress info messages from try?
86+
let initialLog ← Core.getMessageLog
87+
Elab.Tactic.evalTactic tacStx
88+
Core.setMessageLog initialLog
89+
if remainingGoals.isEmpty then
90+
return some []
91+
else
92+
return none
93+
catch _ =>
94+
return none
95+
3996
/--
4097
Wrapper for calling `Lean.Meta.SolveByElim.solveByElim with
4198
appropriate arguments for library search.
99+
100+
If `grind` is true, `grind` will be used as a fallback discharger for subgoals
101+
that cannot be closed by other means.
102+
If `try?` is true, `try?` will be used as a fallback discharger (via grind internally).
42103
-/
43-
def solveByElim (required : List Expr) (exfalso : Bool) (goals : List MVarId) (maxDepth : Nat) := do
104+
def solveByElim (required : List Expr) (exfalso : Bool) (goals : List MVarId)
105+
(maxDepth : Nat) (grind : Bool := false) (try? : Bool := false) := do
44106
let cfg : SolveByElimConfig :=
45107
{ maxDepth, exfalso := exfalso, symm := true, commitIndependentGoals := true,
46108
transparency := ← getTransparency,
47109
-- `constructor` has been observed to significantly slow down `exact?` in Mathlib.
48110
constructor := false }
111+
-- Add grind or try? as a fallback discharger (tried after intro/constructor fail)
112+
let cfg := if try? then cfg.withDischarge tryDischarger
113+
else if grind then cfg.withDischarge grindDischarger
114+
else cfg
49115
let ⟨lemmas, ctx⟩ ← SolveByElim.mkAssumptionSet false false [] [] #[]
50116
let cfg := if !required.isEmpty then cfg.requireUsingAll required else cfg
51117
SolveByElim.solveByElim cfg lemmas ctx goals

tests/lean/run/info_trees.lean

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,57 +3,59 @@
33
-- it is fine to simply remove the `#guard_msgs` and expected output.
44

55
/--
6-
info: • [Command] @ ⟨77, 0⟩-⟨77, 40⟩ @ Lean.Elab.Command.elabDeclaration
7-
[Term] Nat : Type @ ⟨77, 15⟩-⟨77, 18⟩ @ Lean.Elab.Term.elabIdent
8-
[Completion-Id] Nat : some Sort.{?_uniq.1} @ ⟨77, 15⟩-⟨77, 18⟩
9-
[Term] Nat : Type @ ⟨77, 15⟩-⟨77, 18⟩
10-
[Term] n (isBinder := true) : Nat @ ⟨77, 11⟩-⟨77, 12⟩
11-
[Term] 0 ≤ n : Prop @ ⟨77, 22⟩-⟨77, 27⟩ @ «_aux_Init_Notation___macroRules_term_≤__2»
6+
info: • [Command] @ ⟨79, 0⟩-⟨79, 40⟩ @ Lean.Elab.Command.elabDeclaration
7+
[Term] Nat : Type @ ⟨79, 15⟩-⟨79, 18⟩ @ Lean.Elab.Term.elabIdent
8+
[Completion-Id] Nat : some Sort.{?_uniq.1} @ ⟨79, 15⟩-⟨79, 18⟩
9+
[Term] Nat : Type @ ⟨79, 15⟩-⟨79, 18⟩
10+
[Term] n (isBinder := true) : Nat @ ⟨79, 11⟩-⟨79, 12⟩
11+
[Term] 0 ≤ n : Prop @ ⟨79, 22⟩-⟨79, 27⟩ @ «_aux_Init_Notation___macroRules_term_≤__2»
1212
[MacroExpansion]
1313
0 ≤ n
1414
===>
1515
binrel% LE.le✝ 0 n
16-
[Term] 0 ≤ n : Prop @ ⟨77, 22⟩†-⟨77, 27⟩† @ Lean.Elab.Term.Op.elabBinRel
17-
[Term] 0 ≤ n : Prop @ ⟨77, 22⟩†-⟨77, 27⟩†
18-
[Completion-Id] LE.le✝ : none @ ⟨77, 22⟩†-⟨77, 27⟩†
19-
[Term] 0 : Nat @ ⟨77, 22⟩-⟨77, 23⟩ @ Lean.Elab.Term.elabNumLit
20-
[Term] n : Nat @ ⟨77, 26⟩-⟨77, 27⟩ @ Lean.Elab.Term.elabIdent
21-
[Completion-Id] n : none @ ⟨77, 26⟩-⟨77, 27⟩
22-
[Term] n : Nat @ ⟨77, 26⟩-⟨77, 27⟩
16+
[Term] 0 ≤ n : Prop @ ⟨79, 22⟩†-⟨79, 27⟩† @ Lean.Elab.Term.Op.elabBinRel
17+
[Term] 0 ≤ n : Prop @ ⟨79, 22⟩†-⟨79, 27⟩†
18+
[Completion-Id] LE.le✝ : none @ ⟨79, 22⟩†-⟨79, 27⟩†
19+
[Term] 0 : Nat @ ⟨79, 22⟩-⟨79, 23⟩ @ Lean.Elab.Term.elabNumLit
20+
[Term] n : Nat @ ⟨79, 26⟩-⟨79, 27⟩ @ Lean.Elab.Term.elabIdent
21+
[Completion-Id] n : none @ ⟨79, 26⟩-⟨79, 27⟩
22+
[Term] n : Nat @ ⟨79, 26⟩-⟨79, 27⟩
2323
[CustomInfo(Lean.Elab.Term.AsyncBodyInfo)]
24-
[Term] n (isBinder := true) : Nat @ ⟨77, 11⟩-⟨77, 12⟩
24+
[Term] n (isBinder := true) : Nat @ ⟨79, 11⟩-⟨79, 12⟩
2525
[CustomInfo(Lean.Elab.Term.BodyInfo)]
26-
[Tactic] @ ⟨77, 31⟩-⟨77, 40⟩
27-
(Term.byTactic "by" (Tactic.tacticSeq (Tactic.tacticSeq1Indented [(Tactic.exact? "exact?" [])])))
26+
[Tactic] @ ⟨79, 31⟩-⟨79, 40⟩
27+
(Term.byTactic
28+
"by"
29+
(Tactic.tacticSeq (Tactic.tacticSeq1Indented [(Tactic.exact? "exact?" (Tactic.optConfig []) [])])))
2830
before ⏎
2931
n : Nat
3032
⊢ 0 ≤ n
3133
after no goals
32-
[Tactic] @ ⟨77, 31⟩-⟨77, 33⟩
34+
[Tactic] @ ⟨79, 31⟩-⟨79, 33⟩
3335
"by"
3436
before ⏎
3537
n : Nat
3638
⊢ 0 ≤ n
3739
after no goals
38-
[Tactic] @ ⟨77, 34⟩-⟨77, 40⟩ @ Lean.Elab.Tactic.evalTacticSeq
39-
(Tactic.tacticSeq (Tactic.tacticSeq1Indented [(Tactic.exact? "exact?" [])]))
40+
[Tactic] @ ⟨79, 34⟩-⟨79, 40⟩ @ Lean.Elab.Tactic.evalTacticSeq
41+
(Tactic.tacticSeq (Tactic.tacticSeq1Indented [(Tactic.exact? "exact?" (Tactic.optConfig []) [])]))
4042
before ⏎
4143
n : Nat
4244
⊢ 0 ≤ n
4345
after no goals
44-
[Tactic] @ ⟨77, 34⟩-⟨77, 40⟩ @ Lean.Elab.Tactic.evalTacticSeq1Indented
45-
(Tactic.tacticSeq1Indented [(Tactic.exact? "exact?" [])])
46+
[Tactic] @ ⟨79, 34⟩-⟨79, 40⟩ @ Lean.Elab.Tactic.evalTacticSeq1Indented
47+
(Tactic.tacticSeq1Indented [(Tactic.exact? "exact?" (Tactic.optConfig []) [])])
4648
before ⏎
4749
n : Nat
4850
⊢ 0 ≤ n
4951
after no goals
50-
[Tactic] @ ⟨77, 34⟩-⟨77, 40⟩ @ Lean.Elab.LibrarySearch.evalExact
51-
(Tactic.exact? "exact?" [])
52+
[Tactic] @ ⟨79, 34⟩-⟨79, 40⟩ @ Lean.Elab.LibrarySearch.evalExact
53+
(Tactic.exact? "exact?" (Tactic.optConfig []) [])
5254
before ⏎
5355
n : Nat
5456
⊢ 0 ≤ n
5557
after no goals
56-
[Tactic] @ ⟨77, 34⟩†-⟨77, 40⟩† @ Lean.Elab.Tactic.evalExact
58+
[Tactic] @ ⟨79, 34⟩†-⟨79, 40⟩† @ Lean.Elab.Tactic.evalExact
5759
(Tactic.exact "exact" (Term.app `Nat.zero_le [`n]))
5860
before ⏎
5961
n : Nat
@@ -66,8 +68,8 @@ info: • [Command] @ ⟨77, 0⟩-⟨77, 40⟩ @ Lean.Elab.Command.elabDeclarati
6668
[Completion-Id] n : some Nat @ ⟨1, 5⟩†-⟨1, 5⟩†
6769
[Term] n : Nat @ ⟨1, 5⟩†-⟨1, 5⟩†
6870
[CustomInfo(Lean.Meta.Tactic.TryThis.TryThisInfo)]
69-
[Term] t (isBinder := true) : ∀ (n : Nat), 0 ≤ n @ ⟨77, 8⟩-⟨77, 9⟩
70-
[Term] t (isBinder := true) : ∀ (n : Nat), 0 ≤ n @ ⟨77, 8⟩-⟨77, 9⟩
71+
[Term] t (isBinder := true) : ∀ (n : Nat), 0 ≤ n @ ⟨79, 8⟩-⟨79, 9⟩
72+
[Term] t (isBinder := true) : ∀ (n : Nat), 0 ≤ n @ ⟨79, 8⟩-⟨79, 9⟩
7173
---
7274
info: Try this:
7375
[apply] exact Nat.zero_le n

0 commit comments

Comments
 (0)