Skip to content

Commit a391359

Browse files
committed
refactor: frame the lattice meet through the general gadget
Drop the meet-specific `meet_wp_imp_le_wp_skipFrame`; `vcgen` now frames every operator, the lattice meet included, through the single gadget `op_wp_upperAdjoint_le_wp_skipFrame` (renamed from `wp_imp_le_wp_skipFrame`, built on `WP.Frames.op_wp_upperAdjoint_le_wp`). The meet residual `upperAdjoint (meet F) b` is folded to `F ⇨ b` (definitionally equal: `himp` is the meet upper adjoint) by `foldUpperAdjointMeet?`, exposing the meet operand so the `himp` split decomposes it. `applyFrame` no longer branches on the operator.
1 parent 2feba90 commit a391359

4 files changed

Lines changed: 31 additions & 30 deletions

File tree

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

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -447,14 +447,11 @@ private def applyFrame (scope : VCGen.Scope) (goal : MVarId) (pre : Expr) (info
447447
| return .notFramed goal info
448448
-- `info.args.take 7` are the program's own `wp` arguments (program type, value, assertions, `WP`
449449
-- instance); `mkAppOptM` synthesizes the remaining instances against the assertion's own
450-
-- `CompleteLattice` so the framing shares the structure the program's `wp` uses.
450+
-- `CompleteLattice` so the framing shares the structure the program's `wp` uses. The lattice meet
451+
-- is just the instance `op := (· ⊓ ·)`; its residual folds to `⇨` (see `foldUpperAdjointMeet?`).
451452
let specProof ←
452-
if op.eta.getAppFn.isConstOf ``Lean.Order.meet then
453-
Meta.mkAppOptM ``Std.Internal.Do.Gadget.meet_wp_imp_le_wp_skipFrame
454-
((info.args.take 7).map some ++ #[none, some F])
455-
else
456-
Meta.mkAppOptM ``Std.Internal.Do.Gadget.wp_imp_le_wp_skipFrame
457-
((info.args.take 7).map some ++ #[none, some op, none, some F])
453+
Meta.mkAppOptM ``Std.Internal.Do.Gadget.op_wp_upperAdjoint_le_wp_skipFrame
454+
((info.args.take 7).map some ++ #[none, some op, none, some F])
458455
let some specThm ← mkSpecTheoremFromStx (← getRef) specProof
459456
| throwError "frame: could not build spec from the frame gadget for{indentExpr info.prog}"
460457
let some rule ← (tryMkBackwardRuleFromSpec specThm info).run
@@ -463,6 +460,21 @@ private def applyFrame (scope : VCGen.Scope) (goal : MVarId) (pre : Expr) (info
463460
| throwError "frame: failed to apply rule for{indentExpr info.prog}"
464461
return .framed scope subgoals
465462

463+
/-- Fold a frame residual `upperAdjoint (meet F) b` on the RHS to Heyting `F ⇨ b` (definitionally
464+
equal: `himp` *is* the meet upper adjoint), exposing the meet operand `F` so the `himp` split can
465+
decompose it. This lets the lattice meet be framed by the general gadget like any operator, with no
466+
meet-specific spec; a non-meet operator's residual is left for its registered `impSplit`. -/
467+
private def foldUpperAdjointMeet? (goal : MVarId) (target rhs : Expr) : VCGenM (Option MVarId) := do
468+
unless rhs.isAppOf ``Lean.Order.PreservesSup.upperAdjoint do return none
469+
let args := rhs.getAppArgs
470+
let some slice := args[2]? | return none
471+
unless slice.isAppOf ``Lean.Order.meet && slice.getAppNumArgs == 3 do return none
472+
let himpExpr ← Meta.mkAppM ``Lean.Order.himp #[slice.appArg!, args[3]!]
473+
let newRhs ← mkAppNS himpExpr (args.extract 4 args.size)
474+
let relArgs := target.getAppArgs
475+
let newTarget ← mkAppNS target.getAppFn (relArgs.set! (relArgs.size - 1) newRhs)
476+
return some (← goal.replaceTargetDefEq newTarget)
477+
466478
/--
467479
The main VC generation step. Operates on a plain `MVarId` with no knowledge of grind.
468480
Returns `.goals subgoals` when the goal was decomposed, or a classification result
@@ -522,6 +534,7 @@ public def solve (scope : VCGen.Scope) (goal : MVarId) : VCGenM SolveResult := g
522534
-- Phase 3: shape the `rhs` (reduce an EPost projection, decompose a lattice connective), then
523535
-- discharge a residual entailment against the lifted hypothesis.
524536
if let some g ← reduceEPostHead? goal target α inst pre rhs then return .goals scope [g]
537+
if let some g ← foldUpperAdjointMeet? goal target rhs then return .goals scope [g]
525538
if let some gs ← splitLatticeOp? goal rhs then return .goals scope gs
526539
if let some gs ← liftedHyp? scope goal α pre rhs then return .goals scope gs
527540

src/Std/Internal/Do/WP/Frame.lean

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ structure WP.Frames {R : Type t} (op : R → Pred → Pred) (x : Prog) (F : R) :
3535

3636
/-- The framed spec `vcgen` applies for `x`, when each `op r` preserves `Sup`: framing `x` by `F`
3737
makes `op F (wp x (fun a => PreservesSup.upperAdjoint (op F) (Q a)))` a precondition for `wp x Q`. -/
38-
theorem WP.Frames.conj_wp_imp_le_wp {R : Type t} (op : R → Pred → Pred) [∀ r, PreservesSup (op r)]
39-
{x : Prog} {F : R} (hframes : WP.Frames op x F) :
38+
theorem WP.Frames.op_wp_upperAdjoint_le_wp {R : Type t} (op : R → Pred → Pred)
39+
[∀ r, PreservesSup (op r)] {x : Prog} {F : R} (hframes : WP.Frames op x F) :
4040
∀ Q E, op F (wp x (fun a => PreservesSup.upperAdjoint (op F) (Q a)) E) ⊑ wp x Q E := by
4141
intros
4242
apply PartialOrder.rel_trans

src/Std/Internal/Do/WP/FrameGadget.lean

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,27 +23,15 @@ variable {Prog : Type u} {Value : Type v} {Pred : Type w} {EPred : Type z}
2323
Stripped by `vcgen` before the real spec applies. -/
2424
def skipFrame {α : Sort u} (a : α) : α := a
2525

26-
/-- `meet_wp_imp_le_wp` with the program wrapped in `skipFrame`, the per-call spec `vcgen`
27-
applies for a framed program. The frame `F` is the first explicit argument so that `vcgen` can
28-
partially apply it (pinning `F`) and feed the result through the ordinary spec machinery, leaving
29-
`x`, `epost`, `Q`, and `hframe` schematic. -/
30-
theorem meet_wp_imp_le_wp_skipFrame [∀ a : Pred, PreservesSup (meet a)] (F : Pred) (x : Prog)
31-
(E : EPred) (Q : Value → Pred)
32-
(hframes : WP.Frames meet x F) :
33-
F ⊓ wp (skipFrame x) (fun a => F ⇨ Q a) E ⊑ wp x Q E := by
34-
refine PartialOrder.rel_trans (hframes.conj_wp_le_wp_conj (fun a => F ⇨ Q a) E) ?_
35-
apply WP.wp_consequence
36-
intro a
37-
exact meet_himp_le
38-
39-
/-- `meet_wp_imp_le_wp_skipFrame` for an arbitrary frame operator `conj` whose slices preserve `Sup`,
40-
the per-call spec `vcgen` applies for a program framed with a non-meet operator. `conj` is the first
41-
explicit argument so that `vcgen` can pin it (to the inferred frame operator) while leaving `F`, `x`,
42-
`E`, `Q`, and `hframes` schematic. -/
43-
theorem wp_imp_le_wp_skipFrame {R : Type r} (conj : R → Pred → Pred) [∀ r, PreservesSup (conj r)]
44-
(F : R) (x : Prog) (E : EPred) (Q : Value → Pred)
26+
/-- The per-call spec `vcgen` applies for a framed program: for a frame operator `conj` whose slices
27+
preserve `Sup`, framing by `F` runs the program under the upper adjoint of `conj F` and re-applies
28+
`conj F`. `conj` is the first explicit argument so that `vcgen` can pin it (to the inferred frame
29+
operator) while leaving `F`, `x`, `E`, `Q`, and `hframes` schematic. The lattice meet is the instance
30+
`conj := (· ⊓ ·)`, whose residual `vcgen` folds to Heyting `⇨`. -/
31+
theorem op_wp_upperAdjoint_le_wp_skipFrame {R : Type r} (conj : R → Pred → Pred)
32+
[∀ r, PreservesSup (conj r)] (F : R) (x : Prog) (E : EPred) (Q : Value → Pred)
4533
(hframes : WP.Frames conj x F) :
4634
conj F (wp (skipFrame x) (fun a => PreservesSup.upperAdjoint (conj F) (Q a)) E) ⊑ wp x Q E :=
47-
hframes.conj_wp_imp_le_wp _ _ _
35+
hframes.op_wp_upperAdjoint_le_wp _ _ _
4836

4937
end Std.Internal.Do.Gadget

tests/elab/vcgenTickFrames.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ theorem tickFrames {α : Type} (x : TickM α) (F : Nat) (Q : α → Nat → Prop
243243
F ⋆ TickM.wp x Q EPost.Nil.mk ⊑ TickM.wp x (fun a => F ⋆ Q a) EPost.Nil.mk :=
244244
(frames_costConj x F).conj_wp_le_wp_conj Q EPost.Nil.mk
245245

246-
/-- The framed-spec corollary, mirroring `WP.Frames.conj_wp_imp_le_wp`: running `x` under the magic
246+
/-- The framed-spec corollary, mirroring `WP.Frames.op_wp_upperAdjoint_le_wp`: running `x` under the magic
247247
wand `F -⋆ Q` and re-conjoining the budget `F` is a precondition for running `x` under `Q`. -/
248248
theorem tickFrames_imp {α : Type} (x : TickM α) (F : Nat) (Q : α → Nat → Prop) :
249249
F ⋆ TickM.wp x (fun a => F -⋆ Q a) EPost.Nil.mk ⊑ TickM.wp x Q EPost.Nil.mk := by

0 commit comments

Comments
 (0)