Skip to content

Commit 245ed05

Browse files
authored
fix: grind +splitImp, arrow propagator, missing normalization rule (#8158)
This PR fixes the `grind +splitImp` and the arrow propagator. Given `p : Prop`, the propagator was incorrectly assuming `A` was always a proposition in an arrow `A -> p`. This PR also adds a missing normalization rule to `grind`.
1 parent eaf1c6b commit 245ed05

File tree

8 files changed

+78
-49
lines changed

8 files changed

+78
-49
lines changed

src/Init/Grind/Norm.lean

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ theorem not_eq_prop (p q : Prop) : (¬(p = q)) = (p = ¬q) := by
3131
theorem imp_eq (p q : Prop) : (p → q) = (¬ p ∨ q) := by
3232
by_cases p <;> by_cases q <;> simp [*]
3333

34+
-- Unless `+splitImp` is used, `grind` will not be able to do much with this kind of implication.
35+
-- Thus, this normalization step is enabled by default.
36+
theorem forall_imp_eq_or {α} (p : α → Prop) (q : Prop) : ((∀ a, p a) → q) = ((∃ a, ¬ p a) ∨ q) := by
37+
rw [imp_eq]; simp
38+
3439
theorem true_imp_eq (p : Prop) : (True → p) = p := by simp
3540
theorem false_imp_eq (p : Prop) : (False → p) = True := by simp
3641
theorem imp_true_eq (p : Prop) : (p → True) = True := by simp
@@ -125,6 +130,7 @@ init_grind_norm
125130
dite_eq_ite
126131
-- Forall
127132
forall_and forall_false forall_true
133+
forall_imp_eq_or
128134
-- Exists
129135
exists_const exists_or exists_prop exists_and_left exists_and_right
130136
-- Bool cond

src/Lean/Expr.lean

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1000,6 +1000,18 @@ def bindingInfo! : Expr → BinderInfo
10001000
| lam _ _ _ bi => bi
10011001
| _ => panic! "binding expected"
10021002

1003+
def forallName : (a : Expr) → a.isForall → Name
1004+
| forallE n _ _ _, _ => n
1005+
1006+
def forallDomain : (a : Expr) → a.isForall → Expr
1007+
| forallE _ d _ _, _ => d
1008+
1009+
def forallBody : (a : Expr) → a.isForall → Expr
1010+
| forallE _ _ b _, _ => b
1011+
1012+
def forallInfo : (a : Expr) → a.isForall → BinderInfo
1013+
| forallE _ _ _ i, _ => i
1014+
10031015
def letName! : Expr → Name
10041016
| letE n .. => n
10051017
| _ => panic! "let expression expected"

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ private def addLocalEMatchTheorems (e : Expr) : GoalM Unit := do
8686
def propagateForallPropDown (e : Expr) : GoalM Unit := do
8787
let .forallE n a b bi := e | return ()
8888
if (← isEqFalse e) then
89-
if b.hasLooseBVars then
89+
if b.hasLooseBVars || !(← isProp a) then
9090
let α := a
9191
let p := b
9292
-- `e` is of the form `∀ x : α, p x`

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def isMorallyIff (e : Expr) : Bool :=
5959

6060
/-- Inserts `e` into the list of case-split candidates if applicable. -/
6161
private def checkAndAddSplitCandidate (e : Expr) : GoalM Unit := do
62-
match e with
62+
match h : e with
6363
| .app .. =>
6464
if (← getConfig).splitIte && (e.isIte || e.isDIte) then
6565
addSplitCandidate (.default e)
@@ -91,12 +91,13 @@ private def checkAndAddSplitCandidate (e : Expr) : GoalM Unit := do
9191
addSplitCandidate (.default e)
9292
| .forallE _ d _ _ =>
9393
if (← getConfig).splitImp then
94-
addSplitCandidate (.default e)
94+
if (← isProp d) then
95+
addSplitCandidate (.imp e (h ▸ rfl))
9596
else if Arith.isRelevantPred d then
9697
if (← getConfig).lookahead then
97-
addLookaheadCandidate (.default e)
98+
addLookaheadCandidate (.imp e (h ▸ rfl))
9899
else
99-
addSplitCandidate (.default e)
100+
addSplitCandidate (.imp e (h ▸ rfl))
100101
| _ => pure ()
101102

102103
/--

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

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -83,20 +83,6 @@ private def isCongrToPrevSplit (c : Expr) : GoalM Bool := do
8383
else
8484
return c'.isApp && isCongruent (← get).enodes c c'
8585

86-
private def checkForallStatus (e : Expr) : GoalM SplitStatus := do
87-
if (← isEqTrue e) then
88-
let .forallE _ p q _ := e | return .resolved
89-
if (← isEqTrue p <||> isEqFalse p) then
90-
return .resolved
91-
unless q.hasLooseBVars do
92-
if (← isEqTrue q <||> isEqFalse q) then
93-
return .resolved
94-
return .ready 2
95-
else if (← isEqFalse e) then
96-
return .resolved
97-
else
98-
return .notReady
99-
10086
private def checkDefaultSplitStatus (e : Expr) : GoalM SplitStatus := do
10187
match_expr e with
10288
| Or a b => checkDisjunctStatus e a b
@@ -112,10 +98,6 @@ private def checkDefaultSplitStatus (e : Expr) : GoalM SplitStatus := do
11298
if (← isResolvedCaseSplit e) then
11399
trace_goal[grind.debug.split] "split resolved: {e}"
114100
return .resolved
115-
if e.isForall then
116-
let s ← checkForallStatus e
117-
trace_goal[grind.debug.split] "{e}, status: {repr s}"
118-
return s
119101
if (← isCongrToPrevSplit e) then
120102
return .resolved
121103
if let some info := isMatcherAppCore? (← getEnv) e then
@@ -156,9 +138,25 @@ def checkSplitInfoArgStatus (a b : Expr) (eq : Expr) : GoalM SplitStatus := do
156138
it_b := it_b.appFn!
157139
return .ready 2
158140

141+
private def checkForallStatus (imp : Expr) (h : imp.isForall) : GoalM SplitStatus := do
142+
if (← isEqTrue imp) then
143+
let p := imp.forallDomain h
144+
let q := imp.forallBody h
145+
if (← isEqTrue p <||> isEqFalse p) then
146+
return .resolved
147+
unless q.hasLooseBVars do
148+
if (← isEqTrue q <||> isEqFalse q) then
149+
return .resolved
150+
return .ready 2
151+
else if (← isEqFalse imp) then
152+
return .resolved
153+
else
154+
return .notReady
155+
159156
def checkSplitStatus (s : SplitInfo) : GoalM SplitStatus := do
160157
match s with
161158
| .default e => checkDefaultSplitStatus e
159+
| .imp e h => checkForallStatus e h
162160
| .arg a b _ eq => checkSplitInfoArgStatus a b eq
163161

164162
private inductive SplitCandidate where
@@ -229,9 +227,7 @@ private def mkCasesMajor (c : Expr) : GoalM Expr := do
229227
return mkGrindEM c
230228
| Not e => return mkGrindEM e
231229
| _ =>
232-
if let .forallE _ p _ _ := c then
233-
return mkGrindEM p
234-
else if (← isEqTrue c) then
230+
if (← isEqTrue c) then
235231
return mkOfEqTrueCore c (← mkEqTrueProof c)
236232
else
237233
return c
@@ -242,6 +238,12 @@ private def introNewHyp (goals : List Goal) (acc : List Goal) (generation : Nat)
242238
| [] => return acc.reverse
243239
| goal::goals => introNewHyp goals ((← intros generation goal) ++ acc) generation
244240

241+
private def casesWithTrace (major : Expr) : GoalM (List MVarId) := do
242+
if (← getConfig).trace then
243+
if let .const declName _ := (← whnfD (← inferType major)).getAppFn then
244+
saveCases declName false
245+
cases (← get).mvarId major
246+
245247
/--
246248
Selects a case-split from the list of candidates,
247249
and returns a new list of goals if successful.
@@ -250,22 +252,20 @@ def splitNext : GrindTactic := fun goal => do
250252
let (goals?, _) ← GoalM.run goal do
251253
let .some c numCases isRec _ ← selectNextSplit?
252254
| return none
253-
let c := c.getExpr
254-
let gen ← getGeneration c
255+
let cExpr := c.getExpr
256+
let gen ← getGeneration cExpr
255257
let genNew := if numCases > 1 || isRec then gen+1 else gen
256-
markCaseSplitAsResolved c
257-
trace_goal[grind.split] "{c}, generation: {gen}"
258-
let mvarIds ← if (← isMatcherApp c) then
259-
casesMatch (← get).mvarId c
258+
markCaseSplitAsResolved cExpr
259+
trace_goal[grind.split] "{cExpr}, generation: {gen}"
260+
let mvarIds ← if let .imp e h := c then
261+
casesWithTrace (mkGrindEM (e.forallDomain h))
262+
else if (← isMatcherApp cExpr) then
263+
casesMatch (← get).mvarId cExpr
260264
else
261-
let major ← mkCasesMajor c
262-
if (← getConfig).trace then
263-
if let .const declName _ := (← whnfD (← inferType major)).getAppFn then
264-
saveCases declName false
265-
cases (← get).mvarId major
265+
casesWithTrace (← mkCasesMajor cExpr)
266266
let goal ← get
267267
let numSubgoals := mvarIds.length
268-
let goals := mvarIds.mapIdx fun i mvarId => { goal with mvarId, split.trace := { expr := c, i, num := numSubgoals } :: goal.split.trace }
268+
let goals := mvarIds.mapIdx fun i mvarId => { goal with mvarId, split.trace := { expr := cExpr, i, num := numSubgoals } :: goal.split.trace }
269269
let goals ← introNewHyp goals [] genNew
270270
return some goals
271271
return goals?

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

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -496,24 +496,37 @@ inductive SplitInfo where
496496
Term `e` may be an inductive predicate, `match`-expression, `if`-expression, implication, etc.
497497
-/
498498
default (e : Expr)
499+
/-- `e` is an implication and we want to split on its antecedent. -/
500+
| imp (e : Expr) (h : e.isForall)
499501
| /--
500502
Given applications `a` and `b`, case-split on whether the corresponding
501503
`i`-th arguments are equal or not. The split is only performed if all other
502504
arguments are already known to be equal or are also tagged as split candidates.
503505
-/
504506
arg (a b : Expr) (i : Nat) (eq : Expr)
505-
deriving BEq, Hashable, Inhabited
507+
deriving Hashable, Inhabited
508+
509+
def SplitInfo.beq : SplitInfo → SplitInfo → Bool
510+
| .default e₁, .default e₂ => e₁ == e₂
511+
| .imp e₁ _, .imp e₂ _ => e₁ == e₂
512+
| .arg a₁ b₁ i₁ eq₁, arg a₂ b₂ i₂ eq₂ => a₁ == a₂ && b₁ == b₂ && i₁ == i₂ && eq₁ == eq₂
513+
| _, _ => false
514+
515+
instance : BEq SplitInfo where
516+
beq := SplitInfo.beq
506517

507518
def SplitInfo.getExpr : SplitInfo → Expr
508-
| .default (.forallE _ d _ _) => d
509519
| .default e => e
520+
| .imp e h => e.forallDomain h
510521
| .arg _ _ _ eq => eq
511522

512523
def SplitInfo.lt : SplitInfo → SplitInfo → Bool
513-
| .default e₁, .default e₂ => e₁.lt e₂
524+
| .default e₁, .default e₂ => e₁.lt e₂
525+
| .imp e₁ _, .imp e₂ _ => e₁.lt e₂
514526
| .arg _ _ _ e₁, .arg _ _ _ e₂ => e₁.lt e₂
515-
| .default _, .arg .. => true
516-
| .arg .., .default _ => false
527+
| .default .., _ => true
528+
| .imp .., _ => true
529+
| _, _ => false
517530

518531
/-- Argument `arg : type` of an application `app` in `SplitInfo`. -/
519532
structure SplitArg where
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
1+
set_option grind.warning false
12
variable {α : Type u} {l : List α} {P Q : α → Bool}
23

34
attribute [grind] List.countP_nil List.countP_cons
45

5-
-- It would be really nice if we didn't need to `specialize` here!
6-
76
theorem List.countP_le_countP (hpq : ∀ x ∈ l, P x → Q x) :
87
l.countP P ≤ l.countP Q := by
98
induction l with
109
| nil => grind
1110
| cons x xs ih =>
12-
specialize ih (by grind)
1311
grind
1412

1513
theorem List.countP_lt_countP (hpq : ∀ x ∈ l, P x → Q x) (y:α) (hx: y ∈ l) (hxP : P y = false) (hxQ : Q y) :
@@ -18,5 +16,4 @@ theorem List.countP_lt_countP (hpq : ∀ x ∈ l, P x → Q x) (y:α) (hx: y ∈
1816
| nil => grind
1917
| cons x xs ih =>
2018
have : xs.countP P ≤ xs.countP Q := countP_le_countP (by grind)
21-
specialize ih (by grind)
2219
grind
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
reset_grind_attrs%
2-
2+
set_option grind.warning false
33
open List
44

55
attribute [grind] List.map_nil
66

77
theorem map_eq_cons_iff {f : α → β} {l : List α} :
88
map f l = b :: l₂ ↔ ∃ a l₁, l = a :: l₁ ∧ f a = b ∧ map f l₁ = l₂ := by
99
cases l
10-
case nil => grind -- kernel error
10+
case nil => grind
1111
case cons a l => sorry

0 commit comments

Comments
 (0)