diff --git a/Cslib/Languages/LambdaCalculus/LocallyNameless/Context.lean b/Cslib/Languages/LambdaCalculus/LocallyNameless/Context.lean index c1c20a96..b5f11ca2 100644 --- a/Cslib/Languages/LambdaCalculus/LocallyNameless/Context.lean +++ b/Cslib/Languages/LambdaCalculus/LocallyNameless/Context.lean @@ -17,43 +17,96 @@ Contexts as pairs of free variables and types. universe u v -variable {Var : Type u} {Ty : Type v} [DecidableEq Var] +variable {α : Type u} {β : Type v} + +-- TODO: These are pieces of API that cannot be directly automated by adding `grind` attributes to +-- `Mathlib.Data.List.Sigma`. We should consider upstreaming them to Mathlib. +namespace List + +variable {β : α → Type v} {l₁ l₂ : List (Sigma β)} + +/-- Keys distribute with appending. -/ +theorem keys_append : (l₁ ++ l₂).keys = l₁.keys ++ l₂.keys := by + simp [keys] + +variable [DecidableEq α] in +/-- Sublists without duplicate keys preserve lookups. -/ +theorem sublist_dlookup (nd₂ : l₂.NodupKeys) (s : l₁ <+ l₂) (mem : b ∈ l₁.dlookup a) : + b ∈ l₂.dlookup a := by + grind [Option.mem_def, → perm_nodupKeys, dlookup_append, => perm_dlookup, + → Sublist.exists_perm_append] + +@[grind =] +lemma nodupKeys_middle : (l₁ ++ s :: l₂).NodupKeys ↔ (s :: (l₁ ++ l₂)).NodupKeys := by + simp_all [NodupKeys, keys, nodup_middle] + +end List namespace LambdaCalculus.LocallyNameless +variable [DecidableEq α] + /-- A typing context is a list of free variables and corresponding types. -/ -abbrev Context (Var : Type u) (Ty : Type v) := List ((_ : Var) × Ty) +abbrev Context (α : Type u) (β : Type v) := List ((_ : α) × β) namespace Context open List -/-- The domain of a context is the finite set of free variables it uses. -/ -@[simp, grind =] -def dom : Context Var Ty → Finset Var := toFinset ∘ keys +-- we would like grind to see through certain notations +attribute [scoped grind =] Option.mem_def +attribute [scoped grind _=_] List.append_eq +attribute [scoped grind =] List.Nodup +attribute [scoped grind =] List.NodupKeys +attribute [scoped grind _=_] List.singleton_append -/-- A well-formed context. -/ -abbrev Ok : Context Var Ty → Prop := NodupKeys +-- a few grinds on Option: +attribute [scoped grind =] Option.or_eq_some_iff +attribute [scoped grind =] Option.or_eq_none_iff -instance : HasWellFormed (Context Var Ty) := - ⟨Ok⟩ +-- we would like grind to treat list and finset membership the same +attribute [scoped grind _=_] List.mem_toFinset -variable {Γ Δ : Context Var Ty} +-- otherwise, we mostly reuse existing API in `Mathlib.Data.List.Sigma` +attribute [scoped grind =] List.keys_cons +attribute [scoped grind =] List.dlookup_cons_eq +attribute [scoped grind =] List.dlookup_cons_ne +attribute [scoped grind =] List.dlookup_nil +attribute [scoped grind _=_] List.dlookup_isSome +attribute [scoped grind →] List.perm_nodupKeys -/-- Context membership is preserved on permuting a context. -/ -theorem dom_perm_mem_iff (h : Γ.Perm Δ) {x : Var} : x ∈ Γ.dom ↔ x ∈ Δ.dom := by - induction h <;> simp_all only [dom, Function.comp_apply, mem_toFinset, keys_cons, mem_cons] - grind +/-- The domain of a context is the finite set of free variables it uses. -/ +@[simp, grind =] +def dom (Γ : Context α β) : Finset α := Γ.keys.toFinset -omit [DecidableEq Var] in -/-- Context well-formedness is preserved on permuting a context. -/ -@[scoped grind →] -theorem wf_perm (h : Γ.Perm Δ) : Γ✓ → Δ✓ := (List.perm_nodupKeys h).mp +instance : HasWellFormed (Context α β) := + ⟨NodupKeys⟩ + +omit [DecidableEq α] in +@[scoped grind _=_] +theorem haswellformed_def (Γ : Context α β) : Γ✓ = Γ.NodupKeys := by rfl + +variable {Γ Δ : Context α β} -omit [DecidableEq Var] in +omit [DecidableEq α] in /-- Context well-formedness is preserved on removing an element. -/ @[scoped grind →] theorem wf_strengthen (ok : (Δ ++ ⟨x, σ⟩ :: Γ)✓) : (Δ ++ Γ)✓ := by - exact List.NodupKeys.sublist (by simp) ok + grind [keys_append] + +/-- A mapping of values within a context. -/ +@[simp, scoped grind] +def map_val (f : β → β) (Γ : Context α β) : Context α β := + Γ.map (fun ⟨var,ty⟩ => ⟨var,f ty⟩) + +omit [DecidableEq α] in +/-- A mapping of values preserves keys. -/ +@[scoped grind] +lemma map_val_keys (f) : Γ.keys = (Γ.map_val f).keys := by + induction Γ <;> grind + +/-- A mapping of values maps lookups. -/ +lemma map_val_mem (mem : σ ∈ Γ.dlookup x) (f) : f σ ∈ (Γ.map_val f).dlookup x := by + induction Γ <;> grind end LambdaCalculus.LocallyNameless.Context diff --git a/Cslib/Languages/LambdaCalculus/LocallyNameless/Fsub/Basic.lean b/Cslib/Languages/LambdaCalculus/LocallyNameless/Fsub/Basic.lean new file mode 100644 index 00000000..ce0770cb --- /dev/null +++ b/Cslib/Languages/LambdaCalculus/LocallyNameless/Fsub/Basic.lean @@ -0,0 +1,107 @@ +/- +Copyright (c) 2025 Chris Henson. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Chris Henson +-/ + +import Cslib.Foundations.Data.HasFresh +import Cslib.Foundations.Syntax.HasSubstitution +import Cslib.Languages.LambdaCalculus.LocallyNameless.Context + +/-! # λ-calculus + +The λ-calculus with polymorphism and subtyping, with a locally nameless representation of syntax. + +## References + +* [A. Chargueraud, *The Locally Nameless Representation*][Chargueraud2012] +* See also , from which + this is adapted + +-/ + +variable {Var : Type*} [HasFresh Var] [DecidableEq Var] + +namespace LambdaCalculus.LocallyNameless.Fsub + +/-- Types of the polymorphic lambda calculus. -/ +inductive Ty (Var : Type*) + /-- The type ⊤, with a single inhabitant. -/ + | top : Ty Var + /-- Bound variables that appear in a type, using a de-Bruijn index. -/ + | bvar : ℕ → Ty Var + /-- Free type variables. -/ + | fvar : Var → Ty Var + /-- A function type. -/ + | arrow : Ty Var → Ty Var → Ty Var + /-- A universal quantification. -/ + | all : Ty Var → Ty Var → Ty Var + /-- A sum type. -/ + | sum : Ty Var → Ty Var → Ty Var + deriving Inhabited + +/-- Syntax of locally nameless lambda terms, with free variables over `Var`. -/ +inductive Term (Var : Type*) + /-- Bound term variables that appear under a lambda abstraction, using a de-Bruijn index. -/ + | bvar : ℕ → Term Var + /-- Free term variables. -/ + | fvar : Var → Term Var + /-- Lambda abstraction, introducing a new bound term variable. -/ + | abs : Ty Var → Term Var → Term Var + /-- Function application. -/ + | app : Term Var → Term Var → Term Var + /-- Type abstraction, introducing a new bound type variable. -/ + | tabs : Ty Var → Term Var → Term Var + /-- Type application. -/ + | tapp : Term Var → Ty Var → Term Var + /-- Binding of a term. -/ + | let' : Term Var → Term Var → Term Var + /-- Left constructor of a sum. -/ + | inl : Term Var → Term Var + /-- Right constructor of a sum. -/ + | inr : Term Var → Term Var + /-- Case matching on a sum. -/ + | case : Term Var → Term Var → Term Var → Term Var + +/-- A context binding. -/ +inductive Binding (Var : Type*) + /-- Subtype binding. -/ + | sub : Ty Var → Binding Var + /-- Type binding. -/ + | ty : Ty Var → Binding Var + deriving Inhabited + +/-- Free variables of a type. -/ +@[scoped grind =] +def Ty.fv : Ty Var → Finset Var +| top | bvar _ => {} +| fvar X => {X} +| arrow σ τ | all σ τ | sum σ τ => σ.fv ∪ τ.fv + +/-- Free variables of a binding. -/ +@[scoped grind =] +def Binding.fv : Binding Var → Finset Var +| sub σ | ty σ => σ.fv + +/-- Free type variables of a term. -/ +@[scoped grind =] +def Term.fv_ty : Term Var → Finset Var +| bvar _ | fvar _ => {} +| abs σ t₁ | tabs σ t₁ | tapp t₁ σ => σ.fv ∪ t₁.fv_ty +| inl t₁ | inr t₁ => t₁.fv_ty +| app t₁ t₂ | let' t₁ t₂ => t₁.fv_ty ∪ t₂.fv_ty +| case t₁ t₂ t₃ => t₁.fv_ty ∪ t₂.fv_ty ∪ t₃.fv_ty + +/-- Free term variables of a term. -/ +@[scoped grind =] +def Term.fv_tm : Term Var → Finset Var +| bvar _ => {} +| fvar x => {x} +| abs _ t₁ | tabs _ t₁ | tapp t₁ _ | inl t₁ | inr t₁ => t₁.fv_tm +| app t₁ t₂ | let' t₁ t₂ => t₁.fv_tm ∪ t₂.fv_tm +| case t₁ t₂ t₃ => t₁.fv_tm ∪ t₂.fv_tm ∪ t₃.fv_tm + +/-- A context of bindings. -/ +abbrev Env (Var : Type*) := Context Var (Binding Var) + +end LambdaCalculus.LocallyNameless.Fsub diff --git a/Cslib/Languages/LambdaCalculus/LocallyNameless/Fsub/Opening.lean b/Cslib/Languages/LambdaCalculus/LocallyNameless/Fsub/Opening.lean new file mode 100644 index 00000000..68bc3b69 --- /dev/null +++ b/Cslib/Languages/LambdaCalculus/LocallyNameless/Fsub/Opening.lean @@ -0,0 +1,416 @@ +/- +Copyright (c) 2025 Chris Henson. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Chris Henson +-/ + +import Cslib.Languages.LambdaCalculus.LocallyNameless.Fsub.Basic + + +/-! # λ-calculus + +The λ-calculus with polymorphism and subtyping, with a locally nameless representation of syntax. +This file defines opening, local closure, and substitution. + +## References + +* [A. Chargueraud, *The Locally Nameless Representation*][Chargueraud2012] +* See also , from which + this is adapted + +-/ + +variable {Var : Type*} [HasFresh Var] [DecidableEq Var] + +namespace LambdaCalculus.LocallyNameless.Fsub + +namespace Ty + +/-- Variable opening (type opening to type) of the ith bound variable. -/ +@[scoped grind =] +def openRec (X : ℕ) (δ : Ty Var) : Ty Var → Ty Var +| top => top +| bvar Y => if X = Y then δ else bvar Y +| fvar X => fvar X +| arrow σ τ => arrow (openRec X δ σ) (openRec X δ τ) +| all σ τ => all (openRec X δ σ) (openRec (X + 1) δ τ) +| sum σ τ => sum (openRec X δ σ) (openRec X δ τ) + +scoped notation:68 γ "⟦" X " ↝ " δ "⟧ᵞ"=> openRec X δ γ + +/-- Variable opening (type opening to type) of the closest binding. -/ +@[scoped grind =] +def open' (γ δ : Ty Var) := openRec 0 δ γ + +scoped infixr:80 " ^ᵞ " => open' + +/-- Locally closed types. -/ +inductive LC : Ty Var → Prop + | top : LC top + | var : LC (fvar X) + | arrow : LC σ → LC τ → LC (arrow σ τ) + | all (L : Finset Var) : LC σ → (∀ X ∉ L, LC (τ ^ᵞ fvar X)) → LC (all σ τ) + | sum : LC σ → LC τ → LC (sum σ τ) + +attribute [scoped grind] LC.top LC.var LC.arrow LC.sum + +/-- Type substitution. -/ +@[scoped grind =] +def subst (X : Var) (δ : Ty Var) : Ty Var → Ty Var +| top => top +| bvar J => bvar J +| fvar Y => if Y = X then δ else fvar Y +| arrow σ τ => arrow (subst X δ σ) (subst X δ τ) +| all σ τ => all (subst X δ σ) (subst X δ τ) +| sum σ τ => sum (subst X δ σ) (subst X δ τ) + +instance : HasSubstitution (Ty Var) Var (Ty Var) where + subst γ X δ := Ty.subst X δ γ + +variable {σ τ δ γ : Ty Var} + +omit [HasFresh Var] [DecidableEq Var] in +/-- An opening appearing in both sides of an equality of types can be removed. -/ +lemma openRec_neq_eq {σ τ γ : Ty Var} (neq : X ≠ Y) (h : σ⟦Y ↝ τ⟧ᵞ = σ⟦Y ↝ τ⟧ᵞ⟦X ↝ γ⟧ᵞ) : + σ = σ⟦X ↝ γ⟧ᵞ := by induction σ generalizing Y X <;> grind + +/-- A locally closed type is unchanged by opening. -/ +lemma openRec_lc {σ τ : Ty Var} (lc : σ.LC) : σ = σ⟦X ↝ τ⟧ᵞ := by + induction lc generalizing X <;> (have := fresh_exists <| free_union Var; grind [openRec_neq_eq]) + +omit [HasFresh Var] in +@[scoped grind _=_] +lemma subst_def : Ty.subst (X : Var) (δ : Ty Var) (γ : Ty Var) = γ[X := δ] := by rfl + +omit [HasFresh Var] in +/-- Substitution of a free variable not present in a type leaves it unchanged. -/ +lemma subst_fresh (nmem : X ∉ γ.fv) (δ : Ty Var) : γ = γ[X := δ] := by + induction γ <;> grind + +/-- Substitution of a locally closed type distributes with opening. -/ +lemma openRec_subst (Y : ℕ) (σ τ : Ty Var) (lc : δ.LC) (X : Var) : + (σ⟦Y ↝ τ⟧ᵞ)[X := δ] = σ[X := δ]⟦Y ↝ τ[X := δ]⟧ᵞ := by + induction σ generalizing Y + all_goals grind [openRec_lc] + +/-- Specialize `Ty.openRec_subst` to the first opening. -/ +lemma open_subst (σ τ : Ty Var) (lc : δ.LC) (X : Var) : (σ ^ᵞ τ)[X := δ] = σ[X := δ] ^ᵞ τ[X := δ] + := openRec_subst 0 σ τ lc X + +/-- Specialize `Ty.subst_open` to free variables. -/ +lemma open_subst_var (σ : Ty Var) (neq : Y ≠ X) (lc : δ.LC) : + (σ ^ᵞ fvar Y)[X := δ] = (σ[X := δ]) ^ᵞ fvar Y := by grind [open_subst] + +omit [HasFresh Var] in +/-- Opening to a type is equivalent to opening to a free variable and substituting. -/ +lemma openRec_subst_intro (Y : ℕ) (δ : Ty Var) (nmem : X ∉ γ.fv) : + γ⟦Y ↝ δ⟧ᵞ = (γ⟦Y ↝ fvar X⟧ᵞ)[X := δ] := by + induction γ generalizing δ Y <;> grind + +omit [HasFresh Var] in +/-- Specialize `Ty.openRec_subst_intro` to the first opening. -/ +lemma open_subst_intro (δ : Ty Var) (nmem : X ∉ γ.fv) : γ ^ᵞ δ = (γ ^ᵞ fvar X)[X := δ] := + openRec_subst_intro _ _ nmem + +lemma subst_lc (σ_lc : σ.LC) (τ_lc : τ.LC) (X : Var) : σ[X := τ].LC := by + induction σ_lc + case all => apply LC.all (free_union Var) <;> grind [openRec_subst] + all_goals grind [openRec_subst] + +omit [HasFresh Var] in +lemma nmem_fv_openRec (nmem : X ∉ (σ⟦k ↝ γ⟧ᵞ).fv) : X ∉ σ.fv := by + induction σ generalizing k <;> grind + +omit [HasFresh Var] in +lemma nmem_fv_open (nmem : X ∉ (σ ^ᵞ γ).fv) : X ∉ σ.fv := + Ty.nmem_fv_openRec (k := 0) nmem + +end Ty + +namespace Term + +open scoped Ty + +/-- Variable opening (term opening to type) of the ith bound variable. -/ +@[scoped grind =] +def openRec_ty (X : ℕ) (δ : Ty Var) : Term Var → Term Var +| bvar x => bvar x +| fvar x => fvar x +| abs σ t₁ => abs (σ⟦X ↝ δ⟧ᵞ) (openRec_ty X δ t₁) +| app t₁ t₂ => app (openRec_ty X δ t₁) (openRec_ty X δ t₂) +| tabs σ t₁ => tabs (σ⟦X ↝ δ⟧ᵞ) (openRec_ty (X + 1) δ t₁) +| tapp t₁ σ => tapp (openRec_ty X δ t₁) (σ⟦X ↝ δ⟧ᵞ) +| let' t₁ t₂ => let' (openRec_ty X δ t₁) (openRec_ty X δ t₂) +| inl t₁ => inl (openRec_ty X δ t₁) +| inr t₂ => inr (openRec_ty X δ t₂) +| case t₁ t₂ t₃ => case (openRec_ty X δ t₁) (openRec_ty X δ t₂) (openRec_ty X δ t₃) + +scoped notation:68 t "⟦" X " ↝ " δ "⟧ᵗᵞ"=> openRec_ty X δ t + +/-- Variable opening (term opening to type) of the closest binding. -/ +@[scoped grind =] +def open_ty (t : Term Var) (δ : Ty Var) := openRec_ty 0 δ t + +scoped infixr:80 " ^ᵗᵞ " => open_ty + +/-- Variable opening (term opening to term) of the ith bound variable. -/ +@[scoped grind =] +def openRec_tm (x : ℕ) (s : Term Var) : Term Var → Term Var +| bvar y => if x = y then s else (bvar y) +| fvar x => fvar x +| abs σ t₁ => abs σ (openRec_tm (x + 1) s t₁) +| app t₁ t₂ => app (openRec_tm x s t₁) (openRec_tm x s t₂) +| tabs σ t₁ => tabs σ (openRec_tm x s t₁) +| tapp t₁ σ => tapp (openRec_tm x s t₁) σ +| let' t₁ t₂ => let' (openRec_tm x s t₁) (openRec_tm (x + 1) s t₂) +| inl t₁ => inl (openRec_tm x s t₁) +| inr t₂ => inr (openRec_tm x s t₂) +| case t₁ t₂ t₃ => case (openRec_tm x s t₁) (openRec_tm (x + 1) s t₂) (openRec_tm (x + 1) s t₃) + +scoped notation:68 t "⟦" x " ↝ " s "⟧ᵗᵗ"=> openRec_tm x s t + +/-- Variable opening (term opening to term) of the closest binding. -/ +@[scoped grind =] +def open_tm (t₁ t₂ : Term Var) := openRec_tm 0 t₂ t₁ + +scoped infixr:80 " ^ᵗᵗ " => open_tm + +/-- Locally closed terms. -/ +inductive LC : Term Var → Prop + | var : LC (fvar x) + | abs (L : Finset Var) : σ.LC → (∀ x ∉ L, LC (t₁ ^ᵗᵗ fvar x)) → LC (abs σ t₁) + | app : LC t₁ → LC t₂ → LC (app t₁ t₂) + | tabs (L : Finset Var) : σ.LC → (∀ X ∉ L, LC (t₁ ^ᵗᵞ .fvar X)) → LC (tabs σ t₁) + | tapp : LC t₁ → σ.LC → LC (tapp t₁ σ) + | let' (L : Finset Var) : LC t₁ → (∀ x ∉ L, LC (t₂ ^ᵗᵗ fvar x)) → LC (let' t₁ t₂) + | inl : LC t₁ → LC (inl t₁) + | inr : LC t₁ → LC (inr t₁) + | case (L : Finset Var) : + LC t₁ → + (∀ x ∉ L, LC (t₂ ^ᵗᵗ fvar x)) → + (∀ x ∉ L, LC (t₃ ^ᵗᵗ fvar x)) → + LC (case t₁ t₂ t₃) + +attribute [scoped grind] LC.var LC.app LC.inl LC.inr LC.tapp + +variable {t : Term Var} {δ : Ty Var} + +omit [HasFresh Var] [DecidableEq Var] in +/-- An opening (term to type) appearing in both sides of an equality of terms can be removed. -/ +lemma openRec_ty_neq_eq (neq : X ≠ Y) (eq : t⟦Y ↝ σ⟧ᵗᵞ = t⟦Y ↝ σ⟧ᵗᵞ⟦X ↝ τ⟧ᵗᵞ) : + t = t⟦X ↝ τ⟧ᵗᵞ := by + induction t generalizing X Y <;> grind [Ty.openRec_neq_eq] + +omit [HasFresh Var] [DecidableEq Var] in +/-- Elimination of mixed term and type opening. -/ +@[scoped grind] +lemma openRec_tm_ty_eq (eq : t⟦x ↝ s⟧ᵗᵗ = t⟦x ↝ s⟧ᵗᵗ⟦y ↝ δ⟧ᵗᵞ) : t = t⟦y ↝ δ⟧ᵗᵞ + := by induction t generalizing x y <;> grind + +/-- A locally closed term is unchanged by type opening. -/ +@[scoped grind] +lemma openRec_ty_lc {t : Term Var} (lc : t.LC) : t = t⟦X ↝ σ⟧ᵗᵞ := by + induction lc generalizing X + case let' | case | tabs | abs => + have := fresh_exists <| free_union Var + congr <;> grind [Ty.openRec_lc, openRec_ty_neq_eq] + all_goals grind [Ty.openRec_lc] + +/-- Substitution of a type within a term. -/ +@[scoped grind =] +def subst_ty (X : Var) (δ : Ty Var) : Term Var → Term Var +| bvar x => bvar x +| fvar x => fvar x +| abs σ t₁ => abs (σ [X := δ]) (subst_ty X δ t₁) +| app t₁ t₂ => app (subst_ty X δ t₁) (subst_ty X δ t₂) +| tabs σ t₁ => tabs (σ [X := δ]) (subst_ty X δ t₁) +| tapp t₁ σ => tapp (subst_ty X δ t₁) (σ[X := δ]) +| let' t₁ t₂ => let' (subst_ty X δ t₁) (subst_ty X δ t₂) +| inl t₁ => inl (subst_ty X δ t₁) +| inr t₁ => inr (subst_ty X δ t₁) +| case t₁ t₂ t₃ => case (subst_ty X δ t₁) (subst_ty X δ t₂) (subst_ty X δ t₃) + +instance : HasSubstitution (Term Var) Var (Ty Var) where + subst t X δ := Term.subst_ty X δ t + +omit [HasFresh Var] in +@[scoped grind _=_] +lemma subst_ty_def : subst_ty (X : Var) (δ : Ty Var) (t : Term Var) = t[X := δ] := by rfl + +omit [HasFresh Var] in +/-- Substitution of a free type variable not present in a term leaves it unchanged. -/ +lemma subst_ty_fresh (nmem : X ∉ t.fv_ty) (δ : Ty Var) : t = t [X := δ] := + by induction t <;> grind [Ty.subst_fresh] + +/-- Substitution of a locally closed type distributes with term opening to a type . -/ +lemma openRec_ty_subst_ty (Y : ℕ) (t : Term Var) (σ : Ty Var) (lc : δ.LC) (X : Var) : + (t⟦Y ↝ σ⟧ᵗᵞ)[X := δ] = (t[X := δ])⟦Y ↝ σ[X := δ]⟧ᵗᵞ := by + induction t generalizing Y <;> grind [Ty.openRec_subst] + +/-- Specialize `Term.openRec_ty_subst` to the first opening. -/ +lemma open_ty_subst_ty (t : Term Var) (σ : Ty Var) (lc : δ.LC) (X : Var) : + (t ^ᵗᵞ σ)[X := δ] = t[X := δ] ^ᵗᵞ σ[X := δ] := openRec_ty_subst_ty 0 t σ lc X + +/-- Specialize `Term.open_ty_subst` to free type variables. -/ +lemma open_ty_subst_ty_var (t : Term Var) (neq : Y ≠ X) (lc : δ.LC) : + (t ^ᵗᵞ .fvar Y)[X := δ] = t[X := δ] ^ᵗᵞ .fvar Y := by grind [open_ty_subst_ty] + +omit [HasFresh Var] + +/-- Opening a term to a type is equivalent to opening to a free variable and substituting. -/ +lemma openRec_ty_subst_ty_intro (Y : ℕ) (t : Term Var) (nmem : X ∉ t.fv_ty) : + t⟦Y ↝ δ⟧ᵗᵞ = (t⟦Y ↝ Ty.fvar X⟧ᵗᵞ)[X := δ] := by + induction t generalizing X δ Y <;> grind [Ty.openRec_subst_intro] + +/-- Specialize `Term.openRec_ty_subst_ty_intro` to the first opening. -/ +lemma open_ty_subst_ty_intro (t : Term Var) (δ : Ty Var) (nmem : X ∉ t.fv_ty) : + t ^ᵗᵞ δ = (t ^ᵗᵞ Ty.fvar X)[X := δ] := openRec_ty_subst_ty_intro _ _ nmem + +/-- Substitution of a term within a term. -/ +@[scoped grind =] +def subst_tm (x : Var) (s : Term Var) : Term Var → Term Var +| bvar x => bvar x +| fvar y => if y = x then s else fvar y +| abs σ t₁ => abs σ (subst_tm x s t₁) +| app t₁ t₂ => app (subst_tm x s t₁) (subst_tm x s t₂) +| tabs σ t₁ => tabs σ (subst_tm x s t₁) +| tapp t₁ σ => tapp (subst_tm x s t₁) σ +| let' t₁ t₂ => let' (subst_tm x s t₁) (subst_tm x s t₂) +| inl t₁ => inl (subst_tm x s t₁) +| inr t₁ => inr (subst_tm x s t₁) +| case t₁ t₂ t₃ => case (subst_tm x s t₁) (subst_tm x s t₂) (subst_tm x s t₃) + +instance : HasSubstitution (Term Var) Var (Term Var) where + subst t x s := Term.subst_tm x s t + +@[scoped grind _=_] +lemma subst_tm_def : subst_tm (x : Var) (s : Term Var) (t : Term Var) = t[x := s] := by rfl + +omit [DecidableEq Var] in +/-- An opening (term to term) appearing in both sides of an equality of terms can be removed. -/ +lemma openRec_tm_neq_eq (neq : x ≠ y) (eq : t⟦y ↝ s₁⟧ᵗᵗ = t⟦y ↝ s₁⟧ᵗᵗ⟦x ↝ s₂⟧ᵗᵗ) : + t = t⟦x ↝ s₂⟧ᵗᵗ := by + induction t generalizing x y <;> grind + +omit [DecidableEq Var] in +/-- Elimination of mixed term and type opening. -/ +lemma openRec_ty_tm_eq (eq : t⟦Y ↝ σ⟧ᵗᵞ = t⟦Y ↝ σ⟧ᵗᵞ⟦x ↝ s⟧ᵗᵗ) : t = t⟦x ↝ s⟧ᵗᵗ := by + induction t generalizing x Y <;> grind + +variable [HasFresh Var] + +/-- A locally closed term is unchanged by term opening. -/ +@[scoped grind] +lemma openRec_tm_lc (lc : t.LC) : t = t⟦x ↝ s⟧ᵗᵗ := by + induction lc generalizing x + case let' | case | tabs | abs => + have := fresh_exists <| free_union Var + congr <;> grind [openRec_tm_neq_eq, openRec_ty_tm_eq] + all_goals grind + +variable {t s : Term Var} {δ : Ty Var} {x : Var} + +omit [HasFresh Var] in +/-- Substitution of a free term variable not present in a term leaves it unchanged. -/ +lemma subst_tm_fresh (nmem : x ∉ t.fv_tm) (s : Term Var) : t = t[x := s] := by + induction t <;> grind + +/-- Substitution of a locally closed term distributes with term opening to a term. -/ +lemma openRec_tm_subst_tm (y : ℕ) (t₁ t₂ : Term Var) (lc : s.LC) (x : Var) : + (t₁⟦y ↝ t₂⟧ᵗᵗ)[x := s] = (t₁[x := s])⟦y ↝ t₂[x := s]⟧ᵗᵗ := by + induction t₁ generalizing y <;> grind + +/-- Specialize `Term.openRec_tm_subst_tm` to the first opening. -/ +lemma open_tm_subst_tm (t₁ t₂ : Term Var) (lc : s.LC) (x : Var) : + (t₁ ^ᵗᵗ t₂)[x := s] = (t₁[x := s]) ^ᵗᵗ t₂[x := s] := openRec_tm_subst_tm 0 t₁ t₂ lc x + +/-- Specialize `Term.openRec_tm_subst_tm` to free term variables. -/ +lemma open_tm_subst_tm_var (t : Term Var) (neq : y ≠ x) (lc : s.LC) : + (t ^ᵗᵗ fvar y)[x := s] = (t[x := s]) ^ᵗᵗ fvar y := by grind [open_tm_subst_tm] + +/-- Substitution of a locally closed type distributes with term opening to a term. -/ +lemma openRec_tm_subst_ty (y : ℕ) (t₁ t₂ : Term Var) (δ : Ty Var) (X : Var) : + (t₁⟦y ↝ t₂⟧ᵗᵗ)[X := δ] = (t₁[X := δ])⟦y ↝ t₂[X := δ]⟧ᵗᵗ := by + induction t₁ generalizing y <;> grind + +/-- Specialize `Term.openRec_tm_subst_ty` to the first opening -/ +lemma open_tm_subst_ty (t₁ t₂ : Term Var) (δ : Ty Var) (X : Var) : + (t₁ ^ᵗᵗ t₂)[X := δ] = (t₁[X := δ]) ^ᵗᵗ t₂[X := δ] := openRec_tm_subst_ty 0 t₁ t₂ δ X + +/-- Specialize `Term.open_tm_subst_ty` to free term variables -/ +lemma open_tm_subst_ty_var (t₁ : Term Var) (δ : Ty Var) (X y : Var) : + (t₁ ^ᵗᵗ fvar y)[X := δ] = (t₁[X := δ]) ^ᵗᵗ fvar y := by grind [open_tm_subst_ty] + +/-- Substitution of a locally closed term distributes with term opening to a type. -/ +lemma openRec_ty_subst_tm (Y : ℕ) (t : Term Var) (δ : Ty Var) (lc : s.LC) (x : Var) : + (t⟦Y ↝ δ⟧ᵗᵞ)[x := s] = t[x := s]⟦Y ↝ δ⟧ᵗᵞ := by + induction t generalizing Y <;> grind + +/-- Specialize `Term.openRec_ty_subst_tm` to the first opening. -/ +lemma open_ty_subst_tm (t : Term Var) (δ : Ty Var) (lc : s.LC) (x : Var) : + (t ^ᵗᵞ δ)[x := s] = t[x := s] ^ᵗᵞ δ := openRec_ty_subst_tm 0 t δ lc x + +/-- Specialize `Term.open_ty_subst_tm` to free type variables. -/ +lemma open_ty_subst_tm_var (t : Term Var) (lc : s.LC) (x Y : Var) : + (t ^ᵗᵞ .fvar Y)[x := s] = t[x := s] ^ᵗᵞ .fvar Y := open_ty_subst_tm _ _ lc _ + +omit [HasFresh Var] + +/-- Opening a term to a term is equivalent to opening to a free variable and substituting. -/ +lemma openRec_tm_subst_tm_intro (y : ℕ) (t s : Term Var) (nmem : x ∉ t.fv_tm) : + t⟦y ↝ s⟧ᵗᵗ = (t⟦y ↝ fvar x⟧ᵗᵗ)[x := s] := by + induction t generalizing y <;> grind + +/-- Specialize `Term.openRec_tm_subst_tm_intro` to the first opening. -/ +lemma open_tm_subst_tm_intro (t s : Term Var) (nmem : x ∉ t.fv_tm) : + t ^ᵗᵗs = (t ^ᵗᵗ fvar x)[x := s] := openRec_tm_subst_tm_intro _ _ _ nmem + +variable [HasFresh Var] + +lemma subst_ty_lc (t_lc : t.LC) (δ_lc : δ.LC) (X : Var) : t[X := δ].LC := by + induction t_lc + case' abs => apply LC.abs (free_union Var) + case' tabs => apply LC.tabs (free_union Var) + case' let' => apply LC.let' (free_union Var) + case' case => apply LC.case (free_union Var) + all_goals grind [Ty.subst_lc, open_tm_subst_ty_var, openRec_ty_subst_ty] + +lemma subst_tm_lc (t_lc : t.LC) (s_lc : s.LC) (x : Var) : t[x := s].LC := by + induction t_lc + case' abs => apply LC.abs (free_union Var) + case' let' => apply LC.let' (free_union Var) + case' case => apply LC.case (free_union Var) + case' tabs => apply LC.tabs (free_union Var) + all_goals grind [open_tm_subst_tm_var, open_ty_subst_tm_var] + +end Term + +namespace Binding + +omit [HasFresh Var] + +/-- Binding substitution of types. -/ +@[scoped grind =] +def subst (X : Var) (δ : Ty Var) : Binding Var → Binding Var +| sub γ => sub <| γ[X := δ] +| ty γ => ty <| γ[X := δ] + +instance : HasSubstitution (Binding Var) Var (Ty Var) where + subst γ X δ := Binding.subst X δ γ + +variable {δ γ : Ty Var} {X : Var} + +@[scoped grind _=_] +lemma subst_sub : (sub γ)[X := δ] = sub (γ[X := δ]) := by rfl + +@[scoped grind _=_] +lemma subst_ty : (ty γ)[X := δ] = ty (γ[X := δ]) := by rfl + +open scoped Ty in +/-- Substitution of a free variable not present in a binding leaves it unchanged. -/ +lemma subst_fresh {γ : Binding Var} (nmem : X ∉ γ.fv) (δ : Ty Var) : γ = γ[X := δ] := by + induction γ <;> grind [Ty.subst_fresh] + +end Binding + +end LambdaCalculus.LocallyNameless.Fsub diff --git a/Cslib/Languages/LambdaCalculus/LocallyNameless/Fsub/Reduction.lean b/Cslib/Languages/LambdaCalculus/LocallyNameless/Fsub/Reduction.lean new file mode 100644 index 00000000..87e7a2ec --- /dev/null +++ b/Cslib/Languages/LambdaCalculus/LocallyNameless/Fsub/Reduction.lean @@ -0,0 +1,123 @@ +/- +Copyright (c) 2025 Chris Henson. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Chris Henson +-/ + +import Cslib.Languages.LambdaCalculus.LocallyNameless.Fsub.Opening +import Cslib.Foundations.Semantics.ReductionSystem.Basic + +/-! # λ-calculus + +The λ-calculus with polymorphism and subtyping, with a locally nameless representation of syntax. +This file defines a call-by-value reduction. + +## References + +* [A. Chargueraud, *The Locally Nameless Representation*][Chargueraud2012] +* See also , from which + this is adapted + +-/ + +variable {Var : Type*} + +namespace LambdaCalculus.LocallyNameless.Fsub + +namespace Term + +/-- Existential predicate for being a locally closed body of an abstraction. -/ +@[scoped grind =] +def body (t : Term Var) := ∃ L : Finset Var, ∀ x ∉ L, LC (t ^ᵗᵗ fvar x) + +section + +variable {t₁ t₂ t₃ : Term Var} + +variable [DecidableEq Var] + +/-- Locally closed let bindings have a locally closed body. -/ +@[scoped grind _=_] +lemma body_let : (let' t₁ t₂).LC ↔ t₁.LC ∧ t₂.body := by + constructor <;> intro h <;> cases h + case mp.let' L _ _ => + split_ands + · grind + · exists L + case mpr.intro body => + obtain ⟨_, _⟩ := body + apply LC.let' (free_union Var) <;> grind + +/-- Locally closed case bindings have a locally closed bodies. -/ +@[scoped grind _=_] +lemma body_case : (case t₁ t₂ t₃).LC ↔ t₁.LC ∧ t₂.body ∧ t₃.body := by + constructor <;> intro h + case mp => + cases h with | case L => + split_ands + · grind + · exists L + · exists L + case mpr => + obtain ⟨_, ⟨_, _⟩, ⟨_, _⟩⟩ := h + apply LC.case (free_union Var) <;> grind + +variable [HasFresh Var] + +/-- Opening a body preserves local closure. -/ +@[scoped grind <=] +lemma open_tm_body (body : t₁.body) (lc : t₂.LC) : (t₁ ^ᵗᵗ t₂).LC := by + cases body + have := fresh_exists <| free_union [fv_tm] Var + grind [subst_tm_lc, open_tm_subst_tm_intro] + +end + +/-- Values are irreducible terms. -/ +@[grind] +inductive Value : Term Var → Prop + | abs : LC (abs σ t₁) → Value (abs σ t₁) + | tabs : LC (tabs σ t₁) → Value (tabs σ t₁) + | inl : Value t₁ → Value (inl t₁) + | inr : Value t₁ → Value (inr t₁) + +@[grind →] +lemma Value.lc {t : Term Var} (val : t.Value) : t.LC := by + induction val <;> grind + +/-- The call-by-value reduction relation. -/ +@[grind, reduction_sys rs "βᵛ"] +inductive Red : Term Var → Term Var → Prop + | appₗ : LC t₂ → Red t₁ t₁' → Red (app t₁ t₂) (app t₁' t₂) + | appᵣ : Value t₁ → Red t₂ t₂' → Red (app t₁ t₂) (app t₁ t₂') + | tapp : σ.LC → Red t₁ t₁' → Red (tapp t₁ σ) (tapp t₁' σ) + | abs : LC (abs σ t₁) → Value t₂ → Red (app (abs σ t₁) t₂) (t₁ ^ᵗᵗ t₂) + | tabs : LC (tabs σ t₁) → τ.LC → Red (tapp (tabs σ t₁) τ) (t₁ ^ᵗᵞ τ) + | let_bind : Red t₁ t₁' → t₂.body → Red (let' t₁ t₂) (let' t₁' t₂) + | let_body : Value t₁ → t₂.body → Red (let' t₁ t₂) (t₂ ^ᵗᵗ t₁) + | inl : Red t₁ t₁' → Red (inl t₁) (inl t₁') + | inr : Red t₁ t₁' → Red (inr t₁) (inr t₁') + | case : Red t₁ t₁' → t₂.body → t₃.body → Red (case t₁ t₂ t₃) (case t₁' t₂ t₃) + | case_inl : Value t₁ → t₂.body → t₃.body → Red (case (inl t₁) t₂ t₃) (t₂ ^ᵗᵗ t₁) + | case_inr : Value t₁ → t₂.body → t₃.body → Red (case (inr t₁) t₂ t₃) (t₃ ^ᵗᵗ t₁) + +@[grind _=_] +private lemma rs_eq {t t' : Term Var} : t ⭢βᵛ t' ↔ Red t t' := by + have : (@rs Var).Red = Red := by rfl + simp_all + +variable [HasFresh Var] [DecidableEq Var] in +/-- Terms of a reduction are locally closed. -/ +lemma Red.lc {t t' : Term Var} (red : t ⭢βᵛ t') : t.LC ∧ t'.LC := by + induction red + case abs lc _ | tabs lc _ => + split_ands + · grind + · cases lc + have := fresh_exists <| free_union [fv_tm, fv_ty] Var + grind [subst_tm_lc, subst_ty_lc, open_tm_subst_tm_intro, open_ty_subst_ty_intro] + all_goals grind + +end Term + +end LambdaCalculus.LocallyNameless.Fsub diff --git a/Cslib/Languages/LambdaCalculus/LocallyNameless/Fsub/Safety.lean b/Cslib/Languages/LambdaCalculus/LocallyNameless/Fsub/Safety.lean new file mode 100644 index 00000000..27153349 --- /dev/null +++ b/Cslib/Languages/LambdaCalculus/LocallyNameless/Fsub/Safety.lean @@ -0,0 +1,153 @@ +/- +Copyright (c) 2025 Chris Henson. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Chris Henson +-/ + +import Cslib.Languages.LambdaCalculus.LocallyNameless.Fsub.Typing +import Cslib.Languages.LambdaCalculus.LocallyNameless.Fsub.Reduction + +/-! # λ-calculus + +The λ-calculus with polymorphism and subtyping, with a locally nameless representation of syntax. +This file proves type safety. + +## References + +* [A. Chargueraud, *The Locally Nameless Representation*][Chargueraud2012] +* See also , from which + this is adapted + +-/ + +variable {Var : Type*} [HasFresh Var] [DecidableEq Var] + +namespace LambdaCalculus.LocallyNameless.Fsub + +open Context List Env.Wf Term Ty + +variable {t : Term Var} + +/-- Any reduction step preserves typing. -/ +lemma Typing.preservation (der : Typing Γ t τ) (step : t ⭢βᵛ t') : Typing Γ t' τ := by + induction der generalizing t' + case app Γ _ σ τ _ _ _ _ _ => + cases step + case appₗ | appᵣ => grind + case abs der _ _ => + have sub : Sub Γ (σ.arrow τ) (σ.arrow τ) := by grind [Sub.refl] + have ⟨_, _, ⟨_, _⟩⟩ := der.abs_inv sub + have ⟨_, _⟩ := fresh_exists <| free_union [fv_tm] Var + grind [open_tm_subst_tm_intro, subst_tm, Sub.weaken] + case tapp Γ _ σ τ σ' _ _ _ => + cases step + case tabs der _ _ => + have sub : Sub Γ (σ.all τ) (σ.all τ) := by grind [Sub.refl] + have ⟨_, _, ⟨_, _⟩⟩ := der.tabs_inv sub + have ⟨X, _⟩ := fresh_exists <| free_union [Ty.fv, fv_ty] Var + have : Γ = (Context.map_val (·[X:=σ']) []) ++ Γ := by simp + rw [open_ty_subst_ty_intro (X := X), open_subst_intro (X := X)] <;> grind [subst_ty] + case tapp => grind + case let' Γ _ _ _ _ L der _ ih₁ _ => + cases step + case let_bind red₁ _ => apply Typing.let' L (ih₁ red₁); grind + case let_body => + have ⟨x, _⟩ := fresh_exists <| free_union [fv_tm] Var + grind [open_tm_subst_tm_intro, subst_tm] + case case Γ _ σ τ _ _ _ L _ _ _ ih₁ _ _ => + have sub : Sub Γ (σ.sum τ) (σ.sum τ) := by grind [Sub.refl] + have : Γ = [] ++ Γ := by rfl + cases step + case «case» red₁ _ _ => apply Typing.case L (ih₁ red₁) <;> grind + case case_inl der _ _ => + have ⟨_, ⟨_, _⟩⟩ := der.inl_inv sub + have ⟨x, _⟩ := fresh_exists <| free_union [fv_tm] Var + grind [open_tm_subst_tm_intro, subst_tm] + case case_inr der _ _ => + have ⟨_, ⟨_, _⟩⟩ := der.inr_inv sub + have ⟨x, _⟩ := fresh_exists <| free_union [fv_tm] Var + grind [open_tm_subst_tm_intro, subst_tm] + all_goals grind [cases Red] + +/-- Any typable term either has a reduction step or is a value. -/ +lemma Typing.progress (der : Typing [] t τ) : t.Value ∨ ∃ t', t ⭢βᵛ t' := by + generalize eq : [] = Γ at der + have der' : Typing Γ t τ := by assumption + induction der <;> subst eq <;> simp only [forall_const] at * + case var mem => grind + case app t₁ _ _ t₂ l r ih_l ih_r => + right + cases ih_l l with + | inl val_l => + cases ih_r r with + | inl val_r => + have ⟨σ, t₁, eq⟩ := l.canonical_form_abs val_l + exists t₁ ^ᵗᵗ t₂ + grind + | inr red_r => + obtain ⟨t₂', _⟩ := red_r + exists t₁.app t₂' + grind + | inr red_l => + obtain ⟨t₁', _⟩ := red_l + exists t₁'.app t₂ + grind + case tapp σ' der _ ih => + right + specialize ih der + cases ih with + | inl val => + obtain ⟨_, t, _⟩ := der.canonical_form_tabs val + exists t ^ᵗᵞ σ' + grind + | inr red => + obtain ⟨t', _⟩ := red + exists .tapp t' σ' + grind + case let' t₁ σ t₂ τ L der _ _ ih => + right + cases ih der with + | inl _ => + exists t₂ ^ᵗᵗ t₁ + grind + | inr red => + obtain ⟨t₁', _⟩ := red + exists t₁'.let' t₂ + grind + case inl der _ ih => + cases (ih der) with + | inl val => grind + | inr red => + right + obtain ⟨t', _⟩ := red + exists .inl t' + grind + case inr der _ ih => + cases (ih der) with + | inl val => grind + | inr red => + right + obtain ⟨t', _⟩ := red + exists .inr t' + grind + case case t₁ _ _ t₂ _ t₃ _ der _ _ _ _ ih => + right + cases ih der with + | inl val => + have ⟨t₁, lr⟩ := der.canonical_form_sum val + cases lr <;> [exists t₂ ^ᵗᵗ t₁; exists t₃ ^ᵗᵗ t₁] <;> grind + | inr red => + obtain ⟨t₁', _⟩ := red + exists t₁'.case t₂ t₃ + grind + case sub => grind + case abs σ _ τ L _ _=> + left + constructor + apply LC.abs L <;> grind [cases Env.Wf, cases Term.LC] + case tabs L _ _=> + left + constructor + apply LC.tabs L <;> grind [cases Env.Wf, cases Term.LC] + +end LambdaCalculus.LocallyNameless.Fsub diff --git a/Cslib/Languages/LambdaCalculus/LocallyNameless/Fsub/Subtype.lean b/Cslib/Languages/LambdaCalculus/LocallyNameless/Fsub/Subtype.lean new file mode 100644 index 00000000..7a11bba5 --- /dev/null +++ b/Cslib/Languages/LambdaCalculus/LocallyNameless/Fsub/Subtype.lean @@ -0,0 +1,158 @@ +/- +Copyright (c) 2025 Chris Henson. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Chris Henson +-/ + +import Cslib.Languages.LambdaCalculus.LocallyNameless.Fsub.WellFormed + +/-! # λ-calculus + +The λ-calculus with polymorphism and subtyping, with a locally nameless representation of syntax. +This file defines the subtyping relation. + +## References + +* [A. Chargueraud, *The Locally Nameless Representation*][Chargueraud2012] +* See also , from which + this is adapted + +-/ + +variable {Var : Type*} [DecidableEq Var] + +namespace LambdaCalculus.LocallyNameless.Fsub + +open Ty + +/-- The subtyping relation. -/ +inductive Sub : Env Var → Ty Var → Ty Var → Prop + | top : Γ.Wf → σ.Wf Γ → Sub Γ σ top + | refl_tvar : Γ.Wf → (fvar X).Wf Γ → Sub Γ (fvar X) (fvar X) + | trans_tvar : Binding.sub σ ∈ Γ.dlookup X → Sub Γ σ σ' → Sub Γ (fvar X) σ' + | arrow : Sub Γ σ σ' → Sub Γ τ' τ → Sub Γ (arrow σ' τ') (arrow σ τ) + | all (L : Finset Var) : + Sub Γ σ σ' → + (∀ X ∉ L, Sub (⟨X, Binding.sub σ⟩ :: Γ) (τ' ^ᵞ fvar X) (τ ^ᵞ fvar X)) → + Sub Γ (all σ' τ') (all σ τ) + | sum : Sub Γ σ' σ → Sub Γ τ' τ → Sub Γ (sum σ' τ') (sum σ τ) + +namespace Sub + +open Context List Ty.Wf Env.Wf Binding + +attribute [scoped grind] Sub.top Sub.refl_tvar Sub.trans_tvar Sub.arrow Sub.sum + +variable {Γ Δ Θ : Env Var} {σ τ δ : Ty Var} + +/-- Subtypes have well-formed contexts and types. -/ +@[grind →] +lemma wf (Γ : Env Var) (σ σ' : Ty Var) (sub : Sub Γ σ σ') : Γ.Wf ∧ σ.Wf Γ ∧ σ'.Wf Γ := by + induction sub with + | all => + refine ⟨by grind, ?_, ?_⟩ <;> + apply Wf.all (free_union Var) <;> grind [Wf.narrow_cons, cases Env.Wf, cases LC] + | _ => grind + +/-- Subtypes are reflexive when well-formed. -/ +lemma refl (wf_Γ : Γ.Wf) (wf_σ : σ.Wf Γ) : Sub Γ σ σ := by + induction wf_σ with + | all => apply all (free_union [Context.dom] Var) <;> grind + | _ => grind + +/-- Weakening of subtypes. -/ +lemma weaken (sub : Sub (Γ ++ Θ) σ σ') (wf : (Γ ++ Δ ++ Θ).Wf) : Sub (Γ ++ Δ ++ Θ) σ σ' := by + generalize eq : Γ ++ Θ = ΓΘ at sub + induction sub generalizing Γ + case all => + subst eq + apply all (free_union [Context.dom] Var) <;> grind [keys_append] + all_goals grind [Ty.Wf.weaken, <= sublist_dlookup] + +lemma weaken_head (sub : Sub Δ σ σ') (wf : (Γ ++ Δ).Wf) : Sub (Γ ++ Δ) σ σ' := by + have eq : Γ ++ Δ = [] ++ Γ ++ Δ := by grind + grind [weaken] + +lemma narrow_aux + (trans : ∀ Γ σ τ, Sub Γ σ δ → Sub Γ δ τ → Sub Γ σ τ) + (sub₁ : Sub (Γ ++ ⟨X, Binding.sub δ⟩ :: Δ) σ τ) (sub₂ : Sub Δ δ' δ) : + Sub (Γ ++ ⟨X, Binding.sub δ'⟩ :: Δ) σ τ := by + generalize eq : Γ ++ ⟨X, Binding.sub δ⟩ :: Δ = Θ at sub₁ + induction sub₁ generalizing Γ δ + case trans_tvar σ _ σ' X' mem sub ih => + have p : ∀ δ, Γ ++ ⟨X, Binding.sub δ⟩ :: Δ ~ ⟨X, Binding.sub δ⟩ :: (Γ ++ Δ) := + by grind [perm_middle] + have := perm_dlookup (p := p δ') + have := perm_dlookup (p := p δ) + grind [Sub.weaken, sublist_append_of_sublist_right] + case all => apply Sub.all (free_union Var) <;> grind + all_goals grind [Env.Wf.narrow, Ty.Wf.narrow] + +@[grind →] +lemma trans : Sub Γ σ δ → Sub Γ δ τ → Sub Γ σ τ := by + intro sub₁ sub₂ + have δ_lc : δ.LC := by grind + induction δ_lc generalizing Γ σ τ + case top => cases sub₁ <;> cases sub₂ <;> grind + case var X => + generalize eq : fvar X = γ at sub₁ + induction sub₁ <;> grind [cases Sub] + case arrow σ' τ' _ _ _ _ => + generalize eq : σ'.arrow τ' = γ at sub₁ + induction sub₁ <;> grind [cases Sub] + case sum σ' τ' _ _ _ _ => + generalize eq : σ'.sum τ' = γ at sub₁ + induction sub₁ <;> grind [cases Sub] + case all σ' τ' _ _ _ _ _ => + generalize eq : σ'.all τ' = γ at sub₁ + induction sub₁ + case all => + cases eq + cases sub₂ + case refl.top Γ σ'' τ'' _ _ _ _ _ _ _ => + have : Sub Γ (σ''.all τ'') (σ'.all τ') := by apply all (free_union Var) <;> grind + grind + case refl.all Γ _ _ _ _ _ σ _ _ _ _ _ _ => + apply all (free_union Var) + · grind + · intro X nmem + have eq : ∀ σ, ⟨X, Binding.sub σ⟩ :: Γ = [] ++ ⟨X, Binding.sub σ⟩ :: Γ := by grind + grind [Sub.narrow_aux] + all_goals grind + +instance (Γ : Env Var) : Trans (Sub Γ) (Sub Γ) (Sub Γ) := + ⟨Sub.trans⟩ + +/-- Narrowing of subtypes. -/ +lemma narrow (sub_δ : Sub Δ δ δ') (sub_narrow : Sub (Γ ++ ⟨X, Binding.sub δ'⟩ :: Δ) σ τ) : + Sub (Γ ++ ⟨X, Binding.sub δ⟩ :: Δ) σ τ := by + apply narrow_aux (δ := δ') <;> grind + +variable [HasFresh Var] in +/-- Subtyping of substitutions. -/ +lemma map_subst (sub₁ : Sub (Γ ++ ⟨X, Binding.sub δ'⟩ :: Δ) σ τ) (sub₂ : Sub Δ δ δ') : + Sub (Γ.map_val (·[X:=δ]) ++ Δ) (σ[X:=δ]) (τ[X:=δ]) := by + generalize eq : Γ ++ ⟨X, Binding.sub δ'⟩ :: Δ = Θ at sub₁ + induction sub₁ generalizing Γ + case all => apply Sub.all (free_union Var) <;> grind [open_subst_var] + case trans_tvar σ _ _ X' _ _ _ => + have := map_subst_nmem Δ X δ + have : Γ ++ ⟨X, .sub δ'⟩ :: Δ ~ ⟨X, .sub δ'⟩ :: (Γ ++ Δ) := perm_middle + have : .sub σ ∈ dlookup X' (⟨X, .sub δ'⟩ :: (Γ ++ Δ)) := by grind [perm_dlookup] + have := @map_val_mem Var (f := ((·[X:=δ]) : Binding Var → Binding Var)) + by_cases X = X' + · trans δ' <;> grind [→ mem_dlookup, Ty.subst_fresh, Ty.Wf.nmem_fv, weaken_head, keys_append] + · grind [keys_append] + all_goals + grind [Env.Wf.to_ok, keys_append, Sub.refl, Env.Wf.map_subst, Ty.Wf.map_subst] + +/-- Strengthening of subtypes. -/ +lemma strengthen (sub : Sub (Γ ++ ⟨X, Binding.ty δ⟩ :: Δ) σ τ) : Sub (Γ ++ Δ) σ τ := by + generalize eq : Γ ++ ⟨X, Binding.ty δ⟩ :: Δ = Θ at sub + induction sub generalizing Γ + case all => apply Sub.all (free_union Var) <;> grind + all_goals grind [to_ok, Wf.strengthen, Env.Wf.strengthen, dlookup_append] + +end Sub + +end LambdaCalculus.LocallyNameless.Fsub diff --git a/Cslib/Languages/LambdaCalculus/LocallyNameless/Fsub/Typing.lean b/Cslib/Languages/LambdaCalculus/LocallyNameless/Fsub/Typing.lean new file mode 100644 index 00000000..4506b86e --- /dev/null +++ b/Cslib/Languages/LambdaCalculus/LocallyNameless/Fsub/Typing.lean @@ -0,0 +1,241 @@ +/- +Copyright (c) 2025 Chris Henson. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Chris Henson +-/ + +import Cslib.Languages.LambdaCalculus.LocallyNameless.Fsub.WellFormed +import Cslib.Languages.LambdaCalculus.LocallyNameless.Fsub.Subtype +import Cslib.Languages.LambdaCalculus.LocallyNameless.Fsub.Reduction + +/-! # λ-calculus + +The λ-calculus with polymorphism and subtyping, with a locally nameless representation of syntax. +This file defines the typing relation. + +## References + +* [A. Chargueraud, *The Locally Nameless Representation*][Chargueraud2012] +* See also , from which + this is adapted + +-/ + +variable {Var : Type*} [DecidableEq Var] [HasFresh Var] + +namespace LambdaCalculus.LocallyNameless.Fsub + +open Term Ty Ty.Wf Env.Wf Sub Context List Binding + +/-- The typing relation. -/ +inductive Typing : Env Var → Term Var → Ty Var → Prop + | var : Γ.Wf → Binding.ty σ ∈ Γ.dlookup x → Typing Γ (fvar x) σ + | abs (L : Finset Var) : + (∀ x ∉ L, Typing (⟨x, Binding.ty σ⟩ :: Γ) (t₁ ^ᵗᵗ fvar x) τ) → + Typing Γ (abs σ t₁) (arrow σ τ) + | app : Typing Γ t₁ (arrow σ τ) → Typing Γ t₂ σ → Typing Γ (app t₁ t₂) τ + | tabs (L : Finset Var) : + (∀ X ∉ L, Typing (⟨X, Binding.sub σ⟩ :: Γ) (t₁ ^ᵗᵞ fvar X) (τ ^ᵞ fvar X)) → + Typing Γ (tabs σ t₁) (all σ τ) + | tapp : Typing Γ t₁ (all σ τ) → Sub Γ σ' σ → Typing Γ (tapp t₁ σ') (τ ^ᵞ σ') + | sub : Typing Γ t τ → Sub Γ τ τ' → Typing Γ t τ' + | let' (L : Finset Var) : + Typing Γ t₁ σ → + (∀ x ∉ L, Typing (⟨x, Binding.ty σ⟩ :: Γ) (t₂ ^ᵗᵗ fvar x) τ) → + Typing Γ (let' t₁ t₂) τ + | inl : Typing Γ t₁ σ → τ.Wf Γ → Typing Γ (inl t₁) (sum σ τ) + | inr : Typing Γ t₁ τ → σ.Wf Γ → Typing Γ (inr t₁) (sum σ τ) + | case (L : Finset Var) : + Typing Γ t₁ (sum σ τ) → + (∀ x ∉ L, Typing (⟨x, Binding.ty σ⟩ :: Γ) (t₂ ^ᵗᵗ fvar x) δ) → + (∀ x ∉ L, Typing (⟨x, Binding.ty τ⟩ :: Γ) (t₃ ^ᵗᵗ fvar x) δ) → + Typing Γ (case t₁ t₂ t₃) δ + +namespace Typing + +variable {Γ Δ Θ : Env Var} {σ τ δ : Ty Var} + +attribute [grind] Typing.var Typing.app Typing.tapp Typing.sub Typing.inl Typing.inr + +/-- Typings have well-formed contexts and types. -/ +@[grind →] +lemma wf {Γ : Env Var} {t : Term Var} {τ : Ty Var} (der : Typing Γ t τ) : Γ.Wf ∧ t.LC ∧ τ.Wf Γ := by + induction der <;> let L := free_union Var <;> have := fresh_exists L + case tabs => refine ⟨?_, LC.tabs L ?_ ?_, Ty.Wf.all L ?_ ?_⟩ <;> grind [cases Env.Wf] + case abs => refine ⟨?_, LC.abs L ?_ ?_, ?_⟩ <;> grind [Wf.strengthen, cases Env.Wf] + case let' => refine ⟨?_, LC.let' L ?_ ?_, ?_⟩ <;> grind [Ty.Wf.strengthen] + case case => refine ⟨?_, LC.case L ?_ ?_ ?_, ?_⟩ <;> grind [Ty.Wf.strengthen] + all_goals grind [of_bind_ty, open_lc, cases Env.Wf, cases Ty.Wf] + +/-- Weakening of typings. -/ +lemma weaken (der : Typing (Γ ++ Δ) t τ) (wf : (Γ ++ Θ ++ Δ).Wf) : + Typing (Γ ++ Θ ++ Δ) t τ := by + generalize eq : Γ ++ Δ = ΓΔ at der + induction der generalizing Γ + case' abs => apply abs ((Γ ++ Θ ++ Δ).dom ∪ free_union Var) + case' tabs => apply tabs ((Γ ++ Θ ++ Δ).dom ∪ free_union Var) + case' let' der _ => apply let' ((Γ ++ Θ ++ Δ).dom ∪ free_union Var) (der wf eq) + case' case der _ _ => apply case ((Γ ++ Θ ++ Δ).dom ∪ free_union Var) (der wf eq) + all_goals + grind [Wf.weaken, Sub.weaken, Wf.of_env_ty, Wf.of_env_sub, Sub.refl, <= sublist_dlookup] + +/-- Weakening of typings (at the front). -/ +lemma weaken_head (der : Typing Δ t τ) (wf : (Γ ++ Δ).Wf) : + Typing (Γ ++ Δ) t τ := by + have eq : Δ = [] ++ Δ := by rfl + rw [eq] at der + have := Typing.weaken der wf + grind + +/-- Narrowing of typings. -/ +lemma narrow (sub : Sub Δ δ δ') (der : Typing (Γ ++ ⟨X, Binding.sub δ'⟩ :: Δ) t τ) : + Typing (Γ ++ ⟨X, Binding.sub δ⟩ :: Δ) t τ := by + generalize eq : Γ ++ ⟨X, Binding.sub δ'⟩ :: Δ = Θ at der + induction der generalizing Γ + case var X' _ _ => + have : X ≠ X' := by grind [→ List.mem_dlookup] + have p (δ) : Γ ++ ⟨X, .sub δ⟩ :: Δ ~ ⟨X, .sub δ⟩ :: (Γ ++ Δ) := perm_middle + grind [Env.Wf.narrow, List.perm_nodupKeys, => List.perm_dlookup] + case' abs => apply abs (free_union Var) + case' tabs => apply tabs (free_union Var) + case' let' der _ => apply let' (free_union Var) (der eq) + case' case der _ _ => apply case (free_union Var) (der eq) + all_goals grind [Ty.Wf.narrow, Env.Wf.narrow, Sub.narrow] + +/-- Term substitution within a typing. -/ +lemma subst_tm (der : Typing (Γ ++ ⟨X, .ty σ⟩ :: Δ) t τ) (der_sub : Typing Δ s σ) : + Typing (Γ ++ Δ) (t[X := s]) τ := by + generalize eq : Γ ++ ⟨X, .ty σ⟩ :: Δ = Θ at der + induction der generalizing Γ X + case var σ' _ X' _ _ => + have : Γ ++ ⟨X, .ty σ⟩ :: Δ ~ ⟨X, .ty σ⟩ :: (Γ ++ Δ) := perm_middle + by_cases eq : X = X' + · grind [→ List.mem_dlookup, weaken_head, Env.Wf.strengthen] + · grind [Env.Wf.strengthen, => List.perm_dlookup] + case abs => + apply abs (free_union Var) + grind [open_tm_subst_tm_var] + case tabs => + apply tabs (free_union Var) + grind [open_ty_subst_tm_var] + case let' der _ => + apply let' (free_union Var) (der eq) + grind [open_tm_subst_tm_var] + case case der _ _ => + apply case (free_union Var) (der eq) <;> grind [open_tm_subst_tm_var] + all_goals grind [Env.Wf.strengthen, Ty.Wf.strengthen, Sub.strengthen] + +/-- Type substitution within a typing. -/ +lemma subst_ty (der : Typing (Γ ++ ⟨X, Binding.sub δ'⟩ :: Δ) t τ) (sub : Sub Δ δ δ') : + Typing (Γ.map_val (·[X := δ]) ++ Δ) (t[X := δ]) (τ[X := δ]) := by + generalize eq : Γ ++ ⟨X, Binding.sub δ'⟩ :: Δ = Θ at der + induction der generalizing Γ X + case var σ _ X' _ mem => + have := map_subst_nmem Δ X δ + have : Γ ++ ⟨X, .sub δ'⟩ :: Δ ~ ⟨X, .sub δ'⟩ :: (Γ ++ Δ) := perm_middle + have : .ty σ ∈ dlookup X' (⟨X, .sub δ'⟩ :: (Γ ++ Δ)) := by grind [perm_dlookup] + have := @map_val_mem Var (f := ((·[X:=δ]) : Binding Var → Binding Var)) + grind [Env.Wf.map_subst, keys_append, → notMem_keys_of_nodupKeys_cons] + case abs => + apply abs (free_union [Ty.fv] Var) + grind [Ty.subst_fresh, open_tm_subst_ty_var] + case tabs => + apply tabs (free_union Var) + grind [open_ty_subst_ty_var, open_subst_var] + case let' der _ => + apply let' (free_union Var) (der eq) + grind [open_tm_subst_ty_var] + case case der _ _ => + apply case (free_union Var) (der eq) <;> grind [open_tm_subst_ty_var] + case tapp => grind [Ty.open_subst, Env.Wf.map_subst, Ty.Wf.map_subst, Sub.map_subst] + all_goals grind [Env.Wf.map_subst, Ty.Wf.map_subst, Sub.map_subst] + +open Term Ty + +omit [HasFresh Var] + +/-- Invert the typing of an abstraction. -/ +lemma abs_inv (der : Typing Γ (.abs γ' t) τ) (sub : Sub Γ τ (arrow γ δ)) : + Sub Γ γ γ' + ∧ ∃ δ' L, ∀ x ∉ (L : Finset Var), + Typing (⟨x, Binding.ty γ'⟩ :: Γ) (t ^ᵗᵗ .fvar x) δ' ∧ Sub Γ δ' δ := by + generalize eq : Term.abs γ' t = e at der + induction der generalizing t γ' γ δ + case abs τ L _ _ => + cases eq + cases sub + split_ands + · assumption + · exists τ, L + grind + case sub Γ _ τ τ' _ _ ih => + subst eq + have sub' : Sub Γ τ (γ.arrow δ) := by grind + obtain ⟨_, δ', L, _⟩ := ih sub' (by rfl) + split_ands + · assumption + · exists δ', L + all_goals grind + +variable [HasFresh Var] in +/-- Invert the typing of a type abstraction. -/ +lemma tabs_inv (der : Typing Γ (.tabs γ' t) τ) (sub : Sub Γ τ (all γ δ)) : + Sub Γ γ γ' + ∧ ∃ δ' L, ∀ X ∉ (L : Finset Var), + Typing (⟨X, Binding.sub γ⟩ :: Γ) (t ^ᵗᵞ fvar X) (δ' ^ᵞ fvar X) + ∧ Sub (⟨X, Binding.sub γ⟩ :: Γ) (δ' ^ᵞ fvar X) (δ ^ᵞ fvar X) := by + generalize eq : Term.tabs γ' t = e at der + induction der generalizing γ δ t γ' + case tabs σ Γ _ τ L der _ => + cases sub with | all L' sub => + split_ands + · grind + · exists τ, L ∪ L' + intro X _ + have eq : ⟨X, Binding.sub γ⟩ :: Γ = [] ++ ⟨X, Binding.sub γ⟩ :: Γ := by rfl + grind [narrow] + case sub Γ _ τ τ' _ _ ih => + subst eq + have sub' : Sub Γ τ (γ.all δ) := by trans τ' <;> grind + obtain ⟨_, δ', L, _⟩ := ih sub' (by rfl) + split_ands + · assumption + · exists δ', L + all_goals grind + +/-- Invert the typing of a left case. -/ +lemma inl_inv (der : Typing Γ (.inl t) τ) (sub : Sub Γ τ (sum γ δ)) : + ∃ γ', Typing Γ t γ' ∧ Sub Γ γ' γ := by + generalize eq : t.inl =t at der + induction der generalizing γ δ <;> grind [cases Sub] + +/-- Invert the typing of a right case. -/ +lemma inr_inv (der : Typing Γ (.inr t) T) (sub : Sub Γ T (sum γ δ)) : + ∃ δ', Typing Γ t δ' ∧ Sub Γ δ' δ := by + generalize eq : t.inr =t at der + induction der generalizing γ δ <;> grind [cases Sub] + +/-- A value that types as a function is an abstraction. -/ +lemma canonical_form_abs (val : Value t) (der : Typing [] t (arrow σ τ)) : + ∃ δ t', t = .abs δ t' := by + generalize eq : σ.arrow τ = γ at der + generalize eq' : [] = Γ at der + induction der generalizing σ τ <;> grind [cases Sub, cases Value] + +/-- A value that types as a quantifier is a type abstraction. -/ +lemma canonical_form_tabs (val : Value t) (der : Typing [] t (all σ τ)) : + ∃ δ t', t = .tabs δ t' := by + generalize eq : σ.all τ = γ at der + generalize eq' : [] = Γ at der + induction der generalizing σ τ <;> grind [cases Sub, cases Value] + +/-- A value that types as a sum is a left or right case. -/ +lemma canonical_form_sum (val : Value t) (der : Typing [] t (sum σ τ)) : + ∃ t', t = .inl t' ∨ t = .inr t' := by + generalize eq : σ.sum τ = γ at der + generalize eq' : [] = Γ at der + induction der generalizing σ τ <;> grind [cases Sub, cases Value] + +end Typing + +end LambdaCalculus.LocallyNameless.Fsub diff --git a/Cslib/Languages/LambdaCalculus/LocallyNameless/Fsub/WellFormed.lean b/Cslib/Languages/LambdaCalculus/LocallyNameless/Fsub/WellFormed.lean new file mode 100644 index 00000000..a50694f1 --- /dev/null +++ b/Cslib/Languages/LambdaCalculus/LocallyNameless/Fsub/WellFormed.lean @@ -0,0 +1,188 @@ +/- +Copyright (c) 2025 Chris Henson. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Chris Henson +-/ + +import Cslib.Languages.LambdaCalculus.LocallyNameless.Fsub.Basic +import Cslib.Languages.LambdaCalculus.LocallyNameless.Fsub.Opening + +/-! # λ-calculus + +The λ-calculus with polymorphism and subtyping, with a locally nameless representation of syntax. +This file defines the well-formedness condition for types and contexts. + +## References + +* [A. Chargueraud, *The Locally Nameless Representation*][Chargueraud2012] +* See also , from which + this is adapted + +-/ + +variable {Var : Type*} [DecidableEq Var] + +namespace LambdaCalculus.LocallyNameless.Fsub + +open scoped Ty in +/-- A type is well-formed when it is locally closed and all free variables appear in a context. -/ +inductive Ty.Wf : Env Var → Ty Var → Prop + | top : Ty.Wf Γ top + | var : Binding.sub σ ∈ Γ.dlookup X → Ty.Wf Γ (fvar X) + | arrow : Ty.Wf Γ σ → Ty.Wf Γ τ → Ty.Wf Γ (arrow σ τ) + | all (L : Finset Var) : + Ty.Wf Γ σ → + (∀ X ∉ L, Ty.Wf (⟨X,Binding.sub σ⟩ :: Γ) (τ ^ᵞ fvar X)) → + Ty.Wf Γ (all σ τ) + | sum : Ty.Wf Γ σ → Ty.Wf Γ τ → Ty.Wf Γ (sum σ τ) + +attribute [scoped grind] Ty.Wf.top Ty.Wf.var Ty.Wf.arrow Ty.Wf.sum + +/-- An environment is well-formed if it binds each variable exactly once to a well-formed type. -/ +inductive Env.Wf : Env Var → Prop + | empty : Wf [] + | sub : Wf Γ → τ.Wf Γ → X ∉ Γ.dom → Wf (⟨X, Binding.sub τ⟩ :: Γ) + | ty : Wf Γ → τ.Wf Γ → x ∉ Γ.dom → Wf (⟨x, Binding.ty τ⟩ :: Γ) + +attribute [scoped grind] Env.Wf.sub Env.Wf.ty + +variable {Γ Δ Θ : Env Var} {σ τ τ' γ δ : Ty Var} + +open scoped Context in +/-- A well-formed context contains no duplicate keys. -/ +@[scoped grind →] +lemma Env.Wf.to_ok {Γ : Env Var} (wf : Γ.Wf) : Γ✓ := by + induction wf <;> constructor <;> first | assumption | grind [List.mem_keys_of_mem] + +namespace Ty.Wf + +open Context List Binding +open scoped Env.Wf + +/-- A well-formed type is locally closed. -/ +@[grind →] +theorem lc (wf : σ.Wf Γ) : σ.LC := by + induction wf with + | all => apply LC.all (free_union Var) <;> grind + | _ => grind + +/-- A type remains well-formed under context permutation. -/ +theorem perm_env (wf : σ.Wf Γ) (perm : Γ ~ Δ) (ok_Γ : Γ✓) (ok_Δ : Δ✓) : σ.Wf Δ := by + induction wf generalizing Δ with + | all => apply all (free_union [dom] Var) <;> grind [Perm.cons, nodupKeys_cons] + | _ => grind [perm_dlookup] + +/-- A type remains well-formed under context weakening (in the middle). -/ +theorem weaken (wf_ΓΘ : σ.Wf (Γ ++ Θ)) (ok_ΓΔΘ : (Γ ++ Δ ++ Θ)✓) : σ.Wf (Γ ++ Δ ++ Θ) := by + generalize eq : Γ ++ Θ = ΓΔ at wf_ΓΘ + induction wf_ΓΘ generalizing Γ + case all => apply all ((Γ ++ Δ ++ Θ).dom ∪ free_union Var) <;> grind + all_goals grind [NodupKeys.sublist, <= sublist_dlookup] + +/-- A type remains well-formed under context weakening (at the front). -/ +theorem weaken_head (wf : σ.Wf Δ) (ok : (Γ ++ Δ)✓) : σ.Wf (Γ ++ Δ) := by + have : Γ ++ Δ = [] ++ Γ ++ Δ := by rfl + grind [weaken] + +theorem weaken_cons (wf : σ.Wf Δ) (ok : (⟨X, b⟩ :: Δ)✓) : σ.Wf (⟨X, b⟩:: Δ) := by + grind [weaken_head] + +/-- A type remains well-formed under context narrowing. -/ +lemma narrow (wf : σ.Wf (Γ ++ ⟨X, Binding.sub τ⟩ :: Δ)) (ok : (Γ ++ ⟨X, Binding.sub τ'⟩ :: Δ)✓) : + σ.Wf (Γ ++ ⟨X, Binding.sub τ'⟩ :: Δ) := by + generalize eq : (Γ ++ ⟨X, Binding.sub τ⟩ :: Δ) = Θ at wf + induction wf generalizing Γ with + | all => apply all (free_union [dom] Var) <;> grind [nodupKeys_cons, keys_append] + | _ => grind [sublist_dlookup, dlookup_append] + +lemma narrow_cons (wf : σ.Wf (⟨X, Binding.sub τ⟩ :: Δ)) (ok : (⟨X, Binding.sub τ'⟩ :: Δ)✓) : + σ.Wf (⟨X, Binding.sub τ'⟩ :: Δ) := by + rw [←List.nil_append (⟨X, sub τ'⟩ :: Δ)] + grind [narrow] + +/-- A type remains well-formed under context strengthening. -/ +lemma strengthen (wf : σ.Wf (Γ ++ ⟨X, Binding.ty τ⟩ :: Δ)) : σ.Wf (Γ ++ Δ) := by + generalize eq : Γ ++ ⟨X, Binding.ty τ⟩ :: Δ = Θ at wf + induction wf generalizing Γ with + | all => apply all (free_union [Context.dom] Var) <;> grind + | _ => grind [dlookup_append] + +variable [HasFresh Var] in +/-- A type remains well-formed under context substitution (of a well-formed type). -/ +lemma map_subst (wf_σ : σ.Wf (Γ ++ ⟨X, Binding.sub τ⟩ :: Δ)) (wf_τ' : τ'.Wf Δ) + (ok : (Γ.map_val (·[X:=τ']) ++ Δ)✓) : σ[X:=τ'].Wf <| Γ.map_val (·[X:=τ']) ++ Δ := by + have := @map_val_mem Var (Binding Var) + generalize eq : Γ ++ ⟨X, Binding.sub τ⟩ :: Δ = Θ at wf_σ + induction wf_σ generalizing Γ τ' + case all γ _ _ _ _ _ _ => + subst eq + apply all (free_union [dom] Var) + · grind + · intro X' _ + have : (map_val (·[X:=τ']) (⟨X', Binding.sub γ⟩ :: Γ) ++ Δ)✓ := by grind [keys_append] + grind [open_subst_var] + all_goals grind [weaken_head, dlookup_append] + +variable [HasFresh Var] in +/-- A type remains well-formed under opening (to a well-formed type). -/ +lemma open_lc (ok_Γ : Γ✓) (wf_all : (Ty.all σ τ).Wf Γ) (wf_δ : δ.Wf Γ) : (τ ^ᵞ δ).Wf Γ := by + cases wf_all with | all => + let ⟨X, _⟩ := fresh_exists <| free_union [fv, Context.dom] Var + have : Γ = Context.map_val (·[X:=δ]) [] ++ Γ := by grind + grind [open_subst_intro, map_subst] + +/-- A type bound in a context is well formed. -/ +lemma of_bind_ty (wf : Γ.Wf) (bind : Binding.ty σ ∈ Γ.dlookup X) : σ.Wf Γ := by + induction wf <;> grind [weaken_head] + +/-- A type at the head of a well-formed context is well-formed. -/ +lemma of_env_ty (wf : Env.Wf (⟨X, Binding.ty σ⟩ :: Γ)) : σ.Wf Γ := by + cases wf + assumption + +/-- A subtype at the head of a well-formed context is well-formed. -/ +lemma of_env_sub (wf : Env.Wf (⟨X, Binding.sub σ⟩ :: Γ)) : σ.Wf Γ := by + cases wf + assumption + +variable [HasFresh Var] in +/-- A variable not appearing in a context does not appear in its well-formed types. -/ +lemma nmem_fv {σ : Ty Var} (wf : σ.Wf Γ) (nmem : X ∉ Γ.dom) : X ∉ σ.fv := by + induction wf with + | all => have := fresh_exists <| free_union [dom] Var; grind [nmem_fv_open, openRec_lc] + | _ => grind + +end Ty.Wf + +namespace Env.Wf + +open Context List Binding + +/-- A context remains well-formed under narrowing (of a well-formed subtype). -/ +lemma narrow (wf_env : Env.Wf (Γ ++ ⟨X, Binding.sub τ⟩ :: Δ)) (wf_τ' : τ'.Wf Δ) : + Env.Wf (Γ ++ ⟨X, Binding.sub τ'⟩ :: Δ) := by + induction Γ <;> cases wf_env <;> + grind [Ty.Wf.narrow, keys_append, eq_nil_of_append_eq_nil, cases Env.Wf] + +/-- A context remains well-formed under strengthening. -/ +lemma strengthen (wf : Env.Wf <| Γ ++ ⟨X, Binding.ty τ⟩ :: Δ) : Env.Wf <| Γ ++ Δ := by + induction Γ <;> cases wf <;> grind [Ty.Wf.strengthen, keys_append] + +variable [HasFresh Var] in +/-- A context remains well-formed under substitution (of a well-formed type). -/ +lemma map_subst (wf_env : Env.Wf (Γ ++ ⟨X, Binding.sub τ⟩ :: Δ)) (wf_τ' : τ'.Wf Δ) : + Env.Wf <| Γ.map_val (·[X:=τ']) ++ Δ := by + induction Γ generalizing wf_τ' Δ τ' <;> cases wf_env + case nil => grind + case cons.sub | cons.ty => constructor <;> grind [Ty.Wf.map_subst, keys_append] + +variable [HasFresh Var] + +/-- A well-formed context is unchaged by substituting for a free key. -/ +lemma map_subst_nmem (Γ : Env Var) (X : Var) (σ : Ty Var) (wf : Γ.Wf) (nmem : X ∉ Γ.dom) : + Γ = Γ.map_val (·[X:=σ]) := by + induction wf <;> grind [Ty.Wf.nmem_fv, Binding.subst_fresh] + +end Env.Wf + +end LambdaCalculus.LocallyNameless.Fsub diff --git a/Cslib/Languages/LambdaCalculus/LocallyNameless/Stlc/Basic.lean b/Cslib/Languages/LambdaCalculus/LocallyNameless/Stlc/Basic.lean index 5e28a18f..016f5e02 100644 --- a/Cslib/Languages/LambdaCalculus/LocallyNameless/Stlc/Basic.lean +++ b/Cslib/Languages/LambdaCalculus/LocallyNameless/Stlc/Basic.lean @@ -102,7 +102,7 @@ lemma subst_aux (h : Δ ++ ⟨x, σ⟩ :: Γ ⊢ t ∶ τ) (der : Γ ⊢ s ∶ case var x' τ ok mem => simp only [subst_fvar] subst eq - cases (Context.wf_perm (by simp) ok : (⟨x, σ⟩ :: Δ ++ Γ)✓) + cases ((List.perm_nodupKeys (by simp)).mp ok : (⟨x, σ⟩ :: Δ ++ Γ)✓) case cons ok_weak _ => observe perm : (Γ ++ Δ).Perm (Δ ++ Γ) by_cases h : x = x' <;> simp only [h] @@ -120,7 +120,7 @@ lemma subst_aux (h : Δ ++ ⟨x, σ⟩ :: Γ ⊢ t ∶ τ) (der : Γ ⊢ s ∶ match mem with | _ => simp_all rw [eq'] refine (weaken der ?_).perm perm - exact Context.wf_perm (id (List.Perm.symm perm)) ok_weak + grind case abs σ Γ' t T2 xs ih' ih => apply Typing.abs (free_union Var) intros