@@ -122,20 +122,18 @@ private def tryRfl (goal : MVarId) (target pre rhs : Expr) : VCGenM (Option (Lis
122122 goal.assign (mkApp3 refl relArgs[0 ]! relArgs[1 ]! pre)
123123 return some []
124124
125- /-- Strategy 5: close `pre ⊑ rhs` against the newest pure hypothesis in the local context ,
126- which is typically the precondition lifted just before the current spec rule was applied.
127- This is one defeq check against one hypothesis, not an assumption search.
125+ /-- Strategy 5: close `pre ⊑ rhs` against the most recently lifted pure precondition ,
126+ cached in `Scope.lastLiftedPre?`. This is one defeq check against one hypothesis, not an
127+ assumption search.
128128
129129Goals whose RHS contains metavariables are skipped: the defeq check could otherwise assign
130130a post abstraction from an unrelated hypothesis. -/
131- private def tryLiftedHyp (goal : MVarId) (α pre rhs : Expr) : VCGenM (Option (List MVarId)) :=
131+ private def tryLiftedHyp (scope : VCGen.Scope) (goal : MVarId) (α pre rhs : Expr) :
132+ VCGenM (Option (List MVarId)) :=
132133 goal.withContext do
133134 if rhs.hasExprMVar then return none
134- let some hyp ← (← getLCtx).findDeclRevM? (fun decl => do
135- if decl.isImplementationDetail then return none
136- else if ← Meta.isProp decl.type then return some decl
137- else return none)
138- | return none
135+ let some fvarId := scope.lastLiftedPre? | return none
136+ let some hyp := (← getLCtx).find? fvarId | return none
139137 let proof? ← match_expr rhs with
140138 | CompleteLattice.ofProp _l _inst φ => do
141139 if ← isDefEqS φ hyp.type then
@@ -155,25 +153,17 @@ private def tryLiftedHyp (goal : MVarId) (α pre rhs : Expr) : VCGenM (Option (L
155153 goal.assign proof
156154 return some []
157155
158- /--
159- Lift a non-trivial embedded pure precondition `⌜φ⌝` into the local context, leaving `⊤`
160- as the residual precondition. Used before a decomposition step that would duplicate the
161- precondition into several subgoals (a `⊓` or match split), and before applying a spec whose own
162- precondition is `⊤` (whose precondition VC would otherwise discard the fact). Returns `none` when
163- the precondition is not such a `⌜φ⌝`.
164- -/
165- private def liftPurePre? (goal : MVarId) (pre : Expr) : VCGenM (Option MVarId) := do
156+ /-- Strategy 6: lift an embedded pure precondition `⌜φ⌝` into the local context, leaving `⊤`
157+ as the residual precondition. Runs before state-argument introduction, which would otherwise
158+ leave `⌜φ⌝` applied to the introduced arguments. Returns the new goal and the hypothesis. -/
159+ private def tryOfPropPreIntro (goal : MVarId) (pre : Expr) : VCGenM (Option (MVarId × FVarId)) := do
166160 let_expr CompleteLattice.ofProp _l _inst φ := pre | return none
167161 if φ.isTrue then return none
168162 return some (← introPre (ofProp := true ) goal)
169163
170- /-- Strategy 6: decompose a supported lattice connective on the RHS, lifting a pure
171- precondition first when the connective is `⊓` (which duplicates the precondition). -/
172- private def tryLattice (goal : MVarId) (target pre rhs : Expr) (preIsTop : Bool) :
164+ /-- Strategy 8: decompose a supported lattice connective on the RHS. -/
165+ private def tryLattice (goal : MVarId) (target rhs : Expr) (preIsTop : Bool) :
173166 VCGenM (Option (List MVarId)) := do
174- if rhs.isAppOf ``meet then
175- if let some g ← liftPurePre? goal pre then
176- return some [g]
177167 solveLatticeConnective? goal target rhs preIsTop
178168
179169/-- Replace the program in `goal`'s target with `prog` (which must be definitionally equal). -/
@@ -213,16 +203,14 @@ private def tryWPLet (goal : MVarId) (target : Expr) (info : WPInfo) : VCGenM (O
213203 return some (← substOrHoistLet goal target info name type val body nondep
214204 info.prog.getAppRevArgs)
215205
216- /-- Strategy 7b: split an `ite`/`dite`/match program ( or iota-reduce a matcher with a concrete
217- discriminant), lifting a pure precondition first since the split duplicates it into the alts . -/
218- private def tryWPMatch (goal : MVarId) (target pre : Expr) (info : WPInfo) :
206+ /-- Strategy 7b: split an `ite`/`dite`/match program, or iota-reduce a matcher with a concrete
207+ discriminant. -/
208+ private def tryWPMatch (goal : MVarId) (target : Expr) (info : WPInfo) :
219209 VCGenM (Option (List MVarId)) := do
220210 let some splitInfo ← liftMetaM <| Lean.Elab.Tactic.Do.getSplitInfo? info.prog | return none
221211 if splitInfo matches .matcher .. then
222212 if let some prog ← liftMetaM <| withReducible <| reduceRecMatcher? info.prog then
223213 return some [← replaceProgDefEq goal target info (← shareCommonInc prog)]
224- if let some g ← liftPurePre? goal pre then
225- return some [g]
226214 let rule ← mkBackwardRuleForSplitCached splitInfo info
227215 let .goals goals ← rule.applyChecked goal m! "split rule for{ indentExpr info.prog} "
228216 | throwError "Failed to apply split rule for {indentExpr info.prog}"
@@ -255,19 +243,15 @@ private def tryWPHeadReduce (goal : MVarId) (target : Expr) (info : WPInfo) :
255243 return some (← replaceProgDefEq goal target info prog)
256244
257245/-- Strategy 7e: look up a registered `@[spec]` theorem (triple or simp) for the program head
258- and apply its cached backward rule.
259-
260- When the rule has a premise besides the precondition VC that mentions parts of the program,
261- a pure goal precondition is lifted into the local context first. Such premises never see the
262- goal's precondition, so the lift is what keeps the fact available in them. -/
263- private def applySpec (scope : VCGen.Scope) (goal : MVarId) (target pre α : Expr)
264- (preIsTop : Bool) (info : WPInfo) : VCGenM SolveResult := do
246+ and apply its cached backward rule. -/
247+ private def applySpec (scope : VCGen.Scope) (goal : MVarId) (target : Expr) (info : WPInfo) :
248+ VCGenM SolveResult := do
265249 trace[Elab.Tactic.Do.vcgen] "Applying a spec for { info.prog} . Excess args: { info.excessArgs} "
266250 match ← SpecTheorems.findSpecs scope.specs info.prog with
267251 | .error thms => return .noSpecFoundForProgram info.prog info.m thms
268252 | .ok thm =>
269253 trace[Elab.Tactic.Do.vcgen] "Spec for { info.prog} : { thm.proof} "
270- let some ( rule, needsPureLift) ←
254+ let some rule ←
271255 try
272256 mkBackwardRuleFromSpecCached thm info |>.run
273257 catch ex =>
@@ -277,11 +261,6 @@ private def applySpec (scope : VCGen.Scope) (goal : MVarId) (target pre α : Exp
277261 Pred:{indentExpr info.Pred}\n \
278262 excessArgs: {info.excessArgs}"
279263 | return .noSpecFoundForProgram info.prog info.m #[thm]
280- if needsPureLift && !preIsTop then
281- if let some g ← liftPurePre? goal pre then
282- return .goals scope [g]
283- if α.isProp then
284- return .goals scope [← introPre (ofProp := false ) goal]
285264 let .goals goals ← rule.applyChecked goal m! "spec rule for{ indentExpr info.prog} "
286265 | do
287266 let ruleType ← Meta.inferType rule.expr
@@ -314,23 +293,23 @@ The function performs the following steps in order:
3142932. **Target-let handling** : zeta-substitute duplicable top-level lets, otherwise introduce them.
3152943. **Triple unfolding** : If the target is `⦃P⦄ x ⦃Q; E⦄`, unfold into `P ⊑ wp x Q E`.
3162954. **Syntactic rfl** : close `pre ⊑ rhs` by `PartialOrder.rel_refl` when both sides unify.
317- 5. **Lifted-hypothesis discharge** : close `pre ⊑ rhs` with the newest pure hypothesis,
318- typically the precondition lifted before the previous spec application.
319- 6. **State-argument introduction** : If the lattice carrier is a function type
296+ 5. **Lifted-hypothesis discharge** : close `pre ⊑ rhs` against the most recently lifted
297+ precondition, cached in `Scope.lastLiftedPre?`.
298+ 6. **Embedded pure precondition introduction** : lift a `⌜φ⌝` precondition into the local
299+ context, before state-argument introduction would apply it to the introduced arguments.
300+ 7. **State-argument introduction** : If the lattice carrier is a function type
320301 `σ₁ → ... → σₙ → Base`, introduce all excess state arguments.
321- 7. **EPost projection reduction** : reduce an `EPost.Cons.head` RHS to the projected component.
322- 8. **Lattice decomposition** : decompose `⊓`, `⇨`, `⌜p⌝` and `⊤` RHS connectives.
323- 9. **WP decomposition** : when the RHS is `wp e post epost s₁ ... sₙ`, in order:
324- hoist/zeta program-head lets, split `ite`/`dite`/match, zeta-unfold fvar program heads,
325- reduce projection heads, and finally apply a registered `@[spec]` theorem.
326- 10. **Pure precondition introduction** (last resort): introduce a bare pure precondition (on
327- the `Prop` lattice) so the residual VC has the form `⊤ ⊑ φ`.
328-
329- Pure preconditions are deliberately introduced *late* (step 10, and inside steps 8/9 right
330- before a `⊓` or match split duplicates them, or before a spec rule with a post or epost VC
331- loses them): introducing them eagerly would turn reflexivity-closable verification conditions
332- such as `Q x ⊑ wp (pure x) Q E` into goals that need an assumption search. The lifted fact is
333- handed back to the handoff VCs by step 5.
302+ 8. **Bare pure precondition introduction** : on the `Prop` lattice, lift a non-`⊤`
303+ precondition into the local context.
304+ 9. **EPost projection reduction** : reduce an `EPost.Cons.head` RHS to the projected component.
305+ 10. **Lattice decomposition** : decompose `⊓`, `⇨`, `⌜p⌝` and `⊤` RHS connectives.
306+ 11. **WP decomposition** : when the RHS is `wp e post epost s₁ ... sₙ`, in order:
307+ hoist/zeta program-head lets, split `ite`/`dite`/match, zeta-unfold fvar program heads,
308+ reduce projection heads, and finally apply a registered `@[spec]` theorem.
309+
310+ Pure preconditions are lifted as soon as they arise (steps 6 and 8). The lifted hypothesis is
311+ cached in `Scope.lastLiftedPre?`, and step 5 closes the handoff VCs of subsequent spec
312+ applications against it with a single defeq check.
334313-/
335314public def solve (scope : VCGen.Scope) (goal : MVarId) : VCGenM SolveResult := goal.withContext do
336315 if ← outOfFuel then return .stop
@@ -354,19 +333,25 @@ public def solve (scope : VCGen.Scope) (goal : MVarId) : VCGenM SolveResult := g
354333 let preIsTop := pre.isAppOf ``Lean.Order.top && pre.getAppNumArgs == 2
355334
356335 if let some gs ← tryRfl goal target pre rhs then return .goals scope gs
357- if let some gs ← tryLiftedHyp goal α pre rhs then return .goals scope gs
336+ if let some gs ← tryLiftedHyp scope goal α pre rhs then return .goals scope gs
358337
359- -- Phase 2: introduce excess state arguments and reduce EPost projections, so that the RHS
360- -- exposes either a lattice connective or a `wp` application.
338+ -- Phase 2: lift pure preconditions as they arise, introduce excess state arguments, and
339+ -- reduce EPost projections, so that the RHS exposes a lattice connective or a `wp`
340+ -- application over a `⊤` precondition.
341+ if let some (g, h) ← tryOfPropPreIntro goal pre then
342+ return .goals { scope with lastLiftedPre? := some h } [g]
361343 if α.isForall then return .goals scope [← introStateArgs preIsTop goal]
344+ if !preIsTop && α.isProp then
345+ let (g, h) ← introPre (ofProp := false ) goal
346+ return .goals { scope with lastLiftedPre? := some h } [g]
362347 if let some g ← reduceEPostHead? goal target α inst pre rhs then return .goals scope [g]
363348
364349 -- Collect new local specs before any strategy that may emit multiple subgoals
365350 -- (`tryWPMatch`, `tryLattice`) or apply a registered spec (`applySpec`).
366351 let scope ← scope.collectLocalSpecs goal
367352
368353 -- Phase 3: lattice connective decomposition.
369- if let some gs ← tryLattice goal target pre rhs preIsTop then return .goals scope gs
354+ if let some gs ← tryLattice goal target rhs preIsTop then return .goals scope gs
370355
371356 -- Phase 4: wp decomposition. The program-shape steps below all consume one unit of fuel
372357 -- (the `stepLimit` config option) when they make progress.
@@ -375,7 +360,7 @@ public def solve (scope : VCGen.Scope) (goal : MVarId) : VCGenM SolveResult := g
375360 if let some g ← tryWPLet goal target info then
376361 VCGen.burnOne
377362 return .goals scope [g]
378- if let some gs ← tryWPMatch goal target pre info then
363+ if let some gs ← tryWPMatch goal target info then
379364 VCGen.burnOne
380365 return .goals scope gs
381366 if let some g ← tryWPFVarZeta goal target info then
@@ -389,14 +374,9 @@ public def solve (scope : VCGen.Scope) (goal : MVarId) : VCGenM SolveResult := g
389374 let f := info.prog.getAppFn
390375 if f.isConst || f.isFVar then
391376 VCGen.burnOne
392- return ← applySpec scope goal target pre α preIsTop info
377+ return ← applySpec scope goal target info
393378 return .noStrategyForProgram info.prog
394379
395- -- Phase 5 (last resort): introduce a bare pure precondition so the residual VC
396- -- has the form `⊤ ⊑ φ`.
397- if !preIsTop && α.isProp then
398- return .goals scope [← introPre (ofProp := false ) goal]
399-
400380 return .noProgramOrLatticeFoundInTarget rhs
401381
402382end VCGen
0 commit comments