Skip to content

Commit 8d9d814

Browse files
authored
feat: grind simple strategy (#6503)
This PR adds a simple strategy to the (WIP) `grind` tactic. It just keeps internalizing new theorem instances found by E-matching. The simple strategy can solve examples such as: ```lean grind_pattern Array.size_set => Array.set a i v h grind_pattern Array.get_set_eq => a.set i v h grind_pattern Array.get_set_ne => (a.set i v hi)[j] example (as bs : Array α) (v : α) (i : Nat) (h₁ : i < as.size) (h₂ : bs = as.set i v) : as.size = bs.size := by grind example (as bs cs : Array α) (v : α) (i : Nat) (h₁ : i < as.size) (h₂ : bs = as.set i v) (h₃ : cs = bs) (h₄ : i ≠ j) (h₅ : j < cs.size) (h₆ : j < as.size) : cs[j] = as[j] := by grind opaque R : Nat → Nat → Prop theorem Rtrans (a b c : Nat) : R a b → R b c → R a c := sorry grind_pattern Rtrans => R a b, R b c example : R a b → R b c → R c d → R d e → R a d := by grind ```
1 parent a08379c commit 8d9d814

17 files changed

+349
-318
lines changed

src/Lean/Meta/Tactic/Grind.lean

+4-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ prelude
77
import Lean.Meta.Tactic.Grind.Attr
88
import Lean.Meta.Tactic.Grind.RevertAll
99
import Lean.Meta.Tactic.Grind.Types
10-
import Lean.Meta.Tactic.Grind.Preprocessor
1110
import Lean.Meta.Tactic.Grind.Util
1211
import Lean.Meta.Tactic.Grind.Cases
1312
import Lean.Meta.Tactic.Grind.Injection
@@ -22,6 +21,9 @@ import Lean.Meta.Tactic.Grind.Simp
2221
import Lean.Meta.Tactic.Grind.Ctor
2322
import Lean.Meta.Tactic.Grind.Parser
2423
import Lean.Meta.Tactic.Grind.EMatchTheorem
24+
import Lean.Meta.Tactic.Grind.EMatch
25+
import Lean.Meta.Tactic.Grind.Main
26+
2527

2628
namespace Lean
2729

@@ -41,8 +43,8 @@ builtin_initialize registerTraceClass `grind.simp
4143
builtin_initialize registerTraceClass `grind.debug
4244
builtin_initialize registerTraceClass `grind.debug.proofs
4345
builtin_initialize registerTraceClass `grind.debug.congr
44-
builtin_initialize registerTraceClass `grind.debug.pre
4546
builtin_initialize registerTraceClass `grind.debug.proof
4647
builtin_initialize registerTraceClass `grind.debug.proj
48+
builtin_initialize registerTraceClass `grind.debug.parent
4749

4850
end Lean

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ This is an auxiliary function performed while merging equivalence classes.
4141
private def removeParents (root : Expr) : GoalM ParentSet := do
4242
let parents ← getParentsAndReset root
4343
for parent in parents do
44+
trace[grind.debug.parent] "remove: {parent}"
4445
modify fun s => { s with congrTable := s.congrTable.erase { e := parent } }
4546
return parents
4647

@@ -50,6 +51,7 @@ This is an auxiliary function performed while merging equivalence classes.
5051
-/
5152
private def reinsertParents (parents : ParentSet) : GoalM Unit := do
5253
for parent in parents do
54+
trace[grind.debug.parent] "reinsert: {parent}"
5355
addCongrTable parent
5456

5557
/-- Closes the goal when `True` and `False` are in the same equivalence class. -/
@@ -223,10 +225,8 @@ where
223225
internalize rhs generation
224226
addEqCore lhs rhs proof isHEq
225227

226-
/--
227-
Adds a new hypothesis.
228-
-/
229-
def addHyp (fvarId : FVarId) (generation := 0) : GoalM Unit := do
228+
/-- Adds a new hypothesis. -/
229+
def addHypothesis (fvarId : FVarId) (generation := 0) : GoalM Unit := do
230230
add (← fvarId.getType) (mkFVar fvarId) generation
231231

232232
end Lean.Meta.Grind

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

+12-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Authors: Leonardo de Moura
55
-/
66
prelude
77
import Lean.Meta.Tactic.Grind.Types
8-
import Lean.Meta.Tactic.Grind.Internalize
8+
import Lean.Meta.Tactic.Grind.Intro
99

1010
namespace Lean.Meta.Grind
1111
namespace EMatch
@@ -278,4 +278,15 @@ def ematch : GoalM Unit := do
278278
gmt := s.gmt + 1
279279
}
280280

281+
/-- Performs one round of E-matching, and assert new instances. -/
282+
def ematchAndAssert? (goal : Goal) : GrindM (Option (List Goal)) := do
283+
let numInstances := goal.numInstances
284+
let goal ← GoalM.run' goal ematch
285+
if goal.numInstances == numInstances then
286+
return none
287+
assertAll goal
288+
289+
def ematchStar (goal : Goal) : GrindM (List Goal) := do
290+
iterate goal ematchAndAssert?
291+
281292
end Lean.Meta.Grind

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def propagateForallProp (parent : Expr) : GoalM Unit := do
2020
unless (← isEqTrue p) do return ()
2121
let h₁ ← mkEqTrueProof p
2222
let qh₁ := q.instantiate1 (mkApp2 (mkConst ``of_eq_true) p h₁)
23-
let r ← pre qh₁
23+
let r ← simp qh₁
2424
let q := mkLambda n bi p q
2525
let q' := r.expr
2626
internalize q' (← getGeneration parent)

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

+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/-
2+
Copyright (c) 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Leonardo de Moura
5+
-/
6+
prelude
7+
import Init.Grind.Lemmas
8+
import Lean.Meta.Tactic.Assert
9+
import Lean.Meta.Tactic.Grind.Simp
10+
import Lean.Meta.Tactic.Grind.Types
11+
import Lean.Meta.Tactic.Grind.Cases
12+
import Lean.Meta.Tactic.Grind.Injection
13+
import Lean.Meta.Tactic.Grind.Core
14+
15+
namespace Lean.Meta.Grind
16+
17+
private inductive IntroResult where
18+
| done
19+
| newHyp (fvarId : FVarId) (goal : Goal)
20+
| newDepHyp (goal : Goal)
21+
| newLocal (fvarId : FVarId) (goal : Goal)
22+
deriving Inhabited
23+
24+
private def introNext (goal : Goal) : GrindM IntroResult := do
25+
let target ← goal.mvarId.getType
26+
if target.isArrow then
27+
goal.mvarId.withContext do
28+
let p := target.bindingDomain!
29+
if !(← isProp p) then
30+
let (fvarId, mvarId) ← goal.mvarId.intro1P
31+
return .newLocal fvarId { goal with mvarId }
32+
else
33+
let tag ← goal.mvarId.getTag
34+
let q := target.bindingBody!
35+
-- TODO: keep applying simp/eraseIrrelevantMData/canon/shareCommon until no progress
36+
let r ← simp p
37+
let fvarId ← mkFreshFVarId
38+
let lctx := (← getLCtx).mkLocalDecl fvarId target.bindingName! r.expr target.bindingInfo!
39+
let mvarNew ← mkFreshExprMVarAt lctx (← getLocalInstances) q .syntheticOpaque tag
40+
let mvarIdNew := mvarNew.mvarId!
41+
mvarIdNew.withContext do
42+
let h ← mkLambdaFVars #[mkFVar fvarId] mvarNew
43+
match r.proof? with
44+
| some he =>
45+
let hNew := mkAppN (mkConst ``Lean.Grind.intro_with_eq) #[p, r.expr, q, he, h]
46+
goal.mvarId.assign hNew
47+
return .newHyp fvarId { goal with mvarId := mvarIdNew }
48+
| none =>
49+
-- `p` and `p'` are definitionally equal
50+
goal.mvarId.assign h
51+
return .newHyp fvarId { goal with mvarId := mvarIdNew }
52+
else if target.isLet || target.isForall then
53+
let (fvarId, mvarId) ← goal.mvarId.intro1P
54+
mvarId.withContext do
55+
let localDecl ← fvarId.getDecl
56+
if (← isProp localDecl.type) then
57+
-- Add a non-dependent copy
58+
let mvarId ← mvarId.assert (← mkFreshUserName localDecl.userName) localDecl.type (mkFVar fvarId)
59+
return .newDepHyp { goal with mvarId }
60+
else
61+
return .newLocal fvarId { goal with mvarId }
62+
else
63+
return .done
64+
65+
private def isCasesCandidate (fvarId : FVarId) : MetaM Bool := do
66+
let .const declName _ := (← fvarId.getType).getAppFn | return false
67+
isGrindCasesTarget declName
68+
69+
private def applyCases? (goal : Goal) (fvarId : FVarId) : MetaM (Option (List Goal)) := goal.mvarId.withContext do
70+
if (← isCasesCandidate fvarId) then
71+
let mvarIds ← cases goal.mvarId fvarId
72+
return mvarIds.map fun mvarId => { goal with mvarId }
73+
else
74+
return none
75+
76+
private def applyInjection? (goal : Goal) (fvarId : FVarId) : MetaM (Option Goal) := do
77+
if let some mvarId ← injection? goal.mvarId fvarId then
78+
return some { goal with mvarId }
79+
else
80+
return none
81+
82+
/-- Introduce new hypotheses (and apply `by_contra`) until goal is of the form `... ⊢ False` -/
83+
partial def intros (goal : Goal) (generation : Nat) : GrindM (List Goal) := do
84+
let rec go (goal : Goal) : StateRefT (Array Goal) GrindM Unit := do
85+
if goal.inconsistent then
86+
return ()
87+
match (← introNext goal) with
88+
| .done =>
89+
if let some mvarId ← goal.mvarId.byContra? then
90+
go { goal with mvarId }
91+
else
92+
modify fun s => s.push goal
93+
| .newHyp fvarId goal =>
94+
if let some goals ← applyCases? goal fvarId then
95+
goals.forM go
96+
else if let some goal ← applyInjection? goal fvarId then
97+
go goal
98+
else
99+
go (← GoalM.run' goal <| addHypothesis fvarId generation)
100+
| .newDepHyp goal =>
101+
go goal
102+
| .newLocal fvarId goal =>
103+
if let some goals ← applyCases? goal fvarId then
104+
goals.forM go
105+
else
106+
go goal
107+
let (_, goals) ← (go goal).run #[]
108+
return goals.toList
109+
110+
/-- Asserts a new fact `prop` with proof `proof` to the given `goal`. -/
111+
def assertAt (goal : Goal) (proof : Expr) (prop : Expr) (generation : Nat) : GrindM (List Goal) := do
112+
-- TODO: check whether `prop` may benefit from `intros` or not. If not, we should avoid the `assert`+`intros` step and use `Grind.add`
113+
let mvarId ← goal.mvarId.assert (← mkFreshUserName `h) prop proof
114+
let goal := { goal with mvarId }
115+
intros goal generation
116+
117+
/-- Asserts next fact in the `goal` fact queue. -/
118+
def assertNext? (goal : Goal) : GrindM (Option (List Goal)) := do
119+
let some (fact, newFacts) := goal.newFacts.dequeue?
120+
| return none
121+
assertAt { goal with newFacts } fact.proof fact.prop fact.generation
122+
123+
partial def iterate (goal : Goal) (f : Goal → GrindM (Option (List Goal))) : GrindM (List Goal) := do
124+
go [goal] []
125+
where
126+
go (todo : List Goal) (result : List Goal) : GrindM (List Goal) := do
127+
match todo with
128+
| [] => return result
129+
| goal :: todo =>
130+
if let some goalsNew ← f goal then
131+
go (goalsNew ++ todo) result
132+
else
133+
go todo (goal :: result)
134+
135+
/-- Asserts all facts in the `goal` fact queue. -/
136+
partial def assertAll (goal : Goal) : GrindM (List Goal) := do
137+
iterate goal assertNext?
138+
139+
end Lean.Meta.Grind

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

+3
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,7 @@ def checkInvariants (expensive := false) : GoalM Unit := do
9999
if expensive && grind.debug.proofs.get (← getOptions) then
100100
checkProofs
101101

102+
def Goal.checkInvariants (goal : Goal) (expensive := false) : GrindM Unit :=
103+
discard <| GoalM.run' goal <| Grind.checkInvariants expensive
104+
102105
end Lean.Meta.Grind

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

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/-
2+
Copyright (c) 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Leonardo de Moura
5+
-/
6+
prelude
7+
import Init.Grind.Lemmas
8+
import Lean.Meta.Tactic.Util
9+
import Lean.Meta.Tactic.Grind.RevertAll
10+
import Lean.Meta.Tactic.Grind.PropagatorAttr
11+
import Lean.Meta.Tactic.Grind.Proj
12+
import Lean.Meta.Tactic.Grind.ForallProp
13+
import Lean.Meta.Tactic.Grind.Util
14+
import Lean.Meta.Tactic.Grind.Inv
15+
import Lean.Meta.Tactic.Grind.Intro
16+
import Lean.Meta.Tactic.Grind.EMatch
17+
18+
namespace Lean.Meta.Grind
19+
20+
def mkMethods : CoreM Methods := do
21+
let builtinPropagators ← builtinPropagatorsRef.get
22+
return {
23+
propagateUp := fun e => do
24+
propagateForallProp e
25+
let .const declName _ := e.getAppFn | return ()
26+
propagateProjEq e
27+
if let some prop := builtinPropagators.up[declName]? then
28+
prop e
29+
propagateDown := fun e => do
30+
let .const declName _ := e.getAppFn | return ()
31+
if let some prop := builtinPropagators.down[declName]? then
32+
prop e
33+
}
34+
35+
def GrindM.run (x : GrindM α) (mainDeclName : Name) (config : Grind.Config) : MetaM α := do
36+
let scState := ShareCommon.State.mk _
37+
let (falseExpr, scState) := ShareCommon.State.shareCommon scState (mkConst ``False)
38+
let (trueExpr, scState) := ShareCommon.State.shareCommon scState (mkConst ``True)
39+
let thms ← grindNormExt.getTheorems
40+
let simprocs := #[(← grindNormSimprocExt.getSimprocs)]
41+
let simp ← Simp.mkContext
42+
(config := { arith := true })
43+
(simpTheorems := #[thms])
44+
(congrTheorems := (← getSimpCongrTheorems))
45+
x (← mkMethods).toMethodsRef { mainDeclName, config, simprocs, simp } |>.run' { scState, trueExpr, falseExpr }
46+
47+
private def mkGoal (mvarId : MVarId) : GrindM Goal := do
48+
let trueExpr ← getTrueExpr
49+
let falseExpr ← getFalseExpr
50+
let thmMap ← getEMatchTheorems
51+
GoalM.run' { mvarId, thmMap } do
52+
mkENodeCore falseExpr (interpreted := true) (ctor := false) (generation := 0)
53+
mkENodeCore trueExpr (interpreted := true) (ctor := false) (generation := 0)
54+
55+
private def initCore (mvarId : MVarId) : GrindM (List Goal) := do
56+
mvarId.ensureProp
57+
-- TODO: abstract metavars
58+
mvarId.ensureNoMVar
59+
let mvarId ← mvarId.clearAuxDecls
60+
let mvarId ← mvarId.revertAll
61+
let mvarId ← mvarId.unfoldReducible
62+
let mvarId ← mvarId.betaReduce
63+
let goals ← intros (← mkGoal mvarId) (generation := 0)
64+
goals.forM (·.checkInvariants (expensive := true))
65+
return goals.filter fun goal => !goal.inconsistent
66+
67+
def all (goals : List Goal) (f : Goal → GrindM (List Goal)) : GrindM (List Goal) := do
68+
goals.foldlM (init := []) fun acc goal => return acc ++ (← f goal)
69+
70+
/-- A very simple strategy -/
71+
private def simple (goals : List Goal) : GrindM (List Goal) := do
72+
all goals ematchStar
73+
74+
def main (mvarId : MVarId) (config : Grind.Config) (mainDeclName : Name) : MetaM (List MVarId) := do
75+
let go : GrindM (List MVarId) := do
76+
let goals ← initCore mvarId
77+
let goals ← simple goals
78+
trace[grind.debug.final] "{← ppGoals goals}"
79+
return goals.map (·.mvarId)
80+
go.run mainDeclName config
81+
82+
/-- Helper function for debugging purposes -/
83+
def preprocessAndProbe (mvarId : MVarId) (mainDeclName : Name) (p : GoalM Unit) : MetaM Unit :=
84+
let go : GrindM Unit := do
85+
let goals ← initCore mvarId
86+
trace[grind.debug.final] "{← ppGoals goals}"
87+
goals.forM fun goal =>
88+
discard <| GoalM.run' goal p
89+
return ()
90+
withoutModifyingMCtx do
91+
go.run mainDeclName {}
92+
93+
end Lean.Meta.Grind

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

+7
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,11 @@ def ppState : GoalM Format := do
5656
r := r ++ "\n" ++ "{" ++ (Format.joinSep (← eqc.mapM ppENodeRef) ", ") ++ "}"
5757
return r
5858

59+
def ppGoals (goals : List Goal) : GrindM Format := do
60+
let mut r := f!""
61+
for goal in goals do
62+
let (f, _) ← GoalM.run goal ppState
63+
r := r ++ Format.line ++ f
64+
return r
65+
5966
end Lean.Meta.Grind

0 commit comments

Comments
 (0)