attr ::= ... | grind cases
The grind cases
attribute marks inductively-defined predicates as suitable for case splitting.
In addition to congruence closure and constraint propagation, grind
performs case analysis.
During case analysis, grind
considers each possible way that a term could have been built, or each possbile value of a particular term, in a manner similar to the cases
and split
tactics.
This case analysis is not exhaustive: grind
only recursively splits cases up to a configured depth limit, and configuration options and annotations control which terms are candidates for splitting.
grind
decides which sub‑term to split on by combining three sources of signal:
These configuration flags determine whether grind
performs certain case splits:
splitIte
(default true
)
Every Lean.Parser.Term.ite
if
-term should be split, as if by the split
tactic.
splitMatch
(default true
)
Every Lean.Parser.Term.match : term
Pattern matching. `match e, ... with | p, ... => f | ...` matches each given
term `e` against each pattern `p` of a match alternative. When all patterns
of an alternative match, the `match` term evaluates to the value of the
corresponding right-hand side `f` with the pattern variables bound to the
respective matched values.
If used as `match h : e, ... with | p, ... => f | ...`, `h : e = p` is available
within `f`.
When not constructing a proof, `match` does not automatically substitute variables
matched on in dependent variables' types. Use `match (generalizing := true) ...` to
enforce this.
Syntax quotations can also be used in a pattern match.
This matches a `Syntax` value against quotations, pattern variables, or `_`.
Quoted identifiers only match identical identifiers - custom matching such as by the preresolved
names only should be done explicitly.
`Syntax.atom`s are ignored during matching by default except when part of a built-in literal.
For users introducing new atoms, we recommend wrapping them in dedicated syntax kinds if they
should participate in matching.
For example, in
```lean
syntax "c" ("foo" <|> "bar") ...
```
`foo` and `bar` are indistinguishable during matching, but in
```lean
syntax foo := "foo"
syntax "c" (foo <|> "bar") ...
```
they are not.
match
-term should be split, as if by the split
tactic.
splitImp
(default false
)
Hypotheses of the form A → B
whose antecedent A
is propositional are split by considering all possibilities for A
.
Arithmetic antecedents are special‑cased: if A
is an arithmetic literal (that is, a proposition formed by operators such as ≤
, =
, ¬
, Dvd
, …) then grind
will split even when splitImp := false
so the integer solver can propagate facts.
The grind
option splits := n
caps the depth of the search tree.
Once a branch performs n
splits grind
stops splitting further in that branch; if the branch cannot be closed it reports that the split threshold has been reached.
Inductive predicates or structures may be tagged with the grind cases
attribute.
grind
treats every instance of that predicate as a candidate for splitting.
attr ::= ... | grind cases
The grind cases
attribute marks inductively-defined predicates as suitable for case splitting.
In this example, grind
proves the theorem by considering both cases for the conditional:
example (c : Bool) (x y : Nat)
(h : (if c then x else y) = 0) :
x = 0 ∨ y = 0 := c:Boolx:Naty:Nath:(if c = true then x else y) = 0⊢ x = 0 ∨ y = 0
All goals completed! 🐙
Disabling splitIte
causes the proof to fail:
example (c : Bool) (x y : Nat)
(h : (if c then x else y) = 0) :
x = 0 ∨ y = 0 := c:Boolx:Naty:Nath:(if c = true then x else y) = 0⊢ x = 0 ∨ y = 0
c:Boolx:Naty:Nath:(if c = true then x else y) = 0⊢ x = 0 ∨ y = 0
In particular, it cannot make progress after discovering that the conditional expression is equal to 0
:
Forbidding all case splitting causes the proof to fail for the same reason:
example (c : Bool) (x y : Nat)
(h : (if c then x else y) = 0) :
x = 0 ∨ y = 0 := c:Boolx:Naty:Nath:(if c = true then x else y) = 0⊢ x = 0 ∨ y = 0
c:Boolx:Naty:Nath:(if c = true then x else y) = 0⊢ x = 0 ∨ y = 0
Allowing just one split is sufficient:
example (c : Bool) (x y : Nat)
(h : (if c then x else y) = 0) :
x = 0 ∨ y = 0 := c:Boolx:Naty:Nath:(if c = true then x else y) = 0⊢ x = 0 ∨ y = 0
All goals completed! 🐙
Disabling case splitting on pattern matches causes grind
to fail in this example:
example (h : y = match x with | 0 => 1 | _ => 2) :
y > 0 := y:Natx:Nath:y =
match x with
| 0 => 1
| x => 2⊢ y > 0
y:Natx:Nath:y =
match x with
| 0 => 1
| x => 2⊢ y > 0
Enabling the option causes the proof to succeed:
example (h : y = match x with | 0 => 1 | _ => 2) :
y > 0 := y:Natx:Nath:y =
match x with
| 0 => 1
| x => 2⊢ y > 0
All goals completed! 🐙
Not30
is a somewhat verbose way to state that a number is not 30
:
inductive Not30 : Nat → Prop where
| gt : x > 30 → Not30 x
| lt : x < 30 → Not30 x
By default, grind
cannot show that Not30
implies that a number is, in fact, not 30
:
example : Not30 n → n ≠ 30 := n:Nat⊢ Not30 n → n ≠ 30 n:Nat⊢ Not30 n → n ≠ 30
This is because grind
does not consider both cases for Not30
Adding the grind cases
attribute to Not30
allows the proof to succeed:
attribute [grind cases] Not30
example : Not30 n → n ≠ 30 := n:Nat⊢ Not30 n → n ≠ 30 All goals completed! 🐙
Similarly, the grind cases
attribute on Even
allows grind
to perform case splits:
@[grind cases]
inductive Even : Nat → Prop
| zero : Even 0
| step : Even n → Even (n + 2)
attribute [grind cases] Even
example (h : Even 5) : False := h:Even 5⊢ False
All goals completed! 🐙
set_option trace.grind.split true in
example (h : Even (n + 2)) : Even n := n:Nath:Even (n + 2)⊢ Even n
All goals completed! 🐙
Case analysis is powerful, but computationally expensive: each level of case splitting multiplies the search space. It's important to be judicious and not perform unnecessary splits. In particular:
Increase splits
only when the goal genuinely needs deeper branching; each extra level multiplies the search space.
Disable splitMatch
when large pattern‑matching definitions explode the tree; this can be observed by setting the trace.grind.split
.
Flags can be combined, e.g. by grind -splitMatch (splits := 10) +splitImp
.
The grind cases
attribute is scoped.
The modifiers Lean.Parser.Term.attrKind
`attrKind` matches `("scoped" <|> "local")?`, used before an attribute like `@[local simp]`.
local
and Lean.Parser.Term.attrKind
`attrKind` matches `("scoped" <|> "local")?`, used before an attribute like `@[local simp]`.
scoped
restrict extra splitting to a section or namespace.
trace.grind.split
Default value: false
enable/disable tracing for the given module and submodules