Add executable evaluator (Phase A) and type system + soundness infra (Phase B).

Phase A — GolangLean/Core/Eval.lean:
  def eval : Nat -> Heap -> Env -> Term -> Option (Value x Heap)
  Fuel-bounded recursive evaluator, total over the fuel.
  theorem eval_sound: eval succeeds => BigStep holds.
  Bridges executable computation to inductive specification.

Phase B — GolangLean/Core/Types.lean:
  inductive Ty {unit, int, bool, arrow, ref}
  TyEnv := List (String x Ty); BinOp.typeOf
  inductive HasType : TyEnv -> Term -> Ty -> Prop
  Standard simply-typed lambda calculus + ML-style references.

Phase B — GolangLean/Core/TypeSoundness.lean:
  abbrev HeapTy := Array Ty
  mutual inductive HasTypeV / HasTypeEnv  (value & env typing under heap-typing)
  def HasTypeH                            (heap conforms to heap-typing)
  def HeapTy.extends                       (prefix-extension of heap-typings)
  thm HeapTy.extends_refl, extends_trans
  thm HasTypeV.weaken, HasTypeEnv.weaken   (mutual; under heap-typing extension)
  thm HasTypeEnv.lookup_correspondence    (well-typed env yields well-typed values)

The preservation theorem itself
  HasType /\ HasTypeH /\ HasTypeEnv /\ BigStep
    ==> ∃ ht', extends /\ HasTypeH' /\ HasTypeV' /\ HasTypeEnv'
is the next deliverable; the infrastructure here is what its proof
case-analysis depends on.

Zero sorries / axioms / admits across the project. Full lake build clean.
This commit is contained in:
Maximus Gorog 2026-05-10 03:51:14 -06:00
parent 878a84e072
commit 3174918193
4 changed files with 384 additions and 0 deletions

View file

@ -14,3 +14,6 @@ import GolangLean.ValueEquiv
import GolangLean.Core.Syntax
import GolangLean.Core.Semantics
import GolangLean.Core.Determinism
import GolangLean.Core.Eval
import GolangLean.Core.Types
import GolangLean.Core.TypeSoundness

190
GolangLean/Core/Eval.lean Normal file
View file

@ -0,0 +1,190 @@
import GolangLean.Core.Semantics
namespace GolangLean.Core
/-! # Executable evaluator and soundness.
A fuel-bounded recursive evaluator
`eval : Nat → Heap → Env → Term → Option (Value × Heap)`
together with the soundness theorem
`eval n h env e = some (v, h') → BigStep h env e v h'`.
Soundness is the bridge that makes the inductive specification *runnable*.
For any closed TGC term you can write a Lean theorem of the form
`BigStep h env e v h'`
and prove it by `decide`-style execution: run `eval`, then apply
`eval_sound` to the equation. -/
def eval : Nat → Heap → Env → Term → Option (Value × Heap)
| 0, _, _, _ => none
| _ + 1, h, _, .unitT => some (.vUnit, h)
| _ + 1, h, _, .intLit k => some (.vInt k, h)
| _ + 1, h, _, .boolLit b => some (.vBool b, h)
| _ + 1, h, env, .var x =>
match env.lookup x with
| some v => some (v, h)
| none => none
| _ + 1, h, env, .lam x body => some (.vClos x body env, h)
| n + 1, h, env, .app e1 e2 =>
match eval n h env e1 with
| some (.vClos x body env', h1) =>
match eval n h1 env e2 with
| some (v2, h2) => eval n h2 (env'.extend x v2) body
| none => none
| _ => none
| n + 1, h, env, .letIn x e1 e2 =>
match eval n h env e1 with
| some (v1, h1) => eval n h1 (env.extend x v1) e2
| none => none
| n + 1, h, env, .ifte ec e1 e2 =>
match eval n h env ec with
| some (.vBool true, h1) => eval n h1 env e1
| some (.vBool false, h1) => eval n h1 env e2
| _ => none
| n + 1, h, env, .binop op e1 e2 =>
match eval n h env e1 with
| some (v1, h1) =>
match eval n h1 env e2 with
| some (v2, h2) =>
match op.apply v1 v2 with
| some v => some (v, h2)
| none => none
| none => none
| none => none
| n + 1, h, env, .refMk e =>
match eval n h env e with
| some (v, h1) => some (.vLoc h1.size, h1.push v)
| none => none
| n + 1, h, env, .deref e =>
match eval n h env e with
| some (.vLoc loc, h1) =>
match h1[loc]? with
| some v => some (v, h1)
| none => none
| _ => none
| n + 1, h, env, .assign e1 e2 =>
match eval n h env e1 with
| some (.vLoc loc, h1) =>
match eval n h1 env e2 with
| some (v2, h2) =>
if loc < h2.size then some (.vUnit, h2.set! loc v2) else none
| none => none
| _ => none
| n + 1, h, env, .seq e1 e2 =>
match eval n h env e1 with
| some (_, h1) => eval n h1 env e2
| none => none
theorem eval_sound :
∀ (n : Nat) (h : Heap) (env : Env) (e : Term) (v : Value) (h' : Heap),
eval n h env e = some (v, h') → BigStep h env e v h' := by
intro n
induction n with
| zero =>
intro h env e v h' heq
simp [eval] at heq
| succ n ih =>
intro h env e v h' heq
cases e with
| unitT =>
simp [eval] at heq
obtain ⟨rfl, rfl⟩ := heq
exact .unitR
| intLit k =>
simp [eval] at heq
obtain ⟨rfl, rfl⟩ := heq
exact .intLitR k
| boolLit b =>
simp [eval] at heq
obtain ⟨rfl, rfl⟩ := heq
exact .boolLitR b
| var x =>
simp only [eval] at heq
split at heq
next vv hL =>
simp at heq
obtain ⟨rfl, rfl⟩ := heq
exact .varR hL
next => simp at heq
| lam x body =>
simp [eval] at heq
obtain ⟨rfl, rfl⟩ := heq
exact .lamR x body
| app e1 e2 =>
simp only [eval] at heq
split at heq
next x body env' h1 heq1 =>
split at heq
next v2 h2 heq2 =>
exact .appR (ih _ _ _ _ _ heq1) (ih _ _ _ _ _ heq2) (ih _ _ _ _ _ heq)
next => simp at heq
all_goals simp at heq
| letIn x e1 e2 =>
simp only [eval] at heq
split at heq
next v1 h1 heq1 =>
exact .letInR (ih _ _ _ _ _ heq1) (ih _ _ _ _ _ heq)
next => simp at heq
| ifte ec e1 e2 =>
simp only [eval] at heq
split at heq
next h1 heq1 =>
exact .ifTR (ih _ _ _ _ _ heq1) (ih _ _ _ _ _ heq)
next h1 heq1 =>
exact .ifFR (ih _ _ _ _ _ heq1) (ih _ _ _ _ _ heq)
all_goals simp at heq
| binop op e1 e2 =>
simp only [eval] at heq
split at heq
next v1 h1 heq1 =>
split at heq
next v2 h2 heq2 =>
split at heq
next vv heqop =>
simp at heq
obtain ⟨rfl, rfl⟩ := heq
exact .binopR (ih _ _ _ _ _ heq1) (ih _ _ _ _ _ heq2) heqop
next => simp at heq
next => simp at heq
next => simp at heq
| refMk e =>
simp only [eval] at heq
split at heq
next vv h1 heq1 =>
simp at heq
obtain ⟨rfl, rfl⟩ := heq
exact .refMkR (ih _ _ _ _ _ heq1)
next => simp at heq
| deref e =>
simp only [eval] at heq
split at heq
next loc h1 heq1 =>
split at heq
next vv hget =>
simp at heq
obtain ⟨rfl, rfl⟩ := heq
exact .derefR (ih _ _ _ _ _ heq1) hget
next => simp at heq
all_goals simp at heq
| assign e1 e2 =>
simp only [eval] at heq
split at heq
next loc h1 heq1 =>
split at heq
next v2 h2 heq2 =>
split at heq
next hb =>
simp at heq
obtain ⟨rfl, rfl⟩ := heq
exact .assignR (ih _ _ _ _ _ heq1) (ih _ _ _ _ _ heq2) hb
next => simp at heq
next => simp at heq
all_goals simp at heq
| seq e1 e2 =>
simp only [eval] at heq
split at heq
next _ h1 heq1 =>
exact .seqR (ih _ _ _ _ _ heq1) (ih _ _ _ _ _ heq)
next => simp at heq
end GolangLean.Core

View file

@ -0,0 +1,101 @@
import GolangLean.Core.Types
import GolangLean.Core.Semantics
namespace GolangLean.Core
/-! # Type soundness for TGC.
Big-step preservation: if `Γ ⊢ e : T` and `BigStep h env e v h'` under
compatible heap/env typings, then there exists an extended heap-typing
under which `v : T` and the new heap is well-typed. There is no
small-step "progress" analogue — big-step proves "if it terminates, the
result has the expected type". -/
abbrev HeapTy := Array Ty
mutual
inductive HasTypeV : HeapTy → Value → Ty → Prop where
| vUnit {ht} : HasTypeV ht .vUnit .unit
| vInt {ht} (n : Int) : HasTypeV ht (.vInt n) .int
| vBool {ht} (b : Bool) : HasTypeV ht (.vBool b) .bool
| vLoc {ht loc T} (h : (ht[loc]? : Option Ty) = some T) :
HasTypeV ht (.vLoc loc) (.ref T)
| vClos {ht x body env Γ T_arg T_ret}
(he : HasTypeEnv ht env Γ)
(hb : HasType (Γ.extend x T_arg) body T_ret) :
HasTypeV ht (.vClos x body env) (.arrow T_arg T_ret)
inductive HasTypeEnv : HeapTy → Env → TyEnv → Prop where
| nil {ht} : HasTypeEnv ht .nil []
| cons {ht x v T env Γ}
(hv : HasTypeV ht v T)
(he : HasTypeEnv ht env Γ) :
HasTypeEnv ht (.cons x v env) ((x, T) :: Γ)
end
def HasTypeH (ht : HeapTy) (h : Heap) : Prop :=
ht.size = h.size ∧
∀ (i : Nat) (T : Ty), (ht[i]? : Option Ty) = some T →
∃ v, (h[i]? : Option Value) = some v ∧ HasTypeV ht v T
def HeapTy.extends (ht ht' : HeapTy) : Prop :=
ht.size ≤ ht'.size ∧
∀ (i : Nat) (T : Ty), (ht[i]? : Option Ty) = some T → (ht'[i]? : Option Ty) = some T
namespace HeapTy
theorem extends_refl (ht : HeapTy) : ht.extends ht :=
⟨Nat.le_refl _, fun _ _ h => h⟩
theorem extends_trans {ht₁ ht₂ ht₃ : HeapTy}
(h₁ : ht₁.extends ht₂) (h₂ : ht₂.extends ht₃) : ht₁.extends ht₃ :=
⟨Nat.le_trans h₁.1 h₂.1, fun i T h => h₂.2 i T (h₁.2 i T h)⟩
end HeapTy
/-! ## Weakening under heap-typing extension. -/
mutual
theorem HasTypeV.weaken {ht ht' v T}
(h : HasTypeV ht v T) (ext : HeapTy.extends ht ht') :
HasTypeV ht' v T := by
cases h with
| vUnit => exact .vUnit
| vInt n => exact .vInt n
| vBool b => exact .vBool b
| vLoc hLoc => exact .vLoc (ext.2 _ _ hLoc)
| vClos he hb => exact .vClos (HasTypeEnv.weaken he ext) hb
theorem HasTypeEnv.weaken {ht ht' env Γ}
(h : HasTypeEnv ht env Γ) (ext : HeapTy.extends ht ht') :
HasTypeEnv ht' env Γ := by
cases h with
| nil => exact .nil
| cons hv he => exact .cons (HasTypeV.weaken hv ext) (HasTypeEnv.weaken he ext)
end
/-! ## Lookup correspondence: well-typed env produces well-typed values. -/
theorem HasTypeEnv.lookup_correspondence :
∀ {ht env Γ x T},
HasTypeEnv ht env Γ → Γ.lookup x = some T →
∃ v, env.lookup x = some v ∧ HasTypeV ht v T
| _, _, _, _, _, .nil, hL => by simp [TyEnv.lookup] at hL
| _, _, _, x, _, @HasTypeEnv.cons _ x' v' T' env' Γ' hv he, hL => by
by_cases hx : x' = x
· subst hx
simp only [TyEnv.lookup] at hL
simp at hL
cases hL
refine ⟨v', ?_, hv⟩
simp [EnvList.lookup]
· simp only [TyEnv.lookup, if_neg hx] at hL
have ⟨v, hLv, hTv⟩ := lookup_correspondence he hL
refine ⟨v, ?_, hTv⟩
simp [EnvList.lookup, hx, hLv]
end GolangLean.Core

View file

@ -0,0 +1,90 @@
import GolangLean.Core.Syntax
namespace GolangLean.Core
/-! # Static type system for TGC.
Five base types: `unit`, `int`, `bool`, `arrow τ₁ τ₂`, `ref τ`. Typing
context `TyEnv` is a list of (name, type) bindings — innermost-first to
match `Env`.
Typing rules `HasType : TyEnv → Term → Ty → Prop` follow the standard
simply-typed lambda calculus + ML-style references discipline. -/
inductive Ty where
| unit : Ty
| int : Ty
| bool : Ty
| arrow : Ty → Ty → Ty
| ref : Ty → Ty
deriving Repr, BEq, DecidableEq, Inhabited
abbrev TyEnv := List (String × Ty)
namespace TyEnv
def lookup : TyEnv → String → Option Ty
| [], _ => none
| (k, T) :: rs, x => if k = x then some T else lookup rs x
def extend (Γ : TyEnv) (x : String) (T : Ty) : TyEnv :=
(x, T) :: Γ
end TyEnv
namespace BinOp
def typeOf : BinOp → Ty → Ty → Option Ty
| .add, .int, .int => some .int
| .sub, .int, .int => some .int
| .mul, .int, .int => some .int
| .eq, .int, .int => some .bool
| .eq, .bool, .bool => some .bool
| .lt, .int, .int => some .bool
| _, _, _ => none
end BinOp
inductive HasType : TyEnv → Term → Ty → Prop where
| unitT {Γ} : HasType Γ .unitT .unit
| intLit {Γ} (n : Int) : HasType Γ (.intLit n) .int
| boolLit {Γ} (b : Bool) : HasType Γ (.boolLit b) .bool
| var {Γ x T} (h : Γ.lookup x = some T) :
HasType Γ (.var x) T
| lam {Γ x body T_arg T_ret}
(h : HasType (Γ.extend x T_arg) body T_ret) :
HasType Γ (.lam x body) (.arrow T_arg T_ret)
| app {Γ e1 e2 T_arg T_ret}
(h1 : HasType Γ e1 (.arrow T_arg T_ret))
(h2 : HasType Γ e2 T_arg) :
HasType Γ (.app e1 e2) T_ret
| letIn {Γ x e1 e2 T1 T2}
(h1 : HasType Γ e1 T1)
(h2 : HasType (Γ.extend x T1) e2 T2) :
HasType Γ (.letIn x e1 e2) T2
| ifte {Γ ec e1 e2 T}
(hc : HasType Γ ec .bool)
(h1 : HasType Γ e1 T)
(h2 : HasType Γ e2 T) :
HasType Γ (.ifte ec e1 e2) T
| binop {Γ op e1 e2 T1 T2 T}
(h1 : HasType Γ e1 T1)
(h2 : HasType Γ e2 T2)
(hOp : op.typeOf T1 T2 = some T) :
HasType Γ (.binop op e1 e2) T
| refMk {Γ e T}
(h : HasType Γ e T) :
HasType Γ (.refMk e) (.ref T)
| deref {Γ e T}
(h : HasType Γ e (.ref T)) :
HasType Γ (.deref e) T
| assign {Γ e1 e2 T}
(h1 : HasType Γ e1 (.ref T))
(h2 : HasType Γ e2 T) :
HasType Γ (.assign e1 e2) .unit
| seq {Γ e1 e2 T1 T2}
(h1 : HasType Γ e1 T1)
(h2 : HasType Γ e2 T2) :
HasType Γ (.seq e1 e2) T2
end GolangLean.Core