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.iteif-term should be split, as if by the split tactic.
splitMatch (default true)
Every Lean.Parser.Term.match : termPattern 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