Skip to content

Commit

Permalink
feat: improve case split heuristic used in grind (#6613)
Browse files Browse the repository at this point in the history
This PR improves the case split heuristic used in the `grind` tactic,
ensuring it now avoids unnecessary case-splits on `Iff`.
  • Loading branch information
leodemoura authored Jan 12, 2025
1 parent 8b1aabb commit 5419025
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 26 deletions.
79 changes: 53 additions & 26 deletions src/Lean/Meta/Tactic/Grind/Split.lean
Original file line number Diff line number Diff line change
Expand Up @@ -17,43 +17,70 @@ inductive CaseSplitStatus where
| ready (numCases : Nat) (isRec := false)
deriving Inhabited, BEq

private def checkCaseSplitStatus (e : Expr) : GoalM CaseSplitStatus := do
match_expr e with
| Or a b =>
if (← isEqTrue e) then
if (← isEqTrue a <||> isEqTrue b) then
return .resolved
else
return .ready 2
else if (← isEqFalse e) then
/-- Given `c`, the condition of an `if-then-else`, check whether we need to case-split on the `if-then-else` or not -/
private def checkIteCondStatus (c : Expr) : GoalM CaseSplitStatus := do
if (← isEqTrue c <||> isEqFalse c) then
return .resolved
else
return .ready 2

/--
Given `e` of the form `a ∨ b`, check whether we are ready to case-split on `e`.
That is, `e` is `True`, but neither `a` nor `b` is `True`."
-/
private def checkDisjunctStatus (e a b : Expr) : GoalM CaseSplitStatus := do
if (← isEqTrue e) then
if (← isEqTrue a <||> isEqTrue b) then
return .resolved
else
return .notReady
| And a b =>
if (← isEqTrue e) then
return .ready 2
else if (← isEqFalse e) then
return .resolved
else
return .notReady

/--
Given `e` of the form `a ∧ b`, check whether we are ready to case-split on `e`.
That is, `e` is `False`, but neither `a` nor `b` is `False`.
-/
private def checkConjunctStatus (e a b : Expr) : GoalM CaseSplitStatus := do
if (← isEqTrue e) then
return .resolved
else if (← isEqFalse e) then
if (← isEqFalse a <||> isEqFalse b) then
return .resolved
else if (← isEqFalse e) then
if (← isEqFalse a <||> isEqFalse b) then
return .resolved
else
return .ready 2
else
return .notReady
| Eq _ _ _ =>
if (← isEqTrue e <||> isEqFalse e) then
return .ready 2
else
return .notReady
| ite _ c _ _ _ =>
if (← isEqTrue c <||> isEqFalse c) then
else
return .notReady

/--
Given `e` of the form `@Eq Prop a b`, check whether we are ready to case-split on `e`.
There are two cases:
1- `e` is `True`, but neither both `a` and `b` are `True`, nor both `a` and `b` are `False`.
2- `e` is `False`, but neither `a` is `True` and `b` is `False`, nor `a` is `False` and `b` is `True`.
-/
private def checkIffStatus (e a b : Expr) : GoalM CaseSplitStatus := do
if (← isEqTrue e) then
if (← (isEqTrue a <&&> isEqTrue b) <||> (isEqFalse a <&&> isEqFalse b)) then
return .resolved
else
return .ready 2
| dite _ c _ _ _ =>
if (← isEqTrue c <||> isEqFalse c) then
else if (← isEqFalse e) then
if (← (isEqTrue a <&&> isEqFalse b) <||> (isEqFalse a <&&> isEqTrue b)) then
return .resolved
else
return .ready 2
else
return .notReady

private def checkCaseSplitStatus (e : Expr) : GoalM CaseSplitStatus := do
match_expr e with
| Or a b => checkDisjunctStatus e a b
| And a b => checkConjunctStatus e a b
| Eq _ a b => checkIffStatus e a b
| ite _ c _ _ _ => checkIteCondStatus c
| dite _ c _ _ _ => checkIteCondStatus c
| _ =>
if (← isResolvedCaseSplit e) then
trace[grind.debug.split] "split resolved: {e}"
Expand Down
26 changes: 26 additions & 0 deletions tests/lean/run/grind_t1.lean
Original file line number Diff line number Diff line change
Expand Up @@ -237,3 +237,29 @@ example {α} (a b c : α) [LE α] :

example (x y : Bool) : ¬(x = true ↔ y = true) ↔ (¬(x = true) ↔ y = true) := by
grind

/--
error: `grind` failed
case grind
p q : Prop
a✝¹ : p = q
a✝ : p
⊢ False
-/
#guard_msgs (error) in
set_option trace.grind.split true in
example (p q : Prop) : (p ↔ q) → p → False := by
grind -- should not split on (p ↔ q)

/--
error: `grind` failed
case grind
p q : Prop
a✝¹ : p = ¬q
a✝ : p
⊢ False
-/
#guard_msgs (error) in
set_option trace.grind.split true in
example (p q : Prop) : ¬(p ↔ q) → p → False := by
grind -- should not split on (p ↔ q)

0 comments on commit 5419025

Please sign in to comment.