mirror of
https://github.com/CatalaLang/catala.git
synced 2024-11-08 07:51:43 +03:00
811 lines
25 KiB
Plaintext
811 lines
25 KiB
Plaintext
module Catala.LambdaCalculus
|
|
open FStar.Mul
|
|
|
|
module T = FStar.Tactics
|
|
|
|
|
|
/// This whole proof is inspired by FStar/examples/metatheory/StlcStrongdbparsubst.fst
|
|
|
|
(*** Syntax *)
|
|
|
|
type ty =
|
|
| TBool : ty
|
|
| TUnit : ty
|
|
| TArrow : tin: ty -> tout: ty -> ty
|
|
| TList: elts:ty -> ty
|
|
| TOption: a: ty -> ty
|
|
|
|
type var = nat
|
|
|
|
type err =
|
|
| EmptyError : err
|
|
| ConflictError : err
|
|
|
|
type lit =
|
|
| LError : err:err -> lit
|
|
| LTrue : lit
|
|
| LFalse : lit
|
|
| LUnit : lit
|
|
|
|
type exp =
|
|
| EVar : v: var -> exp
|
|
| EApp : fn: exp -> arg: exp -> tau_arg: ty -> exp
|
|
| EAbs : vty: ty -> body: exp -> exp
|
|
| EThunk : body:exp -> exp
|
|
| ELit : l: lit -> exp
|
|
| EIf : test: exp -> btrue: exp -> bfalse: exp -> exp
|
|
| ESome : s:exp -> exp
|
|
| ENone : exp
|
|
| EMatchOption : arg:exp -> tau_some: ty -> none:exp -> some:exp -> exp
|
|
| EList : l:list exp -> exp
|
|
| ECatchEmptyError: to_try:exp -> catch_with:exp -> exp
|
|
| EFoldLeft : f:exp -> init:exp -> tau_init:ty -> l:exp -> tau_elt:ty -> exp
|
|
|
|
(*** Operational semantics *)
|
|
|
|
(**** Helpers *)
|
|
|
|
let c_err = ELit (LError ConflictError)
|
|
|
|
let e_err = ELit (LError EmptyError)
|
|
|
|
val is_value: exp -> Tot bool
|
|
let rec is_value e =
|
|
match e with
|
|
| EAbs _ _ | EThunk _ | ELit _ | ENone -> true
|
|
| ESome (ELit (LError _)) -> false
|
|
| ESome e' -> is_value e'
|
|
| EList l -> is_value_list l
|
|
| _ -> false
|
|
and is_value_list (es: list exp) : Tot bool =
|
|
match es with
|
|
| [] -> true
|
|
| hd::tl -> is_value hd && is_value_list tl
|
|
|
|
|
|
let var_to_exp = var -> Tot exp
|
|
|
|
let is_renaming_prop (s: var_to_exp) : prop = forall (x: var). EVar? (s x)
|
|
|
|
let is_renaming_size (s: var_to_exp)
|
|
: GTot (n:int{(is_renaming_prop s) ==> n = 0 /\ (~(is_renaming_prop s) ==> n = 1)})
|
|
=
|
|
if FStar.StrongExcludedMiddle.strong_excluded_middle (is_renaming_prop s) then 0 else 1
|
|
|
|
let increment : var_to_exp = fun y -> EVar (y + 1)
|
|
|
|
let increment_is_renaming (_: unit) : Lemma (is_renaming_prop increment) = ()
|
|
|
|
let is_var_size (e: exp) : int = if EVar? e then 0 else 1
|
|
|
|
|
|
let rec subst (s: var_to_exp) (e: exp) : Pure exp
|
|
(requires True)
|
|
(ensures (fun e' -> (is_renaming_prop s /\ EVar? e) ==> EVar? e'))
|
|
(decreases %[is_var_size e; is_renaming_size s; 1; e])
|
|
=
|
|
match e with
|
|
| EVar x -> s x
|
|
| EAbs t e1 -> EAbs t (subst (subst_abs s) e1)
|
|
| EThunk e1 -> EThunk (subst s e1)
|
|
| EApp e1 e2 tau_arg -> EApp (subst s e1) (subst s e2) tau_arg
|
|
| ELit l -> ELit l
|
|
| EIf e1 e2 e3 -> EIf (subst s e1) (subst s e2) (subst s e3)
|
|
| ESome e1 -> ESome (subst s e1)
|
|
| ENone -> ENone
|
|
| EMatchOption arg tau_some none some ->
|
|
EMatchOption (subst s arg) tau_some (subst s none) (subst s some)
|
|
| EList l -> EList (subst_list s l)
|
|
| ECatchEmptyError to_try catch_with ->
|
|
ECatchEmptyError (subst s to_try) (subst s catch_with)
|
|
| EFoldLeft f init tau_init l tau_elt ->
|
|
EFoldLeft (subst s f) (subst s init) tau_init (subst s l) tau_elt
|
|
and subst_list (s: var_to_exp) (l: list exp) : Tot (list exp)
|
|
(decreases %[1; is_renaming_size s; 1; l]) =
|
|
match l with
|
|
| [] -> []
|
|
| hd :: tl -> (subst s hd) :: (subst_list s tl)
|
|
and subst_abs (s: var_to_exp) (y: var) : Tot (e':exp{is_renaming_prop s ==> EVar? e'})
|
|
(decreases %[1; is_renaming_size s; 0])
|
|
=
|
|
if y = 0 then EVar y else subst increment (s (y -1))
|
|
|
|
let var_to_exp_beta (v: exp) : Tot var_to_exp = fun y ->
|
|
if y = 0 then v else (EVar (y - 1))
|
|
|
|
|
|
(**** Stepping judgment *)
|
|
|
|
type list_step_result =
|
|
| Good of list exp
|
|
| Error of exp
|
|
| Bad
|
|
|
|
let is_not_bad (l: list_step_result) : bool = match l with
|
|
| Good _ | Error _ -> true | Bad -> false
|
|
|
|
let rec step_app (e: exp) (e1: exp{e1 << e}) (e2: exp{e2 << e}) (tau_arg: ty{tau_arg << e})
|
|
: Tot (option exp) (decreases %[ e; 0 ]) =
|
|
if is_value e1
|
|
then
|
|
match e1 with
|
|
| ELit (LError err) -> Some (ELit (LError err))
|
|
| _ ->
|
|
if is_value e2
|
|
then
|
|
match e2 with
|
|
| ELit (LError err) -> Some (ELit (LError err))
|
|
| _ -> begin
|
|
match e1 with
|
|
| EAbs t e' -> Some (subst (var_to_exp_beta e2) e') (* D-Beta *)
|
|
| EThunk e' -> Some e'
|
|
| _ -> None
|
|
end
|
|
else
|
|
(match step e2 with
|
|
| Some e2' -> Some (EApp e1 e2' tau_arg) (* D-Context *)
|
|
| None -> None)
|
|
else
|
|
(match step e1 with
|
|
| Some e1' -> Some (EApp e1' e2 tau_arg) (* D-Context *)
|
|
| None -> None)
|
|
|
|
and step_if (e: exp) (e1: exp{e1 << e}) (e2: exp{e2 << e}) (e3: exp{e3 << e})
|
|
: Tot (option exp) (decreases %[ e; 1 ]) =
|
|
if is_value e1
|
|
then
|
|
match e1 with
|
|
| ELit (LError err) -> Some (ELit (LError err))
|
|
| ELit LTrue -> Some e2
|
|
| ELit LFalse -> Some e3
|
|
| _ -> None
|
|
else
|
|
match (step e1) with
|
|
| Some e1' -> Some (EIf e1' e2 e3)
|
|
| None -> None
|
|
|
|
and step_match
|
|
(e: exp)
|
|
(arg: exp{arg << e})
|
|
(tau_some: ty)
|
|
(none: exp{none << e})
|
|
(some: exp{some << e})
|
|
: Tot (option exp) (decreases %[ e; 2 ]) =
|
|
if is_value arg
|
|
then
|
|
match arg with
|
|
| ENone -> Some none
|
|
| ESome s -> Some (EApp some s tau_some)
|
|
| ELit (LError err) -> Some (ELit (LError err))
|
|
| _ -> None
|
|
else
|
|
match (step arg) with
|
|
| Some arg' -> Some (EMatchOption arg' tau_some none some) (* D-Context *)
|
|
| None -> None
|
|
|
|
and step_list
|
|
(e: exp)
|
|
(l: list exp{l << e})
|
|
: Tot (list_step_result) (decreases %[ e; 3; l ]) =
|
|
match l with
|
|
| [] -> Bad
|
|
| [hd] -> begin
|
|
if is_value hd then Bad else
|
|
match step hd with
|
|
| None -> Bad
|
|
| Some (ELit (LError err)) -> Error (ELit (LError err))
|
|
| Some hd' -> Good ([hd'])
|
|
end
|
|
| hd::tl -> begin
|
|
if is_value hd then
|
|
match step_list e tl with
|
|
| Bad -> Bad
|
|
| Error err -> Error err
|
|
| Good tl' -> Good (hd::tl')
|
|
else
|
|
match step hd with
|
|
| None -> Bad
|
|
| Some (ELit (LError err)) -> Error (ELit (LError err))
|
|
| Some hd' -> Good (hd'::tl)
|
|
end
|
|
|
|
and step_catch
|
|
(e: exp)
|
|
(to_try: exp{to_try << e})
|
|
(catch_with: exp{catch_with << e})
|
|
: Tot (option exp) (decreases %[ e; 4 ]) =
|
|
if is_value to_try then
|
|
match to_try with
|
|
| ELit (LError EmptyError) -> Some catch_with
|
|
| _ -> Some to_try
|
|
else
|
|
match step to_try with
|
|
| None -> None
|
|
| Some to_try' -> Some (ECatchEmptyError to_try' catch_with)
|
|
|
|
and step_fold_left
|
|
(e: exp)
|
|
(f: exp{f << e})
|
|
(init: exp{init << e})
|
|
(tau_init: ty)
|
|
(l: exp{l << e})
|
|
(tau_elt: ty)
|
|
: Tot (option exp) (decreases %[ e; 5; l ]) =
|
|
match is_value f, is_value init, is_value l with
|
|
| false, _, _ -> begin
|
|
match step f with
|
|
| None -> None
|
|
| Some f' -> Some (EFoldLeft f' init tau_init l tau_elt)
|
|
end
|
|
| true, false, _ -> begin
|
|
match step init with
|
|
| None -> None
|
|
| Some init' -> Some (EFoldLeft f init' tau_init l tau_elt)
|
|
end
|
|
| true, true, false -> begin
|
|
match step l with
|
|
| None -> None
|
|
| Some l' -> Some (EFoldLeft f init tau_init l' tau_elt)
|
|
end
|
|
| true, true, true -> begin
|
|
match f, init, l with
|
|
| ELit (LError err), _ , _
|
|
| _, ELit (LError err), _
|
|
| _, _, ELit (LError err) -> Some (ELit (LError err))
|
|
| _, _, EList [] -> Some init
|
|
| _, _, EList (hd::tl) ->
|
|
Some (EFoldLeft
|
|
f (EApp (EApp f init tau_init) hd tau_elt)
|
|
tau_init (EList tl) tau_elt
|
|
)
|
|
| _ -> None
|
|
end
|
|
|
|
and step (e: exp) : Tot (option exp) (decreases %[ e; 6 ]) =
|
|
match e with
|
|
| EApp e1 e2 tau_arg -> step_app e e1 e2 tau_arg
|
|
| EIf e1 e2 e3 -> step_if e e1 e2 e3
|
|
| ESome e1 ->
|
|
if is_value e1 then
|
|
match e1 with
|
|
| ELit (LError err) -> Some (ELit (LError err))
|
|
| _ -> None
|
|
else begin
|
|
match step e1 with
|
|
| None -> None
|
|
| Some e1' -> Some (ESome e1')
|
|
end
|
|
| EMatchOption arg tau_some none some -> step_match e arg tau_some none some
|
|
| EList l -> begin match step_list e l with
|
|
| Bad -> None
|
|
| Error err -> Some err
|
|
| Good l' -> Some (EList l')
|
|
end
|
|
| ECatchEmptyError to_try catch_with -> step_catch e to_try catch_with
|
|
| EFoldLeft f init tau_init l tau_elt -> step_fold_left e f init tau_init l tau_elt
|
|
| _ -> None
|
|
|
|
(*** Typing *)
|
|
|
|
(**** Typing helpers *)
|
|
|
|
type env = FunctionalExtensionality.restricted_t var (fun _ -> option ty)
|
|
|
|
val empty:env
|
|
let empty = FunctionalExtensionality.on_dom var (fun _ -> None)
|
|
|
|
val extend: env -> ty -> Tot env
|
|
let extend g t = FunctionalExtensionality.on_dom var
|
|
(fun x' -> if 0 = x' then Some t else g (x' - 1))
|
|
|
|
(**** Typing judgment *)
|
|
|
|
let rec typing (g: env) (e: exp) (tau: ty) : Tot bool (decreases (e)) =
|
|
match e with
|
|
| EVar x -> g x = Some tau
|
|
| EAbs t e1 ->
|
|
(match tau with
|
|
| TArrow tau_in tau_out -> t = tau_in &&
|
|
typing (extend g t) e1 tau_out
|
|
| _ -> false)
|
|
| EThunk e1 ->
|
|
(match tau with
|
|
| TArrow TUnit tau_out -> typing g e1 tau_out
|
|
| _ -> false)
|
|
| EApp e1 e2 tau_arg -> typing g e1 (TArrow tau_arg tau) && typing g e2 tau_arg
|
|
| ELit LTrue -> tau = TBool
|
|
| ELit LFalse -> tau = TBool
|
|
| ELit LUnit -> tau = TUnit
|
|
| ELit (LError _) -> true
|
|
| EIf e1 e2 e3 -> typing g e1 TBool && typing g e2 tau && typing g e3 tau
|
|
| ESome e1 -> begin
|
|
match tau with
|
|
| TOption t' -> typing g e1 t'
|
|
| _ -> false
|
|
end
|
|
| ENone -> begin
|
|
match tau with
|
|
| TOption _ -> true
|
|
| _ -> false
|
|
end
|
|
| EMatchOption arg tau_some none some ->
|
|
typing g arg (TOption tau_some) &&
|
|
typing g none tau &&
|
|
typing g some (TArrow tau_some tau)
|
|
| EList l -> begin
|
|
match tau with
|
|
| TList tau' -> typing_list g l tau'
|
|
| _ -> false
|
|
end
|
|
| ECatchEmptyError to_try catch_with ->
|
|
typing g to_try tau &&
|
|
typing g catch_with tau
|
|
| EFoldLeft f init tau_init l tau_elt ->
|
|
tau_init = tau &&
|
|
typing g l (TList tau_elt) &&
|
|
typing g init tau &&
|
|
typing g f (TArrow tau (TArrow tau_elt tau))
|
|
| _ -> false
|
|
and typing_list (g: env) (subs: list exp) (tau: ty) : Tot bool (decreases (subs)) =
|
|
match subs with
|
|
| [] -> true
|
|
| hd :: tl -> typing g hd tau && typing_list g tl tau
|
|
|
|
(*** Progress *)
|
|
|
|
(**** Progress lemmas *)
|
|
|
|
let is_bool_value_cannot_be_abs (g: env) (e: exp)
|
|
: Lemma (requires (is_value e /\ (typing g e TBool)))
|
|
(ensures
|
|
(match e with
|
|
| ELit LUnit -> False
|
|
| ELit _ -> True
|
|
| _ -> False)) = ()
|
|
|
|
let typing_conserved_by_list_reduction (g: env) (subs: list exp) (tau: ty)
|
|
: Lemma (requires ((typing_list g subs tau)))
|
|
(ensures (Cons? subs ==> (typing_list g (Cons?.tl subs) tau))) = ()
|
|
|
|
(**** Progress theorem *)
|
|
|
|
let rec size_for_progress (e: exp) : Tot pos =
|
|
match e with
|
|
| EVar _ -> 1
|
|
| EApp fn arg _ -> size_for_progress fn + size_for_progress arg + 1
|
|
| EAbs _ body -> size_for_progress body + 1
|
|
| EThunk body -> size_for_progress body + 1
|
|
| ELit _ -> 1
|
|
| EIf e1 e2 e3 -> size_for_progress e1 + size_for_progress e2 + size_for_progress e3 + 1
|
|
| ESome s -> size_for_progress s + 1
|
|
| ENone -> 1
|
|
| EMatchOption arg _ none some ->
|
|
size_for_progress arg + size_for_progress none + size_for_progress some + 1
|
|
| EList l -> size_for_progress_list l + 1
|
|
| ECatchEmptyError e1 e2 -> size_for_progress e1 + size_for_progress e2 + 1
|
|
| EFoldLeft f init _ l _ ->
|
|
size_for_progress f + size_for_progress init + size_for_progress l +
|
|
(size_for_progress f) * (size_for_progress l) + 1
|
|
and size_for_progress_list (e: list exp) : Tot nat = match e with
|
|
| [] -> 0
|
|
| hd::tl -> size_for_progress hd + size_for_progress_list tl + 10
|
|
|
|
#restart-solver
|
|
|
|
#push-options "--fuel 3 --ifuel 0 --z3rlimit 30 --quake 10/1"
|
|
let lemma_size_fold_step (f init hd: exp) (tl: list exp) (tau_init tau_elt: ty) : Lemma (
|
|
size_for_progress (EFoldLeft f init tau_init (EList (hd::tl)) tau_elt) >
|
|
size_for_progress (EFoldLeft f
|
|
(EApp (EApp f init tau_init) hd tau_elt) tau_init (EList tl) tau_elt)
|
|
) =
|
|
let e = EFoldLeft f init tau_init (EList (hd::tl)) tau_elt in
|
|
let e' = EFoldLeft f (EApp (EApp f init tau_init) hd tau_elt) tau_init (EList tl) tau_elt in
|
|
assert(size_for_progress e =
|
|
size_for_progress f +
|
|
size_for_progress init +
|
|
(size_for_progress hd + size_for_progress_list tl + 10 + 1) +
|
|
(size_for_progress f) * (size_for_progress hd + size_for_progress_list tl + 10 + 1) +
|
|
1);
|
|
assert(size_for_progress e' =
|
|
size_for_progress f +
|
|
(size_for_progress f + size_for_progress init + 1) +
|
|
(size_for_progress hd + 1) +
|
|
(size_for_progress_list tl + 1) +
|
|
(size_for_progress f) * (size_for_progress_list tl + 1) +
|
|
1);
|
|
assert(size_for_progress e - size_for_progress e' =
|
|
- size_for_progress f - 1 - size_for_progress hd - 1
|
|
+ size_for_progress hd + 10
|
|
+ (size_for_progress f) * (size_for_progress hd + 10)
|
|
);
|
|
assert(size_for_progress e - size_for_progress e' >
|
|
- size_for_progress f + 7
|
|
+ (size_for_progress f) * (size_for_progress hd + 10));
|
|
assert(size_for_progress e - size_for_progress e' > 7
|
|
+ (size_for_progress f) * (size_for_progress hd + 9))
|
|
#pop-options
|
|
|
|
#push-options "--fuel 1 --ifuel 0 --z3rlimit 30"
|
|
let lemma_size_fold_f (f init l: exp) (tau_init tau_elt: ty)
|
|
: Lemma (size_for_progress (EFoldLeft f init tau_init l tau_elt) > size_for_progress f)
|
|
=
|
|
()
|
|
#pop-options
|
|
|
|
#push-options "--fuel 1 --ifuel 0 --z3rlimit 30"
|
|
let lemma_size_fold_init (f init l: exp) (tau_init tau_elt: ty)
|
|
: Lemma (size_for_progress (EFoldLeft f init tau_init l tau_elt) > size_for_progress init)
|
|
=
|
|
()
|
|
#pop-options
|
|
|
|
#push-options "--fuel 1 --ifuel 0 --z3rlimit 30"
|
|
let lemma_size_fold_l (f init l: exp) (tau_init tau_elt: ty)
|
|
: Lemma (size_for_progress (EFoldLeft f init tau_init l tau_elt) > size_for_progress l)
|
|
=
|
|
()
|
|
#pop-options
|
|
|
|
#push-options "--fuel 3 --ifuel 2 --z3rlimit 50"
|
|
let rec progress (e: exp) (tau: ty)
|
|
: Lemma (requires (typing empty e tau))
|
|
(ensures (is_value e \/ (Some? (step e))))
|
|
(decreases %[size_for_progress e; 3]) =
|
|
match e with
|
|
| EApp e1 e2 tau_arg ->
|
|
progress e1 (TArrow tau_arg tau);
|
|
progress e2 tau_arg
|
|
| EIf e1 e2 e3 ->
|
|
progress e1 TBool;
|
|
progress e2 tau;
|
|
progress e3 tau;
|
|
if is_value e1 then is_bool_value_cannot_be_abs empty e1
|
|
| ESome s -> begin
|
|
match tau with
|
|
| TOption tau' -> progress s tau'
|
|
| _ -> ()
|
|
end
|
|
| ENone -> ()
|
|
| EMatchOption arg tau_some none some -> begin
|
|
progress arg (TOption tau_some);
|
|
progress none tau;
|
|
progress some (TArrow tau_some tau);
|
|
if is_value arg then
|
|
match arg with
|
|
| ESome s ->
|
|
let result_exp = EApp some s tau_some in
|
|
progress result_exp tau
|
|
| _ -> ()
|
|
else ()
|
|
end
|
|
| EList [] -> ()
|
|
| EList l -> begin
|
|
match tau with
|
|
| TList tau' ->
|
|
progress_list e l tau'
|
|
| _ -> ()
|
|
end
|
|
| ECatchEmptyError to_try catch_with ->
|
|
progress to_try tau;
|
|
progress catch_with tau
|
|
| EFoldLeft f init tau_init l tau_elt -> begin
|
|
match is_value f, is_value init, is_value l with
|
|
| false, _, _ ->
|
|
lemma_size_fold_f f init l tau_init tau_elt;
|
|
progress f (TArrow tau_init (TArrow tau_elt tau_init))
|
|
| true, false, _ ->
|
|
lemma_size_fold_init f init l tau_init tau_elt;
|
|
progress init tau_init
|
|
| true, true, false ->
|
|
lemma_size_fold_l f init l tau_init tau_elt;
|
|
progress l (TList tau_elt)
|
|
| true, true, true -> begin
|
|
match l with
|
|
| EList [] -> ()
|
|
| EList (hd::tl) ->
|
|
let result_exp = EFoldLeft
|
|
f (EApp (EApp f init tau_init) hd tau_elt)
|
|
tau_init (EList tl) tau_elt
|
|
in
|
|
lemma_size_fold_step f init hd tl tau_init tau_elt;
|
|
progress result_exp tau
|
|
| _ -> ()
|
|
end
|
|
end
|
|
| _ -> ()
|
|
and progress_list
|
|
(e: exp)
|
|
(l: list exp{size_for_progress_list l < size_for_progress e /\ l << e}) (tau: ty)
|
|
: Lemma (requires (typing_list empty l tau /\ Cons? l))
|
|
(ensures (is_value_list l \/ (is_not_bad (step_list e l))))
|
|
(decreases %[size_for_progress e; 2; l])
|
|
=
|
|
match l with
|
|
| [hd] -> if is_value hd then () else progress hd tau
|
|
| hd::tl ->
|
|
if is_value hd then progress_list e tl tau else progress hd tau
|
|
#pop-options
|
|
|
|
(*** Preservation *)
|
|
|
|
(**** Preservation helpers *)
|
|
|
|
|
|
let rec substitution_extensionnal
|
|
(s1: var_to_exp)
|
|
(s2: var_to_exp{FStar.FunctionalExtensionality.feq s1 s2})
|
|
(e: exp)
|
|
: Lemma
|
|
(requires (True))
|
|
(ensures (subst s1 e == subst s2 e))
|
|
[SMTPat (subst s1 e); SMTPat (subst s2 e)]
|
|
=
|
|
match e with
|
|
| EVar _ -> ()
|
|
| ELit _ -> ()
|
|
| EThunk e1 -> substitution_extensionnal s1 s2 e1
|
|
| EAbs t e1 ->
|
|
assert (subst s1 (EAbs t e1) == EAbs t (subst (subst_abs s1) e1))
|
|
by (T.norm [zeta; iota; delta_only [`%subst]]);
|
|
assert (subst s2 (EAbs t e1) == EAbs t (subst (subst_abs s2) e1))
|
|
by (T.norm [zeta; iota; delta_only [`%subst]]; T.smt ());
|
|
substitution_extensionnal (subst_abs s1) (subst_abs s2) e1
|
|
| EApp e1 e2 _ ->
|
|
substitution_extensionnal s1 s2 e1;
|
|
substitution_extensionnal s1 s2 e2
|
|
| EIf e1 e2 e3 ->
|
|
substitution_extensionnal s1 s2 e1;
|
|
substitution_extensionnal s1 s2 e2;
|
|
substitution_extensionnal s1 s2 e3
|
|
| ESome e1 -> substitution_extensionnal s1 s2 e1
|
|
| ENone -> ()
|
|
| EMatchOption arg _ none some ->
|
|
substitution_extensionnal s1 s2 arg;
|
|
substitution_extensionnal s1 s2 none;
|
|
substitution_extensionnal s1 s2 some
|
|
| EList l -> substitution_extensionnal_list s1 s2 l
|
|
| ECatchEmptyError to_try catch_with ->
|
|
substitution_extensionnal s1 s2 to_try;
|
|
substitution_extensionnal s1 s2 catch_with
|
|
| EFoldLeft f init _ l _ ->
|
|
substitution_extensionnal s1 s2 f;
|
|
substitution_extensionnal s1 s2 init;
|
|
substitution_extensionnal s1 s2 l
|
|
and substitution_extensionnal_list
|
|
(s1: var_to_exp)
|
|
(s2: var_to_exp{FStar.FunctionalExtensionality.feq s1 s2})
|
|
(l: list exp)
|
|
: Lemma
|
|
(requires (True))
|
|
(ensures (subst_list s1 l == subst_list s2 l))
|
|
=
|
|
match l with
|
|
| [] -> ()
|
|
| hd::tl ->
|
|
substitution_extensionnal s1 s2 hd;
|
|
substitution_extensionnal_list s1 s2 tl
|
|
|
|
let subst_typing (s: var_to_exp) (g1: env) (g2: env) =
|
|
(x:var) -> Lemma
|
|
(requires (Some? (g1 x)))
|
|
(ensures (typing g2 (s x) (Some?.v (g1 x))))
|
|
|
|
let rec substitution_preserves_typing
|
|
(g1: env)
|
|
(e: exp)
|
|
(t: ty)
|
|
(s: var_to_exp)
|
|
(g2: env)
|
|
(s_lemma: subst_typing s g1 g2)
|
|
: Lemma
|
|
(requires (typing g1 e t))
|
|
(ensures (typing g2 (subst s e) t))
|
|
(decreases %[is_var_size e; is_renaming_size s; e])
|
|
=
|
|
match e with
|
|
| EVar x -> s_lemma x
|
|
| EApp e1 e2 t_arg ->
|
|
substitution_preserves_typing g1 e1 (TArrow t_arg t) s g2 s_lemma;
|
|
substitution_preserves_typing g1 e2 t_arg s g2 s_lemma
|
|
| EThunk e1 -> begin
|
|
match t with
|
|
| TArrow TUnit t_out ->
|
|
substitution_preserves_typing g1 e1 t_out s g2 s_lemma
|
|
| _ -> ()
|
|
end
|
|
| EAbs t_arg e1 -> begin
|
|
match t with
|
|
| TArrow t_arg' t_out ->
|
|
if t_arg' <> t_arg then () else
|
|
let s_lemma' : subst_typing increment g2 (extend g2 t_arg) = fun x -> () in
|
|
let s_lemma'' : subst_typing (subst_abs s) (extend g1 t_arg) (extend g2 t_arg) = fun y ->
|
|
if y = 0 then () else
|
|
let n: var = y - 1 in
|
|
s_lemma n;
|
|
assert(typing g2 (s n) (Some?.v (g1 n)));
|
|
substitution_preserves_typing
|
|
g2
|
|
(s n)
|
|
(Some?.v (g1 n))
|
|
increment
|
|
(extend g2 t_arg)
|
|
s_lemma'
|
|
in
|
|
substitution_preserves_typing
|
|
(extend g1 t_arg)
|
|
e1
|
|
t_out
|
|
(subst_abs s)
|
|
(extend g2 t_arg)
|
|
s_lemma''
|
|
| _ -> ()
|
|
end
|
|
| ELit _ -> ()
|
|
| EIf e1 e2 e3 ->
|
|
substitution_preserves_typing g1 e1 TBool s g2 s_lemma;
|
|
substitution_preserves_typing g1 e2 t s g2 s_lemma;
|
|
substitution_preserves_typing g1 e3 t s g2 s_lemma
|
|
| ESome e1 -> begin
|
|
match t with
|
|
| TOption t' -> substitution_preserves_typing g1 e1 t' s g2 s_lemma
|
|
| _ -> ()
|
|
end
|
|
| ENone -> ()
|
|
| EMatchOption arg tau_some none some ->
|
|
substitution_preserves_typing g1 arg (TOption tau_some) s g2 s_lemma;
|
|
substitution_preserves_typing g1 none t s g2 s_lemma;
|
|
substitution_preserves_typing g1 some (TArrow tau_some t) s g2 s_lemma
|
|
| EList l -> begin
|
|
match t with
|
|
| TList t' -> substitution_preserves_typing_list g1 l t' s g2 s_lemma
|
|
| _ -> ()
|
|
end
|
|
| ECatchEmptyError to_try catch_with ->
|
|
substitution_preserves_typing g1 to_try t s g2 s_lemma;
|
|
substitution_preserves_typing g1 catch_with t s g2 s_lemma
|
|
| EFoldLeft f init tau_init l tau_elt ->
|
|
substitution_preserves_typing g1 f (TArrow tau_init (TArrow tau_elt tau_init)) s g2 s_lemma ;
|
|
substitution_preserves_typing g1 init tau_init s g2 s_lemma;
|
|
substitution_preserves_typing g1 l (TList tau_elt) s g2 s_lemma
|
|
and substitution_preserves_typing_list
|
|
(g1: env)
|
|
(l: list exp)
|
|
(t: ty)
|
|
(s: var_to_exp)
|
|
(g2: env)
|
|
(s_lemma: subst_typing s g1 g2)
|
|
: Lemma
|
|
(requires (typing_list g1 l t))
|
|
(ensures (typing_list g2 (subst_list s l) t))
|
|
(decreases %[1; is_renaming_size s; l])
|
|
=
|
|
match l with
|
|
| [] -> ()
|
|
| hd::tl ->
|
|
substitution_preserves_typing g1 hd t s g2 s_lemma;
|
|
substitution_preserves_typing_list g1 tl t s g2 s_lemma
|
|
|
|
(**** Preservation theorem *)
|
|
|
|
#push-options "--fuel 3 --ifuel 1 --z3rlimit 20"
|
|
let rec preservation (e: exp) (tau: ty)
|
|
: Lemma (requires (typing empty e tau /\ Some? (step e)))
|
|
(ensures (typing empty (Some?.v (step e)) tau))
|
|
(decreases %[ e ]) =
|
|
match e with
|
|
| ELit _ -> ()
|
|
| EVar _ -> ()
|
|
| EIf e1 e2 e3 -> if not (is_value e1) then preservation e1 TBool
|
|
| EApp e1 e2 tau_arg ->
|
|
if is_value e1
|
|
then
|
|
match e1 with
|
|
| ELit (LError _) -> ()
|
|
| _ ->
|
|
if is_value e2
|
|
then
|
|
match e1 with
|
|
| EAbs _ ebody ->
|
|
let s_lemma : subst_typing (var_to_exp_beta e2) (extend empty tau_arg) empty =
|
|
fun x -> ()
|
|
in
|
|
substitution_preserves_typing (extend empty tau_arg) ebody tau
|
|
(var_to_exp_beta e2) empty s_lemma
|
|
| _ -> ()
|
|
else preservation e2 tau_arg
|
|
else preservation e1 (TArrow tau_arg tau)
|
|
| ENone -> ()
|
|
| ESome s -> let TOption tau' = tau in if not (is_value s) then preservation s tau'
|
|
| EMatchOption arg tau_some none some ->
|
|
if not (is_value arg) then preservation arg (TOption tau_some)
|
|
| EList [] -> ()
|
|
| EList l -> let TList tau' = tau in preservation_list e l tau'
|
|
| ECatchEmptyError to_try catch_with -> if not (is_value to_try) then preservation to_try tau
|
|
| EFoldLeft f init tau_init l tau_elt -> begin
|
|
match is_value f, is_value init, is_value l with
|
|
| false, _, _ -> preservation f (TArrow tau_init (TArrow tau_elt tau_init))
|
|
| true, false, _ -> preservation init tau_init
|
|
| true, true, false -> preservation l (TList tau_elt)
|
|
| true, true, true -> ()
|
|
end
|
|
and preservation_list
|
|
(e: exp)
|
|
(l: list exp {l << e})
|
|
(tau: ty)
|
|
: Lemma
|
|
(requires (
|
|
Cons? l /\
|
|
typing empty (EList l) (TList tau) /\
|
|
is_not_bad (step_list e l)
|
|
))
|
|
(ensures (
|
|
match step_list e l with
|
|
| Good l' -> typing_list empty l' tau
|
|
| Error err -> typing empty err (TList tau)
|
|
))
|
|
(decreases %[ l ]) =
|
|
match l with
|
|
| [hd] -> begin if is_value hd then () else preservation hd tau end
|
|
| hd :: tl ->
|
|
if is_value hd then begin
|
|
typing_conserved_by_list_reduction empty l tau;
|
|
preservation_list e tl tau
|
|
end else preservation hd tau
|
|
#pop-options
|
|
|
|
(**** Other lemmas *)
|
|
|
|
let identity_var_to_exp : var_to_exp = fun x -> EVar x
|
|
|
|
#push-options "--fuel 3 --ifuel 2"
|
|
let rec subst_by_identity_is_identity (e: exp) : Lemma (subst identity_var_to_exp e == e) =
|
|
match e with
|
|
| EVar _ -> ()
|
|
| ELit _ -> ()
|
|
| EApp e1 e2 _ ->
|
|
subst_by_identity_is_identity e1;
|
|
subst_by_identity_is_identity e2
|
|
| EThunk e1 ->
|
|
subst_by_identity_is_identity e1
|
|
| EAbs t e1 ->
|
|
subst_by_identity_is_identity e1
|
|
| ENone -> ()
|
|
| ESome e1 ->
|
|
subst_by_identity_is_identity e1
|
|
| EIf e1 e2 e3 ->
|
|
subst_by_identity_is_identity e1;
|
|
subst_by_identity_is_identity e2;
|
|
subst_by_identity_is_identity e3
|
|
| EList l ->
|
|
subst_by_identity_is_identity_list l
|
|
| ECatchEmptyError to_try catch_with ->
|
|
subst_by_identity_is_identity to_try;
|
|
subst_by_identity_is_identity catch_with
|
|
| EFoldLeft f init _ l _ ->
|
|
subst_by_identity_is_identity f;
|
|
subst_by_identity_is_identity init;
|
|
subst_by_identity_is_identity l
|
|
| EMatchOption arg _ none some ->
|
|
subst_by_identity_is_identity arg;
|
|
subst_by_identity_is_identity none;
|
|
subst_by_identity_is_identity some
|
|
and subst_by_identity_is_identity_list (l: list exp) : Lemma (subst_list identity_var_to_exp l == l)
|
|
=
|
|
match l with
|
|
| [] -> ()
|
|
| hd::tl ->
|
|
subst_by_identity_is_identity hd;
|
|
subst_by_identity_is_identity_list tl
|
|
#pop-options
|
|
|
|
let typing_empty_can_be_extended (e: exp) (tau: ty) (g: env)
|
|
: Lemma
|
|
(requires (typing empty e tau))
|
|
(ensures (typing g e tau))
|
|
=
|
|
subst_by_identity_is_identity e;
|
|
let s_lemma : subst_typing identity_var_to_exp empty g = fun x -> () in
|
|
substitution_preserves_typing empty e tau identity_var_to_exp g s_lemma
|
|
|
|
let is_error (e: exp) : bool = match e with ELit (LError _) -> true | _ -> false
|