Skip to content

Commit 26bc8c5

Browse files
authored
feat: builtin case splits for grind (#6822)
This PR adds a few builtin case-splits for `grind`. They are similar to builtin `simp` theorems. They reduce the noise in the tactics produced by `grind?`.
1 parent eea2d49 commit 26bc8c5

File tree

3 files changed

+44
-8
lines changed

3 files changed

+44
-8
lines changed

src/Lean/Elab/Tactic/Grind.lean

+6-4
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ def elabGrindParams (params : Grind.Params) (ps : TSyntaxArray ``Parser.Tactic.
5353
| `(Parser.Tactic.grindParam| - $id:ident) =>
5454
let declName ← realizeGlobalConstNoOverloadWithInfo id
5555
if (← Grind.isCasesAttrCandidate declName false) then
56+
Grind.ensureNotBuiltinCases declName
5657
params := { params with casesTypes := (← params.casesTypes.eraseDecl declName) }
5758
else
5859
params := { params with ematch := (← params.ematch.eraseDecl declName) }
@@ -188,11 +189,12 @@ private def mkGrindOnly
188189
| .default => `(Parser.Tactic.grindParam| $decl:ident)
189190
params := params.push param
190191
for declName in trace.eagerCases.toList do
191-
let decl : Ident := mkIdent (← unresolveNameGlobalAvoidingLocals declName)
192-
let param ← `(Parser.Tactic.grindParam| cases eager $decl)
193-
params := params.push param
192+
unless Grind.isBuiltinEagerCases declName do
193+
let decl : Ident := mkIdent (← unresolveNameGlobalAvoidingLocals declName)
194+
let param ← `(Parser.Tactic.grindParam| cases eager $decl)
195+
params := params.push param
194196
for declName in trace.cases.toList do
195-
unless trace.eagerCases.contains declName do
197+
unless trace.eagerCases.contains declName || Grind.isBuiltinEagerCases declName do
196198
let decl : Ident := mkIdent (← unresolveNameGlobalAvoidingLocals declName)
197199
let param ← `(Parser.Tactic.grindParam| cases $decl)
198200
params := params.push param

src/Lean/Meta/Tactic/Grind/Cases.lean

+23-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,22 @@ structure CasesEntry where
1818
eager : Bool
1919
deriving Inhabited
2020

21+
/--
22+
`grind` always case-splits on the following types. Even when using `grind only`.
23+
The goal is to reduce noise in the tactic generated by `grind?`
24+
-/
25+
private def builtinEagerCases : NameSet :=
26+
RBTree.ofList [``And, ``Exists, ``True, ``False, ``Unit, ``Empty]
27+
28+
/--
29+
Returns `true` if `declName` is the name of inductive type/predicate that
30+
even `grind only` case splits on.
31+
Remark: we added support for them to reduce the noise in the tactic generated by
32+
`grind?`
33+
-/
34+
def isBuiltinEagerCases (declName : Name) : Bool :=
35+
builtinEagerCases.contains declName
36+
2137
/-- Returns `true` if `s` contains a `declName`. -/
2238
def CasesTypes.contains (s : CasesTypes) (declName : Name) : Bool :=
2339
s.casesMap.contains declName
@@ -33,10 +49,10 @@ def CasesTypes.find? (s : CasesTypes) (declName : Name) : Option Bool :=
3349
s.casesMap.find? declName
3450

3551
def CasesTypes.isEagerSplit (s : CasesTypes) (declName : Name) : Bool :=
36-
s.casesMap.find? declName |>.getD false
52+
(s.casesMap.find? declName |>.getD false) || isBuiltinEagerCases declName
3753

3854
def CasesTypes.isSplit (s : CasesTypes) (declName : Name) : Bool :=
39-
s.casesMap.find? declName |>.isSome
55+
(s.casesMap.find? declName |>.isSome) || isBuiltinEagerCases declName
4056

4157
builtin_initialize casesExt : SimpleScopedEnvExtension CasesEntry CasesTypes ←
4258
registerSimpleScopedEnvExtension {
@@ -80,7 +96,12 @@ def CasesTypes.eraseDecl (s : CasesTypes) (declName : Name) : CoreM CasesTypes :
8096
else
8197
throwError "`{declName}` is not marked with the `[grind]` attribute"
8298

99+
def ensureNotBuiltinCases (declName : Name) : CoreM Unit := do
100+
if isBuiltinEagerCases declName then
101+
throwError "`{declName}` is marked as a built-in case-split for `grind` and cannot be erased"
102+
83103
def eraseCasesAttr (declName : Name) : CoreM Unit := do
104+
ensureNotBuiltinCases declName
84105
let s := casesExt.getState (← getEnv)
85106
let s ← s.eraseDecl declName
86107
modifyEnv fun env => casesExt.modifyState env fun _ => s

tests/lean/run/grind_trace.lean

+15-2
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,14 @@ example : 0 < (x :: t).length := by
3333
/--
3434
info: Try this: grind only [= List.getElem?_replicate, = List.getElem?_eq_some_iff, = List.getElem?_map, =
3535
List.getElem_replicate, = List.getElem?_eq_none, = Option.map_some', = Option.map_none', = List.length_replicate, →
36-
List.getElem?_eq_getElem, cases And, cases Or, cases Exists]
36+
List.getElem?_eq_getElem, cases Or]
3737
-/
3838
#guard_msgs (info) in
3939
theorem map_replicate' : (List.replicate n a).map f = List.replicate n (f a) := by
4040
grind?
4141

4242
/--
43-
info: Try this: grind only [= List.getLast?_eq_some_iff, List.mem_concat_self, cases Exists]
43+
info: Try this: grind only [= List.getLast?_eq_some_iff, List.mem_concat_self]
4444
-/
4545
#guard_msgs (info) in
4646
theorem mem_of_getLast?_eq_some' {xs : List α} {a : α} (h : xs.getLast? = some a) : a ∈ xs := by
@@ -79,3 +79,16 @@ info: Try this: grind only [usr gthm]
7979
#guard_msgs (info) in
8080
example : g (g (g x)) = g x := by
8181
grind?
82+
83+
/--
84+
error: `And` is marked as a built-in case-split for `grind` and cannot be erased
85+
-/
86+
#guard_msgs (error) in
87+
attribute [-grind] And
88+
89+
/--
90+
error: `And` is marked as a built-in case-split for `grind` and cannot be erased
91+
-/
92+
#guard_msgs (error) in
93+
example : p ∧ q → p := by
94+
grind [-And]

0 commit comments

Comments
 (0)