Skip to content

Commit af1138b

Browse files
committed
feat: linters to try hammers at every step (#30808)
This PR adds three linters which respectively try running `simp_all`, `aesop`, and `grind` at each step in Mathlib. The intention is not to look at individual outputs and make replacements, but rather to use these as baselines for premise selection algorithm comparisons. In leanprover/lean4#10920 I'm adding `grind +premises` (and I'd like to do the same with `simp_all` and `aesop`), and we can then measure how different premise selection algorithms compare at library scale.
1 parent e963315 commit af1138b

File tree

5 files changed

+115
-1
lines changed

5 files changed

+115
-1
lines changed

Mathlib/Tactic/Core.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def MVarId.introsWithBinderIdents
7474
(g : MVarId) (ids : List (TSyntax ``binderIdent)) (maxIntros? : Option Nat := none) :
7575
MetaM (List (TSyntax ``binderIdent) × Array FVarId × MVarId) := do
7676
let type ← g.getType
77-
let type ← instantiateMVars type
77+
let type ← Lean.instantiateMVars type
7878
let n := getIntrosSize type
7979
let n := match maxIntros? with | none => n | some maxIntros => min n maxIntros
8080
if n == 0 then

Mathlib/Tactic/TacticAnalysis/Declarations.lean

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,59 @@ def terminalToGrind : TacticAnalysis.Config where
265265
if oldHeartbeats * 2 < newHeartbeats then
266266
logWarningAt stx m!"'grind' is slower than the original: {oldHeartbeats} -> {newHeartbeats}"
267267

268+
open Elab.Command in
269+
/-- Run a tactic at each proof step. -/
270+
def tryAtEachStep (tac : Syntax → MVarId → CommandElabM (TSyntax `tactic)) : TacticAnalysis.Config where
271+
run seq := do
272+
for (ctx, i) in seq do
273+
if let [goal] := i.goalsBefore then
274+
let tac ← tac i.stx goal
275+
let goalsAfter ← try
276+
ctx.runTacticCode i goal tac
277+
catch _e =>
278+
pure [goal]
279+
if goalsAfter.isEmpty then
280+
logInfoAt i.stx m!"`{i.stx}` can be replaced with `{tac}`"
281+
282+
/-- Run `grind` at every step in proofs, reporting where it succeeds. -/
283+
register_option linter.tacticAnalysis.tryAtEachStepGrind : Bool := {
284+
defValue := false
285+
}
286+
287+
@[tacticAnalysis linter.tacticAnalysis.tryAtEachStepGrind,
288+
inherit_doc linter.tacticAnalysis.tryAtEachStepGrind]
289+
def tryAtEachStepGrind := tryAtEachStep (fun _ _ => `(tactic| grind))
290+
291+
/-- Run `simp_all` at every step in proofs, reporting where it succeeds. -/
292+
register_option linter.tacticAnalysis.tryAtEachStepSimpAll : Bool := {
293+
defValue := false
294+
}
295+
296+
@[tacticAnalysis linter.tacticAnalysis.tryAtEachStepSimpAll,
297+
inherit_doc linter.tacticAnalysis.tryAtEachStepSimpAll]
298+
def tryAtEachStepSimpAll := tryAtEachStep (fun _ _ => `(tactic| simp_all))
299+
300+
/-- Run `aesop` at every step in proofs, reporting where it succeeds. -/
301+
register_option linter.tacticAnalysis.tryAtEachStepAesop : Bool := {
302+
defValue := false
303+
}
304+
305+
@[tacticAnalysis linter.tacticAnalysis.tryAtEachStepAesop,
306+
inherit_doc linter.tacticAnalysis.tryAtEachStepAesop]
307+
def tryAtEachStepAesop := tryAtEachStep
308+
-- As `aesop` isn't imported here, we construct the tactic syntax manually.
309+
fun _ _ => return ⟨TSyntax.raw <|
310+
mkNode `Aesop.Frontend.Parser.aesopTactic #[mkAtom "aesop", mkNullNode]⟩
311+
312+
/-- Run `grind +premises` at every step in proofs, reporting where it succeeds. -/
313+
register_option linter.tacticAnalysis.tryAtEachStepGrindPremises : Bool := {
314+
defValue := false
315+
}
316+
317+
@[tacticAnalysis linter.tacticAnalysis.tryAtEachStepGrindPremises,
318+
inherit_doc linter.tacticAnalysis.tryAtEachStepGrindPremises]
319+
def tryAtEachStepGrindPremises := tryAtEachStep (fun _ _ => `(tactic| grind +premises))
320+
268321
-- TODO: add compatibility with `rintro` and `intros`
269322
/-- Suggest merging two adjacent `intro` tactics which don't pattern match. -/
270323
register_option linter.tacticAnalysis.introMerge : Bool := {

MathlibTest/TacticAnalysis.lean

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Mathlib.Tactic.TacticAnalysis.Declarations
22
import Mathlib.Tactic.AdaptationNote
3+
import Lean.PremiseSelection
34

45
section terminalReplacement
56

@@ -138,3 +139,53 @@ example : ∀ a b : Unit, a = b := by
138139
rfl
139140

140141
end introMerge
142+
143+
section tryAtEachStep
144+
145+
section
146+
set_option linter.tacticAnalysis.tryAtEachStepGrind true
147+
148+
/-- info: `rfl` can be replaced with `grind` -/
149+
#guard_msgs in
150+
example : 1 + 1 = 2 := by
151+
rfl
152+
153+
/--
154+
info: `skip` can be replaced with `grind`
155+
---
156+
info: `rfl` can be replaced with `grind`
157+
---
158+
warning: 'skip' tactic does nothing
159+
160+
Note: This linter can be disabled with `set_option linter.unusedTactic false`
161+
-/
162+
#guard_msgs in
163+
example : 1 + 1 = 2 := by
164+
skip
165+
rfl
166+
167+
end
168+
169+
section
170+
171+
def P (_ : Nat) := True
172+
theorem p : P 37 := trivial
173+
174+
set_premise_selector fun _ _ => pure #[{ name := `p, score := 1.0 }]
175+
176+
-- FIXME: remove this one `grind +premises` lands.
177+
macro_rules | `(tactic| grind +premises) => `(tactic| grind [p])
178+
179+
example : P 37 := by
180+
grind +premises
181+
182+
set_option linter.tacticAnalysis.tryAtEachStepGrindPremises true
183+
184+
/-- info: `trivial` can be replaced with `grind +premises✝` -/
185+
#guard_msgs in
186+
example : P 37 := by
187+
trivial
188+
189+
end
190+
191+
end tryAtEachStep

MathlibTest/tryAtEachStep.lean

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import Mathlib.Tactic.Common
2+
import Mathlib.Tactic.TacticAnalysis.Declarations
3+
4+
set_option linter.tacticAnalysis.tryAtEachStepAesop true
5+
6+
/-- info: `rfl` can be replaced with `aesop` -/
7+
#guard_msgs in
8+
theorem foo : 2 + 2 = 4 := by
9+
rfl

scripts/noshake.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@
201201
["Batteries.Tactic.Basic",
202202
"Mathlib.Mathport.Syntax",
203203
"Mathlib.Tactic.Linter",
204+
"Mathlib.Tactic.Common",
204205
"Mathlib.Init"],
205206
"ignore":
206207
{"Mathlib.Util.Notation3": ["Lean.Elab.AuxDef", "Lean.Elab.BuiltinCommand"],

0 commit comments

Comments
 (0)