Skip to content

Commit fdabf46

Browse files
committed
feat: exact? +grind uses grind as a discharger
1 parent 519ccf5 commit fdabf46

File tree

5 files changed

+120
-9
lines changed

5 files changed

+120
-9
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: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1690,24 +1690,36 @@ 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+
16931701
/--
16941702
Searches environment for definitions or theorems that can solve the goal using `exact`
16951703
with conditions resolved by `solve_by_elim`.
16961704
16971705
The optional `using` clause provides identifiers in the local context that must be
16981706
used by `exact?` when closing the goal. This is most useful if there are multiple
16991707
ways to resolve the goal, and one wants to guide which lemma is used.
1708+
1709+
Use `+grind` to enable `grind` as a fallback discharger for subgoals.
17001710
-/
1701-
syntax (name := exact?) "exact?" (" using " (colGt ident),+)? : tactic
1711+
syntax (name := exact?) "exact?" optConfig (" using " (colGt ident),+)? : tactic
17021712

17031713
/--
17041714
Searches environment for definitions or theorems that can refine the goal using `apply`
17051715
with conditions resolved when possible with `solve_by_elim`.
17061716
17071717
The optional `using` clause provides identifiers in the local context that must be
17081718
used when closing the goal.
1719+
1720+
Use `+grind` to enable `grind` as a fallback discharger for subgoals.
17091721
-/
1710-
syntax (name := apply?) "apply?" (" using " (colGt term),+)? : tactic
1722+
syntax (name := apply?) "apply?" optConfig (" using " (colGt term),+)? : tactic
17111723

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

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)
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: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ 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
1214

1315
public section
1416

@@ -36,16 +38,46 @@ builtin_initialize registerTraceClass `Tactic.librarySearch.lemmas
3638

3739
open SolveByElim
3840

41+
/--
42+
A discharger that tries `grind` on the goal.
43+
The proof is wrapped in `Grind.Marker` so that suggestions display `(by grind)`
44+
instead of the complex grind proof term.
45+
Returns `some []` if grind succeeds, `none` otherwise (leaving the goal as a subgoal).
46+
-/
47+
def grindDischarger (mvarId : MVarId) : MetaM (Option (List MVarId)) := do
48+
try
49+
-- Apply the marker wrapper, creating a subgoal for grind to solve
50+
let type ← mvarId.getType
51+
let u ← getLevel type
52+
let markerExpr := mkApp (.const ``Lean.Grind.Marker [u]) type
53+
let [subgoal] ← mvarId.apply markerExpr
54+
| return none
55+
-- Solve the subgoal with grind
56+
let params ← Grind.mkParams {}
57+
let result ← Grind.main subgoal params
58+
if result.hasFailed then
59+
return none
60+
else
61+
return some []
62+
catch _ =>
63+
return none
64+
3965
/--
4066
Wrapper for calling `Lean.Meta.SolveByElim.solveByElim with
4167
appropriate arguments for library search.
68+
69+
If `grind` is true, `grind` will be used as a fallback discharger for subgoals
70+
that cannot be closed by other means.
4271
-/
43-
def solveByElim (required : List Expr) (exfalso : Bool) (goals : List MVarId) (maxDepth : Nat) := do
72+
def solveByElim (required : List Expr) (exfalso : Bool) (goals : List MVarId)
73+
(maxDepth : Nat) (grind : Bool := false) := do
4474
let cfg : SolveByElimConfig :=
4575
{ maxDepth, exfalso := exfalso, symm := true, commitIndependentGoals := true,
4676
transparency := ← getTransparency,
4777
-- `constructor` has been observed to significantly slow down `exact?` in Mathlib.
4878
constructor := false }
79+
-- Add grind as a fallback discharger (tried after intro/constructor fail)
80+
let cfg := if grind then cfg.withDischarge grindDischarger else cfg
4981
let ⟨lemmas, ctx⟩ ← SolveByElim.mkAssumptionSet false false [] [] #[]
5082
let cfg := if !required.isEmpty then cfg.requireUsingAll required else cfg
5183
SolveByElim.solveByElim cfg lemmas ctx goals
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/-!
2+
# Tests for `exact? +grind` and `apply? +grind`
3+
4+
These tests verify that the `+grind` option is accepted syntactically and
5+
enables `grind` as a fallback discharger for subgoals.
6+
-/
7+
8+
/--
9+
info: Try this:
10+
[apply] exact List.ne_nil_of_length_pos h
11+
-/
12+
#guard_msgs in
13+
example (l : List Nat) (h : 0 < l.length) : l ≠ [] := by exact?
14+
15+
/--
16+
info: Try this:
17+
[apply] exact List.ne_nil_of_length_pos (h trivial)
18+
-/
19+
#guard_msgs in
20+
example (l : List Nat) (h : True → 0 < l.length) : l ≠ [] := by exact?
21+
22+
example (l : List Nat) (h : 1 < l.length) : l ≠ [] := by exact List.ne_nil_of_length_pos (by grind)
23+
24+
/--
25+
info: Try this:
26+
[apply] exact List.ne_nil_of_length_pos (by grind)
27+
-/
28+
#guard_msgs in
29+
example (l : List Nat) (h : 1 < l.length) : l ≠ [] := by
30+
exact? +grind
31+
32+
/--
33+
info: Try this:
34+
[apply] exact List.ne_nil_of_length_pos (by grind)
35+
-/
36+
#guard_msgs in
37+
example {P : Prop} (l : List Nat) (p : P) (h : P → 1 < l.length) : l ≠ [] := by
38+
exact? +grind
39+
40+
axiom foo (x : Nat) : x < 375 < x → x.log2 < 6
41+
42+
/--
43+
info: Try this:
44+
[apply] exact foo x (by grind) (by grind)
45+
-/
46+
#guard_msgs in
47+
example (x : Nat) (h₁ : x < 30) (h₂ : 8 < x) : x.log2 < 6 := by
48+
exact? +grind

0 commit comments

Comments
 (0)