Skip to content

Commit 77da26d

Browse files
committed
refactor: port Grind/Intro.lean to SearchM
1 parent 81467b8 commit 77da26d

File tree

2 files changed

+142
-6
lines changed

2 files changed

+142
-6
lines changed

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

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import Lean.Meta.Tactic.Grind.Cases
1212
import Lean.Meta.Tactic.Grind.CasesMatch
1313
import Lean.Meta.Tactic.Grind.Injection
1414
import Lean.Meta.Tactic.Grind.Core
15-
import Lean.Meta.Tactic.Grind.Combinators
15+
import Lean.Meta.Tactic.Grind.SearchM
16+
import Lean.Meta.Tactic.Grind.Combinators -- TODO: delete
1617

1718
namespace Lean.Meta.Grind
1819

@@ -196,6 +197,96 @@ private def exfalsoIfNotProp (goal : Goal) : MetaM Goal := goal.mvarId.withConte
196197
else
197198
return { goal with mvarId := (← goal.mvarId.exfalso) }
198199

200+
private def applyCases? (fvarId : FVarId) : SearchM Bool := withCurrGoalContext do
201+
/-
202+
Remark: we used to use `whnfD`. This was a mistake, we don't want to unfold user-defined abstractions.
203+
Example: `a ∣ b` is defined as `∃ x, b = a * x`
204+
-/
205+
let type ← whnf (← fvarId.getType)
206+
if isEagerCasesCandidate (← getGoal) type then
207+
if (← cheapCasesOnly) then
208+
unless (← isCheapInductive type) do
209+
return false
210+
if let .const declName _ := type.getAppFn then
211+
saveCases declName true
212+
let mvarId ← mkAuxMVarForCurrGoal
213+
let mvarIds ← cases mvarId (mkFVar fvarId)
214+
let goal ← getGoal
215+
let goals := mvarIds.map fun mvarId => { goal with mvarId }
216+
mkChoice (mkMVar mvarId) goals
217+
return true
218+
return false
219+
220+
/--
221+
Introduce new hypotheses (and apply `by_contra`) until goal is of the form `... ⊢ False`
222+
or is inconsistent.
223+
-/
224+
def intros (generation : Nat) : SearchM Unit := do
225+
repeat
226+
if (← isInconsistent) then
227+
return ()
228+
match (← introNext (← getGoal) generation) with
229+
| .done goal =>
230+
let goal ← exfalsoIfNotProp goal
231+
if let some mvarId ← goal.mvarId.byContra? then
232+
setGoal { goal with mvarId }
233+
else
234+
setGoal goal
235+
return ()
236+
| .newDepHyp goal =>
237+
setGoal goal
238+
| .newLocal fvarId goal =>
239+
setGoal goal
240+
discard <| applyCases? fvarId
241+
| .newHyp fvarId goal =>
242+
if let some goal ← applyInjection? goal fvarId then
243+
setGoal goal
244+
else
245+
setGoal goal
246+
withCurrGoalContext do
247+
unless (← applyCases? fvarId) do
248+
addHypothesis fvarId generation
249+
250+
/-- Asserts a new fact `prop` with proof `proof` to the given `goal`. -/
251+
def assertAt (proof : Expr) (prop : Expr) (generation : Nat) : SearchM Unit := do
252+
if isEagerCasesCandidate (← getGoal) prop then
253+
let goal ← getGoal
254+
let mvarId ← goal.mvarId.assert (← mkFreshUserName `h) prop proof
255+
setGoal { goal with mvarId }
256+
intros generation
257+
else withCurrGoalContext do
258+
let r ← preprocess prop
259+
let prop' := r.expr
260+
let proof' := mkApp4 (mkConst ``Eq.mp [levelZero]) prop r.expr (← r.getProof) proof
261+
add prop' proof' generation
262+
263+
/--
264+
Asserts next fact in the `goal` fact queue.
265+
Returns `true` if the queue was not empty and `false` otherwise.
266+
-/
267+
def assertNext : SearchM Bool := do
268+
let goal ← getGoal
269+
let some (fact, newRawFacts) := goal.newRawFacts.dequeue?
270+
| return false
271+
setGoal { goal with newRawFacts }
272+
assertAt fact.proof fact.prop fact.generation
273+
return true
274+
275+
/--
276+
Asserts all facts in the `goal` fact queue.
277+
Returns `true` if the queue was not empty and `false` otherwise.
278+
-/
279+
def assertAll : SearchM Bool := do
280+
let mut progress := false
281+
repeat
282+
if (← assertNext) then
283+
progress := true
284+
else
285+
return progress
286+
unreachable!
287+
288+
/-! All functions after this point will be deleted after we move to `SearchM` -/
289+
199290
private def applyCasesOld? (goal : Goal) (fvarId : FVarId) : GrindM (Option (List Goal)) := goal.mvarId.withContext do
200291
/-
201292
Remark: we used to use `whnfD`. This was a mistake, we don't want to unfold user-defined abstractions.

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

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ structure Choice where
2020
/--
2121
Expression to be assigned to `goalOld.mvarId` if it is not possible to perform
2222
non-chronological backtracking.
23-
`val` is often a `casesOn` application.
23+
`proof` is often a `casesOn` application containing meta-variables.
2424
-/
25-
val : Expr
25+
proof : Expr
2626
/--
2727
Subgoals that still need to be processed.
2828
-/
@@ -35,6 +35,15 @@ structure SearchM.State where
3535

3636
abbrev SearchM := StateRefT SearchM.State GrindM
3737

38+
def getGoal : SearchM Goal :=
39+
return (← get).goal
40+
41+
def setGoal (goal : Goal) : SearchM Unit :=
42+
modify fun s => { s with goal }
43+
44+
abbrev withCurrGoalContext (x : SearchM α) : SearchM α := do
45+
(← getGoal).mvarId.withContext x
46+
3847
abbrev liftGoalM (x : GoalM α) : SearchM α := do
3948
let (a, goal) ← x.runCore (← get).goal
4049
modify fun s => { s with goal }
@@ -46,6 +55,42 @@ instance : MonadLift GoalM SearchM where
4655
@[inline] def SearchM.run (goal : Goal) (x : SearchM α) : GrindM (α × SearchM.State) :=
4756
goal.mvarId.withContext do StateRefT'.run x { goal }
4857

58+
/--
59+
Given a proof containing meta-variables corresponding to the given subgoals,
60+
create a choice point.
61+
- If there are no choice points, we just close the current goal using `proof`.
62+
- If there is only one subgoal `s`, we close the current goal using `proof`, and
63+
update current goal using `s`.
64+
- If there are more than one `s :: ss`, we create a choice point using the current
65+
goal as the pending goal, and update the current goal with `s`.
66+
-/
67+
def mkChoice (proof : Expr) (subgoals : List Goal) : SearchM Unit := do
68+
assert! !(← isInconsistent)
69+
match subgoals with
70+
| [] =>
71+
(← getGoal).mvarId.assign proof
72+
| [subgoal] =>
73+
(← getGoal).mvarId.assign proof
74+
setGoal subgoal
75+
| subgoal :: subgoals =>
76+
let goalPending ← getGoal
77+
modify fun s => { s with
78+
goal := subgoal
79+
choiceStack := { goalPending, proof, todo := subgoals } :: s.choiceStack
80+
}
81+
82+
/--
83+
Create an auxiliary metavariable with the same type and tag of the metavariable
84+
associated with the current goal.
85+
We use this function to perform `cases` on the current goal without eagerly assignining it.
86+
-/
87+
def mkAuxMVarForCurrGoal : SearchM MVarId := withCurrGoalContext do
88+
let mvarId := (← getGoal).mvarId
89+
let tag ← mvarId.getTag
90+
let type ← mvarId.getType
91+
let mvarNew ← mkFreshExprSyntheticOpaqueMVar type tag
92+
return mvarNew.mvarId!
93+
4994
private def findMaxFVarIdx? (e : Expr) : MetaM (Option Nat) := do
5095
let go (e : Expr) : StateT (Option Nat) MetaM Bool := do
5196
unless e.hasFVar do return false
@@ -90,7 +135,7 @@ private def nextChronoGoal : SearchM Bool := do
90135
| [] =>
91136
-- Choice point has been fully resolved.
92137
-- Go to next one.
93-
choice.goalPending.mvarId.assign choice.val
138+
choice.goalPending.mvarId.assign choice.proof
94139
choices := choices'
95140
| goal :: todo =>
96141
let choice := { choice with todo }
@@ -144,8 +189,8 @@ def nextGoal : SearchM Bool := do
144189
choices := choices'
145190
else match choice.todo with
146191
| [] =>
147-
-- All subgoals have been solved. We can finally assign `choice.val` to `goalOld.mvarId`.
148-
let proof ← instantiateMVars choice.val
192+
-- All subgoals have been solved. We can finally assign `choice.proof` to `goalOld.mvarId`.
193+
let proof ← instantiateMVars choice.proof
149194
choice.goalPending.mvarId.assign proof
150195
if (← isTargetFalse choice.goalPending.mvarId) then
151196
-- `proof` is a proof of `False`, we can continue using non-chronological backtracking

0 commit comments

Comments
 (0)