Skip to content

Commit dc6864b

Browse files
sgraf812volodeyka
andcommitted
fix: lift pure preconditions only when a premise verifies program parts without them
The goal's precondition reaches exactly one premise of a constructed spec rule: the precondition VC. The solver now lifts a pure goal precondition only when some other premise mentions parts of the program, that is, when program verification continues somewhere the precondition cannot reach. This covers the loop specifications (their step and exit premises mention the loop body) and specs with hypotheses about program arguments, while chains of specs whose program occurs only in the precondition VC keep their reflexivity handoff. The PurePrecond benchmark closes under plain mvcgen' again at its previous cost. Co-authored-by: volodeyka <vovaglad00@gmail.com>
1 parent 1749345 commit dc6864b

5 files changed

Lines changed: 32 additions & 23 deletions

File tree

src/Lean/Elab/Tactic/Do/Internal/VCGen/Context.lean

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,10 @@ public structure VCGen.Scope where
143143
public structure VCGen.State where
144144
/--
145145
A cache mapping registered SpecThms to their backward rule to apply, paired with whether
146-
the rule has a post or epost VC (in which case the caller lifts a pure goal precondition
147-
before applying the rule). The particular rule depends on the theorem name, the `WPMonad`
148-
instance and the number of excess state arguments that the weakest precondition target is
149-
applied to.
146+
the rule has a premise besides the precondition VC that mentions parts of the program (in
147+
which case the caller lifts a pure goal precondition before applying the rule). The
148+
particular rule depends on the theorem name, the `WPMonad` instance and the number of
149+
excess state arguments that the weakest precondition target is applied to.
150150
-/
151151
specBackwardRuleCache : Std.HashMap (Name × Expr × Nat) (BackwardRule × Bool) := {}
152152
/--

src/Lean/Elab/Tactic/Do/Internal/VCGen/RuleConstruction.lean

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -378,11 +378,13 @@ private def mkSpecBackwardProof
378378
let mut postAbstract := postSpec.consumeMData
379379
let mut epostAbstract := epostSpec.consumeMData
380380
let mut specApplied := specProof
381-
let mut hasContinuationVC := false
381+
let progMVars ← getMVars prog
382+
let mut premiseHasProg := false
382383

383384
/- abstract concrete `post` if it is not already abstract -/
384385
unless postAbstract.isMVar do
385-
hasContinuationVC := true
386+
unless premiseHasProg do
387+
premiseHasProg := (← getMVars postSpec).any progMVars.contains
386388
/- `α → Pred`: type of `post` -/
387389
let postTy ← Sym.inferType postSpec
388390
/- mvar `postAbstract` for new abstract `post` -/
@@ -421,7 +423,8 @@ private def mkSpecBackwardProof
421423
This proof DOES NOT have a `?epostImpl` premise -/
422424
specApplied ← mkAppM ``WPMonad.wp_econs_bot_le #[prog, postAbstract, epostAbstract, specApplied]
423425
else
424-
hasContinuationVC := true
426+
unless premiseHasProg do
427+
premiseHasProg := (← getMVars epostSpec).any progMVars.contains
425428
/- Decompose `epostSpec ⊑ epostAbstract` into per-component proofs
426429
using `EPost.Cons.mk_le` and `EPost.Nil.le` -/
427430
let hepost ← decomposeEPostRel EPred epostSpec epostAbstract stateArgNames
@@ -450,7 +453,7 @@ private def mkSpecBackwardProof
450453
/- use `PartialOrder.rel_trans` to compose the abstracted `pre` and the proof of the original theorem -/
451454
specApplied ← mkAppM ``PartialOrder.rel_trans #[specAbstract, specApplied]
452455

453-
return (← abstractMVars specApplied, hasContinuationVC)
456+
return (← abstractMVars specApplied, premiseHasProg)
454457

455458
/--
456459
Try to build a backward rule from a single spec theorem in `⊑` form.
@@ -466,7 +469,7 @@ Given a spec `pre ⊑ wp prog post epost` where the lattice type is
466469
public def tryMkBackwardRuleFromSpec (specThm : SpecTheorem) (info : WPInfo)
467470
(stateArgNames : Array Name := #[]) : OptionT SymM (BackwardRule × Bool) := do
468471
-- Instantiate the spec theorem, creating metavars for all universally quantified params
469-
let (_xs, _bs, specProof, specType) ← specThm.instantiate
472+
let (xs, _bs, specProof, specType) ← specThm.instantiate
470473
let_expr PartialOrder.rel Pred' _cl' pre rhs := specType
471474
| throwError "target not a partial order ⊑ application {specType}"
472475
guard <| ← isDefEqGuarded info.Pred Pred'
@@ -481,12 +484,20 @@ public def tryMkBackwardRuleFromSpec (specThm : SpecTheorem) (info : WPInfo)
481484
let ty ← Sym.inferType info.excessArgs[i]
482485
ssTypes := ssTypes.push ty
483486
ss := ss.push <| ← mkFreshExprMVar (userName := stateArgNames[i]?.getD `s) ty
484-
let (res, hasContinuationVC) ← mkSpecBackwardProof pre prog postSpec epostSpec specProof info.EPred ss ssTypes stateArgNames
487+
let (res, postVCHasProg) ← mkSpecBackwardProof pre prog postSpec epostSpec specProof info.EPred ss ssTypes stateArgNames
485488
let rule ← mkBackwardRuleFromExpr res.expr res.paramNames.toList
486-
-- The post and epost VCs never see the goal's precondition. When the rule has one, the
487-
-- caller lifts a pure goal precondition into the local context first, so its fact stays
488-
-- available there.
489-
return (rule, hasContinuationVC)
489+
-- The goal's precondition reaches exactly one premise: the precondition VC. Every other
490+
-- premise that mentions parts of the program verifies program fragments without it. When
491+
-- such a premise exists, the caller lifts a pure goal precondition into the local context
492+
-- first, so its fact stays available everywhere.
493+
let progMVars ← getMVars prog
494+
let mut hypHasProg := false
495+
for x in xs do
496+
if x.isMVar && !hypHasProg then
497+
let xTy ← Meta.inferType x
498+
if ← Meta.isProp xTy then
499+
hypHasProg := (← getMVars xTy).any progMVars.contains
500+
return (rule, hypHasProg || postVCHasProg)
490501

491502
/-! ## Equality spec rules
492503

src/Lean/Elab/Tactic/Do/Internal/VCGen/Solve.lean

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -224,17 +224,17 @@ private def tryWPHeadReduce (goal : MVarId) (target : Expr) (info : WPInfo) :
224224
/-- Strategy 7e: look up a registered `@[spec]` theorem (triple or simp) for the program head
225225
and apply its cached backward rule.
226226
227-
When the rule has a post or epost VC, a pure goal precondition is lifted into the local
228-
context first. Those VCs never see the goal's precondition, so the lift is what keeps the
229-
fact available in them. -/
227+
When the rule has a premise besides the precondition VC that mentions parts of the program,
228+
a pure goal precondition is lifted into the local context first. Such premises never see the
229+
goal's precondition, so the lift is what keeps the fact available in them. -/
230230
private def applySpec (scope : VCGen.Scope) (goal : MVarId) (target pre α : Expr)
231231
(preIsTop : Bool) (info : WPInfo) : VCGenM SolveResult := do
232232
trace[Elab.Tactic.Do.vcgen] "Applying a spec for {info.prog}. Excess args: {info.excessArgs}"
233233
match ← SpecTheorems.findSpecs scope.specs info.prog with
234234
| .error thms => return .noSpecFoundForProgram info.prog info.m thms
235235
| .ok thm =>
236236
trace[Elab.Tactic.Do.vcgen] "Spec for {info.prog}: {thm.proof}"
237-
let some (rule, hasContinuationVC) ←
237+
let some (rule, needsPureLift) ←
238238
try
239239
mkBackwardRuleFromSpecCached thm info |>.run
240240
catch ex =>
@@ -244,7 +244,7 @@ private def applySpec (scope : VCGen.Scope) (goal : MVarId) (target pre α : Exp
244244
Pred:{indentExpr info.Pred}\n\
245245
excessArgs: {info.excessArgs}"
246246
| return .noSpecFoundForProgram info.prog info.m #[thm]
247-
if hasContinuationVC && !preIsTop then
247+
if needsPureLift && !preIsTop then
248248
if let some g ← liftPurePre? goal pre then
249249
return .goals scope [g]
250250
if α.isProp then

src/Std/Internal/Do/Order/Basic.lean

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,6 @@ instance : PartialOrder Prop where
244244
rel_trans := fun h1 h2 x => h2 (h1 x)
245245
rel_antisymm := fun h1 h2 => propext ⟨h1, h2⟩
246246

247-
/-- `⊑` on the `Prop` lattice is implication. Tagged `@[grind =]` so `grind` can decompose
248-
entailments at `Prop` in partially decomposed verification conditions. -/
249247
@[grind =, simp] theorem le_prop_eq_imp (p q : Prop) : (p ⊑ q) = (p → q) := rfl
250248

251249
/-- Supremum for Prop: true iff some element of the set is true -/

tests/bench/mvcgen/sym/vcgen_pure_precond.lean

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ set_option maxHeartbeats 10000000
1616

1717
example : Goal 10 := by
1818
simp only [Goal, loop, step]
19-
mvcgen' with finish
19+
mvcgen'
2020

21-
#eval runBenchUsingTactic ``Goal [``loop, ``step] `(tactic| mvcgen' with finish) `(tactic| fail)
21+
#eval runBenchUsingTactic ``Goal [``loop, ``step] `(tactic| mvcgen') `(tactic| fail)
2222
[100, 400, 700]

0 commit comments

Comments
 (0)