Documentation

Lean.Elab.Tactic.Do.Internal.VCGen.RuleConstruction

Construction of BackwardRules from SpecTheoremNews and split info. Pure SymM — no knowledge of VCGenM. The VCGenM cache wrappers live in VCGen.RuleCache.

Create a backward rule for the SpecTheoremNew that was looked up in the database. In order for the backward rule to apply, we need to instantiate both m and ps with the ones given by the use site, and perhaps emit verification conditions for spec lemmas that would not apply everywhere.

General idea #

Consider the spec theorem Spec.bind:

Spec.bind : ∀ {m : Type u → Type v} {ps : PostShape} [inst : Monad m] [inst_1 : WPMonad m ps]
  {α β : Type u} {x : m α} {f : α → m β} {Q : PostCond β ps},
  ⦃wp⟦x⟧ (fun a => wp⟦f a⟧ Q, Q.snd)⦄ (x >>= f) ⦃Q⦄

This theorem is already in "WP-form", so its postcondition Q is schematic (i.e., a ∀-bound var). However, its precondition wp⟦x⟧ ... is not. Hence we must emit a VC for the precondition:

prf : ∀ {m : Type u → Type v} {ps : PostShape} [inst : Monad m] [inst_1 : WPMonad m ps]
  {α β : Type u} {x : m α} {f : α → m β} {Q : PostCond β ps}
  (P : Assertion ps) (hpre : P ⊢ₛ wp⟦x⟧ (fun a => wp⟦f a⟧ Q, Q.snd)),
  P ⊢ₛ wp⟦x >>= f⟧ Q

(Note that P ⊢ₛ wp⟦x >>= f⟧ Q is the definition of ⦃P⦄ (x >>= f) ⦃Q⦄.) Where prf is constructed by doing SPred.entails.trans hpre spec under the forall telescope. The conclusion of this rule applies to any situation where bind is the top-level symbol in the program.

Postcondition VCs #

Similarly, a VC hpost is generated for the postcondition if it isn't schematic. The details here are more complicated because we need to make available the pure facts in P to prove Q' ⊢ₚ Q, so the hpost obligation becomes P ⊢ₛ ⌜Q' ⊢ₚ Q⌝. For example, a hypothetical restrictive spec for pure in Id would be:

myPure.spec (n : Nat) : ⦃fun x => ⌜True⌝⦄ myPure n ⦃⇓ r x => ⌜r = n⌝⦄

This yields the following backward rule:

prf : ∀ (n : Nat) (P : Assertion .pure) (hpre : P ⊢ₛ ⌜True⌝)
  (Q : PostCond Nat .pure) (hpost : P ⊢ₛ ⌜(⇓ r => ⌜r = n⌝) ⊢ₚ Q⌝),
  P ⊢ₛ wp⟦myPure n⟧ Q

The prf term in this (most general) case is

fun n P hpre Q hpost =>
  SPred.pure_elim hpost fun h =>
    SPred.entails.trans (SPred.entails.trans hpre (myPure.spec n))
      (PredTrans.mono (wp (myPure n)) (⇓ r => ⌜r = n⌝) Q h)

Excess state arguments #

Furthermore, when there are excess state arguments [s₁, ..., sₙ] involved, we rather need to specialize the backward rule for that:

... : ∀ {m : Type u → Type v} {ps : PostShape} [inst : Monad m] [inst_1 : WPMonad m ps]
  {α β : Type u} {x : m α} {f : α → m β} {Q : PostCond β ps}
  (P : Assertion ...) (hpre : P ⊢ₛ wp⟦x⟧ (fun a => wp⟦f a⟧ Q, Q.snd) s₁ ... sₙ),
  P ⊢ₛ wp⟦x >>= f⟧ Q s₁ ... sₙ

Caching #

It turns out we can cache backward rules for the cache key (specThm, m, excessArgs.size). This is very important for performance and helps getting rid of the overhead imposed by the generality of Std.Do. We do that in the VCGenM wrapper mkBackwardRuleFromSpecCached. Furthermore, in order to avoid re-checking the same proof in the kernel, we generate an auxiliary lemma for the backward rule.

Specialization and unfolding of Std.Do abbreviations and defs #

It is unnecessary to use the bind rule in full generality. It is much more efficient to specialize it for the particular monad, postshape and WP instance. In doing so we can also unfold many Std.Do abbreviations, such as Assertion ps and PostCond α ps. We do that by doing unfoldReducible on the forall telescope. The type for StateM Nat and one excess state arg s becomes

prf : ∀ (α : Type) (x : StateT Nat Id α) (β : Type) (f : α → StateT Nat Id β)
        (Q : (β → NatULift Prop) × ExceptConds (PostShape.arg Nat PostShape.pure)) (s : Nat)
        (P : ULift Prop) (hpre : P ⊢ₛ wp⟦x⟧ (fun a => wp⟦f a⟧ Q, Q.snd) s),
        P ⊢ₛ wp⟦x >>= f⟧ Q s

We are still investigating how to get rid of more kernel unfolding overhead, such as for wp and List.rec.

Equations
  • One or more equations did not get rendered due to their size.
Instances For

    Create a backward rule for a simp/equational spec ∀ xs, lhs = rhs.

    Instantiates the equation, unifies with the monad m, synthesizes typeclass instances, reduces projections and applies unfoldReducible to the RHS. Then builds a backward rule of the form:

    ∀ Q s₁ ... sₙ P (h : P ⊢ₛ wp⟦rhs_reduced⟧ Q s₁ ... sₙ), P ⊢ₛ wp⟦lhs⟧ Q s₁ ... sₙ
    

    using Eq.mpr with a congrArg proof.

    For example, MonadState.get.eq_1 : get = self.1 with m = StateT σ m' yields a rule that rewrites wp⟦get⟧ to wp⟦MonadStateOf.get⟧ (after instance synthesis + projection reduction + unfoldReducible).

    Equations
    • One or more equations did not get rendered due to their size.
    Instances For

      Creates a reusable backward rule for splitting ite, dite, or matchers.

      Uses SplitInfo.withAbstract to open fvars for the split, then SplitInfo.splitWith to build the splitting proof. Hypothesis types are discovered via rwIfOrMatcher inside the splitter telescope.

      Equations
      • One or more equations did not get rendered due to their size.
      Instances For