This PR make `mleave` apply `at *` and improve its simp set in order to discharge some more trivialities (#9581). It also improves some documentation.
256 lines
9.4 KiB
Text
256 lines
9.4 KiB
Text
/-
|
||
Copyright (c) 2025 Lean FRO LLC. All rights reserved.
|
||
Released under Apache 2.0 license as described in the file LICENSE.
|
||
Authors: Sebastian Graf
|
||
-/
|
||
module
|
||
|
||
prelude
|
||
public import Std.Do.SPred
|
||
|
||
@[expose] public section
|
||
|
||
/-!
|
||
# Pre and postconditions
|
||
|
||
This module defines `Assertion` and `PostCond`, the types which constitute
|
||
the pre and postconditions of a Hoare triple in the program logic.
|
||
|
||
## Predicate shapes
|
||
|
||
Since `WP` supports arbitrary monads, `PostCond` must be general enough to
|
||
cope with state and exceptions.
|
||
For this reason, `PostCond` is indexed by a `PostShape` which is an abstraction
|
||
of the stack of effects supported by the monad, corresponding directly to
|
||
`StateT` and `ExceptT` layers in a transformer stack.
|
||
For every `StateT σ` effect, we get one `PostShape.arg σ` layer, whereas for every
|
||
`ExceptT ε` effect, we get one `PostShape.except ε` layer.
|
||
Currently, the only supported base layer is `PostShape.pure`.
|
||
|
||
## Pre and postconditions
|
||
|
||
The type of preconditions `Assertion ps` is distinct from the type of postconditions `PostCond α ps`.
|
||
|
||
This is because postconditions not only specify what happens upon successful termination of the
|
||
program, but also need to specify a property that holds upon failure.
|
||
We get one "barrel" for the success case, plus one barrel per `PostShape.except` layer.
|
||
|
||
It does not make much sense to talk about failure barrels in the context of preconditions.
|
||
Hence, `Assertion ps` is defined such that all `PostShape.except` layers are discarded from `ps`,
|
||
via `PostShape.args`.
|
||
-/
|
||
|
||
namespace Std.Do
|
||
|
||
universe u
|
||
|
||
inductive PostShape : Type (u+1) where
|
||
| pure : PostShape
|
||
| arg : (σ : Type u) → PostShape → PostShape
|
||
| except : (ε : Type u) → PostShape → PostShape
|
||
|
||
variable {ps : PostShape.{u}} {α σ ε : Type u}
|
||
|
||
abbrev PostShape.args : PostShape.{u} → List (Type u)
|
||
| .pure => []
|
||
| .arg σ s => σ :: PostShape.args s
|
||
| .except _ s => PostShape.args s
|
||
|
||
/--
|
||
An assertion on the `.arg`s in the given predicate shape.
|
||
```
|
||
example : Assertion (.arg ρ .pure) = (ρ → ULift Prop) := rfl
|
||
example : Assertion (.except ε .pure) = ULift Prop := rfl
|
||
example : Assertion (.arg σ (.except ε .pure)) = (σ → ULift Prop) := rfl
|
||
example : Assertion (.except ε (.arg σ .pure)) = (σ → ULift Prop) := rfl
|
||
```
|
||
This is an abbreviation for `SPred` under the hood, so all theorems about `SPred` apply.
|
||
-/
|
||
abbrev Assertion (ps : PostShape.{u}) : Type u :=
|
||
SPred (PostShape.args ps)
|
||
|
||
/--
|
||
Encodes one continuation barrel for each `PostShape.except` in the given predicate shape.
|
||
```
|
||
example : FailConds (.pure) = Unit := rfl
|
||
example : FailConds (.except ε .pure) = ((ε → ULift Prop) × Unit) := rfl
|
||
example : FailConds (.arg σ (.except ε .pure)) = ((ε → ULift Prop) × Unit) := rfl
|
||
example : FailConds (.except ε (.arg σ .pure)) = ((ε → σ → ULift Prop) × Unit) := rfl
|
||
```
|
||
-/
|
||
def FailConds : PostShape.{u} → Type u
|
||
| .pure => PUnit
|
||
| .arg _ ps => FailConds ps
|
||
| .except ε ps => (ε → Assertion ps) × FailConds ps
|
||
|
||
@[simp]
|
||
def FailConds.const {ps : PostShape.{u}} (p : Prop) : FailConds ps := match ps with
|
||
| .pure => ⟨⟩
|
||
| .arg _ ps => @FailConds.const ps p
|
||
| .except _ ps => (fun _ε => spred(⌜p⌝), @FailConds.const ps p)
|
||
|
||
def FailConds.true : FailConds ps := FailConds.const True
|
||
|
||
def FailConds.false : FailConds ps := FailConds.const False
|
||
|
||
instance : Inhabited (FailConds ps) where
|
||
default := FailConds.true
|
||
|
||
def FailConds.entails {ps : PostShape.{u}} (x y : FailConds ps) : Prop :=
|
||
match ps with
|
||
| .pure => True
|
||
| .arg _ ps => @entails ps x y
|
||
| .except _ ps => (∀ e, x.1 e ⊢ₛ y.1 e) ∧ @entails ps x.2 y.2
|
||
|
||
scoped infix:25 " ⊢ₑ " => FailConds.entails
|
||
|
||
@[refl, simp]
|
||
theorem FailConds.entails.refl {ps : PostShape} (x : FailConds ps) : x ⊢ₑ x := by
|
||
induction ps <;> simp [entails, *]
|
||
|
||
theorem FailConds.entails.rfl {ps : PostShape} {x : FailConds ps} : x ⊢ₑ x := refl x
|
||
|
||
theorem FailConds.entails.trans {ps : PostShape} {x y z : FailConds ps} : (x ⊢ₑ y) → (y ⊢ₑ z) → x ⊢ₑ z := by
|
||
induction ps
|
||
case pure => intro _ _; trivial
|
||
case arg σ s ih => exact ih
|
||
case except ε s ih => intro h₁ h₂; exact ⟨fun e => (h₁.1 e).trans (h₂.1 e), ih h₁.2 h₂.2⟩
|
||
|
||
@[simp]
|
||
theorem FailConds.entails_false {x : FailConds ps} : FailConds.false ⊢ₑ x := by
|
||
induction ps <;> simp_all [false, const, entails, SPred.false_elim]
|
||
|
||
@[simp]
|
||
theorem FailConds.entails_true {x : FailConds ps} : x ⊢ₑ FailConds.true := by
|
||
induction ps <;> simp_all [true, const, entails]
|
||
|
||
@[simp]
|
||
def FailConds.and {ps : PostShape.{u}} (x : FailConds ps) (y : FailConds ps) : FailConds ps :=
|
||
match ps with
|
||
| .pure => ⟨⟩
|
||
| .arg _ ps => @FailConds.and ps x y
|
||
| .except _ _ => (fun e => SPred.and (x.1 e) (y.1 e), FailConds.and x.2 y.2)
|
||
|
||
infixr:35 " ∧ₑ " => FailConds.and
|
||
|
||
@[simp]
|
||
theorem FailConds.and_true {x : FailConds ps} : x ∧ₑ FailConds.true ⊢ₑ x := by
|
||
induction ps
|
||
case pure => trivial
|
||
case arg ih => exact ih
|
||
case except ε ps ih =>
|
||
simp_all only [true, and, const]
|
||
constructor <;> simp only [SPred.and_true.mp, implies_true, ih]
|
||
|
||
@[simp]
|
||
theorem FailConds.true_and {x : FailConds ps} : FailConds.true ∧ₑ x ⊢ₑ x := by
|
||
induction ps
|
||
case pure => trivial
|
||
case arg ih => exact ih
|
||
case except ε ps ih =>
|
||
simp_all only [true, and, const]
|
||
constructor <;> simp only [SPred.true_and.mp, implies_true, ih]
|
||
|
||
@[simp]
|
||
theorem FailConds.and_false {x : FailConds ps} : x ∧ₑ FailConds.false ⊢ₑ FailConds.false := by
|
||
induction ps
|
||
case pure => trivial
|
||
case arg ih => exact ih
|
||
case except ε ps ih =>
|
||
simp_all only [false, and, const]
|
||
constructor <;> simp only [SPred.and_false.mp, implies_true, ih]
|
||
|
||
@[simp]
|
||
theorem FailConds.false_and {x : FailConds ps} : FailConds.false ∧ₑ x ⊢ₑ FailConds.false := by
|
||
induction ps
|
||
case pure => trivial
|
||
case arg ih => exact ih
|
||
case except ε ps ih =>
|
||
simp_all only [and, false, const]
|
||
constructor <;> simp only [SPred.false_and.mp, implies_true, ih]
|
||
|
||
theorem FailConds.and_eq_left {ps : PostShape} {p q : FailConds ps} (h : p ⊢ₑ q) :
|
||
p = (p ∧ₑ q) := by
|
||
induction ps
|
||
case pure => trivial
|
||
case arg ih => exact ih h
|
||
case except ε ps ih =>
|
||
simp_all only [and]
|
||
apply Prod.ext
|
||
· ext a; exact (SPred.and_eq_left.mp (h.1 a)).to_eq
|
||
· exact ih h.2
|
||
|
||
/--
|
||
A postcondition for the given predicate shape, with one `Assertion` for the terminating case and
|
||
one `Assertion` for each `.except` layer in the predicate shape.
|
||
```
|
||
example : PostCond α (.arg ρ .pure) = ((α → ρ → Prop) × Unit) := rfl
|
||
example : PostCond α (.except ε .pure) = ((α → Prop) × (ε → Prop) × Unit) := rfl
|
||
example : PostCond α (.arg σ (.except ε .pure)) = ((α → σ → Prop) × (ε → Prop) × Unit) := rfl
|
||
example : PostCond α (.except ε (.arg σ .pure)) = ((α → σ → Prop) × (ε → σ → Prop) × Unit) := rfl
|
||
```
|
||
-/
|
||
abbrev PostCond (α : Type u) (ps : PostShape.{u}) : Type u :=
|
||
(α → Assertion ps) × FailConds ps
|
||
|
||
@[inherit_doc PostCond]
|
||
scoped macro:max "post⟨" handlers:term,+,? "⟩" : term =>
|
||
`(by exact ⟨$handlers,*, ()⟩)
|
||
-- NB: Postponement through by exact is the entire point of this macro
|
||
-- until https://github.com/leanprover/lean4/pull/8074 lands
|
||
|
||
/--
|
||
A postcondition expressing total correctness.
|
||
That is, it expresses that the asserted computation finishes without throwing an exception
|
||
*and* the result satisfies the given predicate `p`.
|
||
-/
|
||
abbrev PostCond.total (p : α → Assertion ps) : PostCond α ps :=
|
||
(p, FailConds.false)
|
||
|
||
@[inherit_doc PostCond.total]
|
||
scoped macro:max ppAllowUngrouped "⇓" xs:term:max+ " => " e:term : term =>
|
||
`(PostCond.total (by exact fun $xs* => spred($e)))
|
||
|
||
/--
|
||
A postcondition expressing partial correctness.
|
||
That is, it expresses that *if* the asserted computation finishes without throwing an exception
|
||
*then* the result satisfies the given predicate `p`.
|
||
Nothing is asserted when the computation throws an exception.
|
||
-/
|
||
abbrev PostCond.partial (p : α → Assertion ps) : PostCond α ps :=
|
||
(p, FailConds.true)
|
||
|
||
instance : Inhabited (PostCond α ps) where
|
||
default := PostCond.total fun _ => default
|
||
|
||
@[simp]
|
||
def PostCond.entails (p q : PostCond α ps) : Prop :=
|
||
(∀ a, SPred.entails (p.1 a) (q.1 a)) ∧ FailConds.entails p.2 q.2
|
||
|
||
scoped infix:25 " ⊢ₚ " => PostCond.entails
|
||
|
||
@[refl, simp]
|
||
theorem PostCond.entails.refl (Q : PostCond α ps) : Q ⊢ₚ Q := ⟨fun a => SPred.entails.refl (Q.1 a), FailConds.entails.refl Q.2⟩
|
||
theorem PostCond.entails.rfl {Q : PostCond α ps} : Q ⊢ₚ Q := refl Q
|
||
|
||
theorem PostCond.entails.trans {P Q R : PostCond α ps} (h₁ : P ⊢ₚ Q) (h₂ : Q ⊢ₚ R) : P ⊢ₚ R :=
|
||
⟨fun a => (h₁.1 a).trans (h₂.1 a), h₁.2.trans h₂.2⟩
|
||
|
||
@[simp]
|
||
theorem PostCond.entails_total (p : α → Assertion ps) (q : PostCond α ps) : PostCond.total p ⊢ₚ q ↔ ∀ a, p a ⊢ₛ q.1 a := by
|
||
simp only [entails, FailConds.entails_false, and_true]
|
||
|
||
@[simp]
|
||
theorem PostCond.entails_partial (p : PostCond α ps) (q : α → Assertion ps) : p ⊢ₚ PostCond.partial q ↔ ∀ a, p.1 a ⊢ₛ q a := by
|
||
simp only [entails, FailConds.entails_true, and_true]
|
||
|
||
abbrev PostCond.and (p : PostCond α ps) (q : PostCond α ps) : PostCond α ps :=
|
||
(fun a => SPred.and (p.1 a) (q.1 a), FailConds.and p.2 q.2)
|
||
|
||
scoped infixr:35 " ∧ₚ " => PostCond.and
|
||
|
||
theorem PostCond.and_eq_left {p q : PostCond α ps} (h : p ⊢ₚ q) :
|
||
p = (p ∧ₚ q) := by
|
||
ext
|
||
· exact (SPred.and_eq_left.mp (h.1 _)).to_eq
|
||
· exact FailConds.and_eq_left h.2
|