Skip to content

Commit 6118662

Browse files
authored
feat: grind -revert (#11248)
This PR implements the option `revert`, which is set to `false` by default. To recover the old `grind` behavior, you should use `grind +revert`. Previously, `grind` used the `RevSimpIntro` idiom, i.e., it would revert all hypotheses and then re-introduce them while simplifying and applying eager `cases`. This idiom created several problems: * Users reported that `grind` would include unnecessary parameters. See [here](https://leanprover.zulipchat.com/#narrow/channel/270676-lean4/topic/Grind.20aggressively.20includes.20local.20hypotheses.2E/near/554887715). * Unnecessary section variables were also being introduced. See the new test contributed by Sebastian Graf. * Finally, it prevented us from supporting arbitrary parameters as we do in `simp`. In `simp`, I implemented a mechanism that simulates local universe-polymorphic theorems, but this approach could not be used in `grind` because there is no mechanism for reverting (and re-introducing) local universe-polymorphic theorems. Adding such a mechanism would require substantial work: I would need to modify the local context object. I considered maintaining a substitution from the original variables to the new ones, but this is also tricky, because the mapping would have to be stored in the `grind` goal objects, and it is not just a simple mapping. After reverting everything, I would need to keep a sequence of original variables that must be added to the mapping as we re-introduce them, but eager case splits complicate this quite a bit. The whole approach felt overly messy. The new behavior `grind -revert` addresses all these issues. None of the `grind` proofs in our test suite broke after we fixed the bugs exposed by the new feature. That said, the traces and counterexamples produced by `grind` are different. The new proof terms are also different.
1 parent d5ecca9 commit 6118662

24 files changed

+249
-103
lines changed

src/Init/Grind/Config.lean

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,11 @@ structure Config where
167167
When `trace := true`, uses `sorry` to close unsolved branches.
168168
-/
169169
useSorry := true
170+
/--
171+
When `true`, it reverts all hypotheses during the preprocessing step,
172+
and then reintroduces them while simplifying and applying eager `cases`.
173+
-/
174+
revert := false
170175
deriving Inhabited, BEq
171176

172177
/--

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

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,25 @@ import Lean.Meta.Tactic.Grind.Simp
1010
public section
1111
namespace Lean.Meta.Grind
1212

13-
private partial def propagateInjEqs (eqs : Expr) (proof : Expr) : GoalM Unit := do
13+
private partial def propagateInjEqs (eqs : Expr) (proof : Expr) (generation : Nat) : GoalM Unit := do
1414
-- Remark: we must use `shareCommon` before using `pushEq` and `pushHEq`.
1515
-- This is needed because the result type of the injection theorem may allocate
1616
match_expr eqs with
1717
| And left right =>
18-
propagateInjEqs left (.proj ``And 0 proof)
19-
propagateInjEqs right (.proj ``And 1 proof)
18+
propagateInjEqs left (.proj ``And 0 proof) generation
19+
propagateInjEqs right (.proj ``And 1 proof) generation
2020
| Eq _ lhs rhs =>
21-
pushEq (← preprocessLight lhs) (← preprocessLight rhs) proof
21+
let lhs ← preprocessLight lhs
22+
let rhs ← preprocessLight rhs
23+
internalize lhs generation
24+
internalize rhs generation
25+
pushEq lhs rhs proof
2226
| HEq _ lhs _ rhs =>
23-
pushHEq (← preprocessLight lhs) (← preprocessLight rhs) proof
27+
let lhs ← preprocessLight lhs
28+
let rhs ← preprocessLight rhs
29+
internalize lhs generation
30+
internalize rhs generation
31+
pushHEq lhs rhs proof
2432
| _ =>
2533
reportIssue! "unexpected injectivity theorem result type{indentExpr eqs}"
2634
return ()
@@ -51,7 +59,8 @@ def propagateCtor (a b : Expr) : GoalM Unit := do
5159
let mask := mask.set! (n-1) (some (← mkExpectedTypeHint (← mkEqProof a b) (← mkEq a b)))
5260
let injLemma ← mkAppOptM injDeclName mask
5361
let injLemmaType ← inferType injLemma
54-
propagateInjEqs injLemmaType injLemma
62+
let gen := max (← getGeneration a) (← getGeneration b)
63+
propagateInjEqs injLemmaType injLemma gen
5564
else
5665
let .const declName _ := aType.getAppFn | return ()
5766
let noConfusionDeclName := Name.mkStr declName "noConfusion"

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Similar to `Grind.preprocess`, but does not simplify `e` if
3838
We added this feature because it may be coming from external sources
3939
(e.g., manually applying an function induction principle before invoking `grind`).
4040
-/
41-
private def preprocessHypothesis (e : Expr) : GoalM Simp.Result := do
41+
def preprocessHypothesis (e : Expr) : GoalM Simp.Result := do
4242
if isMatchCondCandidate e then
4343
preprocess (markAsPreMatchCond e)
4444
else if let some c := isAlreadyNorm? e then

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

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import Lean.Meta.Tactic.Grind.SimpUtil
2626
import Lean.Meta.Tactic.Grind.LawfulEqCmp
2727
import Lean.Meta.Tactic.Grind.ReflCmp
2828
import Lean.Meta.Tactic.Grind.PP
29+
import Lean.Meta.Tactic.Grind.Simp
30+
import Lean.Meta.Tactic.Grind.Core
2931
public section
3032
namespace Lean.Meta.Grind
3133

@@ -199,19 +201,48 @@ def Result.toMessageData (result : Result) : MetaM MessageData := do
199201
msgs := msgs ++ [msg]
200202
return MessageData.joinSep msgs m!"\n"
201203

204+
/--
205+
When `Config.revert := false`, we preprocess the hypotheses, and add them to the `grind` state.
206+
-/
207+
private def addHypotheses (goal : Goal) : GrindM Goal := GoalM.run' goal do
208+
let mvarDecl ← goal.mvarId.getDecl
209+
for localDecl in mvarDecl.lctx do
210+
if (← isInconsistent) then return ()
211+
let type := localDecl.type
212+
if (← isProp type) then
213+
let r ← preprocessHypothesis type
214+
match r.proof? with
215+
| none => add r.expr localDecl.toExpr
216+
| some h => add r.expr <| mkApp4 (mkConst ``Eq.mp [0]) type r.expr h localDecl.toExpr
217+
else
218+
/-
219+
**Note**: We must internalize local variables because their types may be empty, and may not be
220+
referenced anywhere else. Example:
221+
```
222+
example (a : { x : Int // x < 0 ∧ x > 0 }) : False := by grind
223+
```
224+
-/
225+
let e ← shareCommon localDecl.toExpr
226+
internalize e 0
227+
processNewFacts
228+
202229
private def initCore (mvarId : MVarId) (params : Params) : GrindM Goal := do
203230
/-
204231
**Note**: We used to use `abstractMVars` and `clearImpDetails` here.
205232
These operations are now performed at `withProtectedMCtx`
206233
-/
207234
-- let mvarId ← mvarId.abstractMVars
208235
-- let mvarId ← mvarId.clearImplDetails
209-
let mvarId ← if params.config.clean then pure mvarId else mvarId.markAccessible
210-
let mvarId ← mvarId.revertAll
236+
let mvarId ← if params.config.clean || !params.config.revert then pure mvarId else mvarId.markAccessible
237+
let mvarId ← if params.config.revert then mvarId.revertAll else pure mvarId
211238
let mvarId ← mvarId.unfoldReducible
212239
let mvarId ← mvarId.betaReduce
213240
appendTagSuffix mvarId `grind
214-
mkGoal mvarId params
241+
let goal ← mkGoal mvarId params
242+
if params.config.revert then
243+
return goal
244+
else
245+
addHypotheses goal
215246

216247
def mkResult (params : Params) (failure? : Option Goal) : GrindM Result := do
217248
let issues := (← get).issues

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ A lighter version of `preprocess` which produces a definitionally equal term,
9595
but ensures assumptions made by `grind` are satisfied.
9696
-/
9797
def preprocessLight (e : Expr) : GoalM Expr := do
98+
let e ← instantiateMVars e
9899
shareCommon (← canon (← normalizeLevels (← foldProjs (← eraseIrrelevantMData (← markNestedSubsingletons (← unfoldReducible e))))))
99100

100101
end Lean.Meta.Grind

tests/lean/run/grind_11081.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ open List
2727
error: `grind` failed
2828
case grind.1.1.1.1.1.1.1.1.1
2929
α : Type
30-
inst : (a b : α) → Decidable (a = b)
30+
inst : DecidableEq α
3131
l₁ l₂ : List α
3232
hl : l₂.Subperm l₁
3333
p : α → Bool

tests/lean/run/grind_11124.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ error: `grind` failed
113113
case grind.1.1.2.2.1.1.1
114114
α : Type
115115
β : α → Type
116-
inst : (a b : α) → Decidable (a = b)
116+
inst : DecidableEq α
117117
s₁ s₂ : AList β
118118
h : s₁.Disjoint s₂
119119
x : α

tests/lean/run/grind_11134.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ inst_1 : One α
7272
inst_2 : Zero α
7373
x y : α
7474
h : DvdNotUnit x y
75-
hx0 : ¬x = 0
75+
hx0 : x ≠ 0
7676
d : α
7777
hdu : ¬IsUnit d
7878
hdx : y = x * d

tests/lean/run/grind_const_pattern.lean

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,9 @@ h : ¬f x = 11
7676
[ematch] E-matching patterns
7777
[thm] fa: [f `[a]]
7878
[cutsat] Assignment satisfying linear constraints
79-
[assign] x := 3
80-
[assign] a := 2
81-
[assign] f x := 1
79+
[assign] x := 1
80+
[assign] a := 3
81+
[assign] f x := 2
8282
-/
8383
#guard_msgs (error) in
8484
example : f x = 10 + 1 := by

tests/lean/run/grind_cutsat_le_2.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ trace: [grind.lia.model] a := 7
1515
[grind.lia.model] b := 0
1616
[grind.lia.model] c := 3
1717
[grind.lia.model] d := 2
18+
[grind.lia.model] e := 4
1819
-/
1920
#guard_msgs (trace) in
2021
example (a b c d e : Int) :

0 commit comments

Comments
 (0)