Skip to content
25 changes: 16 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,25 @@ You can see the returned integer in the `pre` element.
### Syntax

```ebnf
ws = { " " | "\n" | "\r" | "\t" } ;
ws = { " " | "\n" | "\r" | "\t" } ;

non-zero-digit = "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;
digit = "0" | non-zero-digit ;
nat-number = "0"
| non-zero-digit , { digit } ;
non-zero-digit = "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;
digit = "0" | non-zero-digit ;
nat-number = "0"
| non-zero-digit , { digit } ;

sign = "+" | "-" ;
int-number = sign , nat-number ;
sign = "+" | "-" ;
int-number = sign , nat-number ;

expr = nat-number , ws
| int-number , ws;
identifier = ( letter | "_" ) , { letter | digit | "_" } ;
letter = "a" | ... | "z" | "A" | ... | "Z" ;

expr = nat-number , ws
| int-number , ws
| identifier , ws (* variable -- not yet implemented in parser *)
| "\" , identifier , "." , ws , expr , ws (* lambda abstraction -- not yet implemented in parser *)
| expr , expr , ws (* function application -- not yet implemented in parser *)
;
```

<!--
Expand Down
7 changes: 6 additions & 1 deletion Tibi/Compiler.lean
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,21 @@ open Wasm.Value

inductive CompileError
| OutOfBounds_Int64 (n : Int)
| Unimplemented

def Expr.compile : Expr → Except CompileError (List Wasm.Instr)
def Expr.compile : Expr ctx ty → Except CompileError (List Wasm.Instr)
| .Const n =>
if h : (-Int64.size : Int) ≤ n ∧ n < Int64.size then
.ok [.i64__const <| Int64.mk ⟨n, h.right, h.left⟩]
else
.error <| .OutOfBounds_Int64 n
| .Var _ =>
.error .Unimplemented -- TODO: implement compilation of variable expressions
| _ => .error .Unimplemented -- TODO: implement compilation of lambda and application expressions

def CompileError.toString : CompileError → String
| .OutOfBounds_Int64 n => s!"{n} is out of Int64 bounds, should be satisfied that -2{Nat.toSuperscriptString 63} ≤ n < 2{Nat.toSuperscriptString 63}"
| .Unimplemented => "Unimplemented"

instance : ToString CompileError where
toString := CompileError.toString
19 changes: 17 additions & 2 deletions Tibi/Interpreter.lean
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,26 @@ import Tibi.Util

namespace Tibi

def Expr.eval : (e : Expr) → {{ r | Eval e r }}
| .Const n =>
def Expr.eval (env : Env ctx) : {ty : Ty} → (e : Expr ctx ty) → ty.dom → {{ r | Eval e r }}
| _, .Const n, _ =>
if h : (-Int64.size : Int) ≤ n ∧ n < Int64.size then
.found (.ok <| Int64.mk ⟨n, h.right, h.left⟩) <| Eval.Const h.right h.left
else
Decidable.not_and_iff_or_not_not.mp h |>.by_cases
(fun h => .found (.error <| .OutOfBounds_Int64 n) <| Eval.ConstErr_ge h)
(fun h => .found (.error <| .OutOfBounds_Int64 n) <| Eval.ConstErr_lt h)
| _, .Var x, _ =>
.found (.ok <| env.lookup x) <| .Var x env
| _, .Lam e', x =>
match e'.eval (x :: env) x with
| .found (.ok v) h => .found (.ok <| fun _ => v) <| .Lam h
| .unknown => .unknown
Comment on lines +18 to +21
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Lam evaluation path computes v by evaluating the body once with the specific input x, then returns the constant function fun _ => v. That makes lambda values independent of the argument (i.e., not a real closure). If Ty.interp (fn a b) is a → b, Lam should return a function that (for each argument) evaluates the body under the extended environment for that argument.

Suggested change
| _, .Lam e', x =>
match e'.eval (x :: env) x with
| .found (.ok v) h => .found (.ok <| fun _ => v) <| .Lam h
| .unknown => .unknown
| _, .Lam _ , _ =>
.unknown

Copilot uses AI. Check for mistakes.
| .int, .App (dom := .int) (.Lam e₁) e₂, _ =>
match e₂.eval env () with
| .found (.ok v) h₂ =>
match e₁.eval (v :: env) v with
| .found (.ok v) h₁ =>
.found (.ok <| v) <| .App h₁ h₂
| _ => .unknown
| _ => .unknown
| _, _, _ => .unknown
Comment on lines +22 to +30
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

App evaluation is currently hard-coded to the very specific shape (.int, App (dom := .int) (.Lam e₁) e₂, _) and falls back to .unknown for other well-typed applications (e.g. non-int domains or when the function expression is not syntactically a Lam). Since Expr.App is part of the core language and Ty.interp (fn a b) is a function type, eval should evaluate the function expression to a value and apply it uniformly for all domains/results, not only this special case.

Copilot uses AI. Check for mistakes.
6 changes: 3 additions & 3 deletions Tibi/Parser.lean
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ private def intNumber : Parser Int :=
| _ => sgn n
)

private def parser : Parser Expr :=
private def parser : Parser (Expr .nil .int) :=
(
natNumber >> ws -- >> ParserT.optional ((ParserT.char '@' : Parser _) >> keyword "Int" >> ws)
|>.map fun (n, _) => .Const n
Expand All @@ -72,10 +72,10 @@ private def parser : Parser Expr :=
|>.map fun (n, _) => .Const n
)

instance : Inhabited (Parser Expr) where
instance : Inhabited (Parser (Expr .nil .int)) where
default := parser

private partial def parse' : ReaderT (IO String) Parser Expr := do
private partial def parse' : ReaderT (IO String) Parser (Expr .nil .int) := do
fun getLine cs =>
match parser cs with
| .error .UnexpectedEndOfInput => do
Expand Down
8 changes: 4 additions & 4 deletions Tibi/Props/Compiler.lean
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import Tibi.Wasm.Semantics
namespace Tibi

theorem Wasm.Reduction.of_has_type_of_eval_ok_of_compile_ok -- Expr.compile_ok_of_has_type_of_eval
: {e : Expr} → HasType e _ty → Eval e (.ok v) → {_ : e.compile = .ok instrs}
: {e : Expr ctx .int} → HasType e _ty → Eval e (.ok v) → e.compile = .ok instrs
→ Wasm.Reduction
{ instrs := instrs ++ K, stack, store, framestate }
{ instrs := K, stack := .Value (.Num (.Int64 v)) :: stack, store, framestate }
| .Const n, .Int64 hLt hGe, .Const _ _, hc => by
have : (Expr.Const n).compile = .ok [.i64__const (Int64.mk ⟨n, hLt, hGe⟩)] := by
-- dsimp [Expr.compile]
| .Const n, .Int64 _ _, .Const hLt hGe, hc => by
have : (Expr.Const n : Expr ctx .int).compile = .ok [.i64__const (Int64.mk ⟨n, hLt, hGe⟩)] := by
apply dite_cond_eq_true <| eq_true <| And.intro hGe hLt
have : instrs = [.i64__const ⟨n, hLt, hGe⟩ ] := Except.ok.inj <| Eq.trans hc.symm this
rw [this]
exact .i64__const
| .App _ _, _, _, hc => by simp [Expr.compile] at hc
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This branch ends with simp [Expr.compile] at hc, which only rewrites the hypothesis hc and does not close the goal. If the intent is to discharge the case by contradiction (since Expr.compile is .error on App), you likely need to derive False from hc and then eliminate it (e.g., by cases hc after simplification).

Suggested change
| .App _ _, _, _, hc => by simp [Expr.compile] at hc
| .App _ _, _, _, hc => by
simp [Expr.compile] at hc
cases hc

Copilot uses AI. Check for mistakes.
47 changes: 31 additions & 16 deletions Tibi/Props/Typing.lean
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,38 @@ import Tibi.Typing
namespace Tibi

theorem HasType.det (h₁ : HasType e t₁) (h₂ : HasType e t₂) : t₁ = t₂ := by
cases h₁ <;> cases h₂ <;> rfl
induction h₁ generalizing t₂ with
| Int64 => cases h₂; rfl
| Var => cases h₂; rfl
| Lam _ ih => cases h₂ with | Lam h' => exact congrArg (Typ.Fn (.Var 0)) (ih h')
| App _ _ ihf _ =>
cases h₂ with
| App hf' _ =>
have h := ihf hf'
simp only [Typ.Fn.injEq] at h
exact h.2

theorem Expr.typeCheck_correct {e : Expr}
: (ht : HasType e ty) → e.typeCheck = .found ty ht
theorem Expr.typeCheck_correct : (ht : HasType e t) → e.typeCheck = .found t ht
| .Int64 (n := n) hLt hGe =>
have := eq_true <| And.intro (ge_iff_le.mp hGe) hLt
dite_cond_eq_true this
| .Var => rfl
| .Lam h => by
have ih := typeCheck_correct h
simp only [Expr.typeCheck, ih]
| .App hf hv => by
have ihf := typeCheck_correct hf
have ihv := typeCheck_correct hv
simp [Expr.typeCheck, ihf, ihv]

theorem Expr.typeCheck_complete {e : Expr}
: e.typeCheck = .unknown → ¬ HasType e ty
:= by
dsimp [Expr.typeCheck]
intro h (ht : HasType e ty)
match ht with
| .Int64 hLt hGe =>
have := eq_true <| And.intro (ge_iff_le.mp hGe) hLt
have := h ▸ dite_cond_eq_true this
exact Maybe.noConfusion this
theorem Expr.typeCheck_complete {e : Expr ctx ty}
: e.typeCheck = .unknown → ¬ HasType e t
:= fun h ht => by
have := typeCheck_correct ht
rw [h] at this
exact Maybe.noConfusion this

instance (e : Expr) (t : Typ) : Decidable (HasType e t) :=
instance (e : Expr ctx ty) (t : Typ) : Decidable (HasType e t) :=
match h : e.typeCheck with
| .found t' ht' =>
if heq : t = t' then
Expand All @@ -32,8 +44,11 @@ instance (e : Expr) (t : Typ) : Decidable (HasType e t) :=
isFalse fun ht => heq (HasType.det ht ht')
| .unknown => isFalse (Expr.typeCheck_complete h)

theorem type_safe {e : Expr}
theorem type_safe {e : Expr ctx ty}
: HasType e t → Eval e r → ∃ v, r = .ok v
| .Int64 hLt hGe, .Const .. => ⟨Int64.mk ⟨_, hLt, hGe⟩, rfl⟩
| .Int64 hLt hGe, .Const .. => ⟨_, rfl⟩
| .Int64 hLt _, .ConstErr_lt h => absurd hLt h
| .Int64 _ hGe, .ConstErr_ge h => absurd hGe h
| .Var, .Var .. => ⟨_, rfl⟩
| .Lam _, .Lam .. => ⟨_, rfl⟩
| .App _ _, .App .. => ⟨_, rfl⟩
Comment on lines +53 to +54
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new type_safe cases for .Lam and .App are proved by ⟨_, rfl⟩, which is only possible because the current Eval.Lam/Eval.App constructors always produce an .ok result and don’t model the real failure modes of application/body evaluation. Once Eval is fixed to depend on Env/argument evaluation, these cases will likely need nontrivial proofs that propagate the IHs rather than finishing by reflexivity.

Suggested change
| .Lam _, .Lam .. => ⟨_, rfl⟩
| .App _ _, .App .. => ⟨_, rfl⟩
| .Lam _, hEval =>
-- Use the structure of the evaluation derivation for lambdas
cases hEval with
| Lam .. => exact ⟨_, rfl⟩
| .App _ _, hEval =>
-- Use the structure of the evaluation derivation for applications
cases hEval with
| App .. => exact ⟨_, rfl⟩

Copilot uses AI. Check for mistakes.
18 changes: 14 additions & 4 deletions Tibi/Semantics.lean
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
import Tibi.FinInt
import Tibi.Semantics.Env
import Tibi.Syntax

namespace Tibi

inductive EvalError
| OutOfBounds_Int64 (n : Int)

inductive Eval : Expr → Except EvalError Int64 → Prop
| Const {n : Int} (hLt : n < Int64.size) (hGe : n >= -Int64.size) : Eval (.Const n) (.ok <| Int64.mk ⟨n, hLt, hGe⟩)
| ConstErr_lt {n : Int} (h : ¬ n < Int64.size) : Eval (.Const n) (.error <| .OutOfBounds_Int64 n)
| ConstErr_ge {n : Int} (h : ¬ n >= -Int64.size) : Eval (.Const n) (.error <| .OutOfBounds_Int64 n)
inductive Eval : (Expr ctx ty) → Except EvalError ty.interp → Prop
| Const {n : Int} (hLt : n < Int64.size) (hGe : n >= -Int64.size)
: Eval (.Const n) (.ok <| Int64.mk ⟨n, hLt, hGe⟩)
| ConstErr_lt {n : Int} (h : ¬ n < Int64.size)
: Eval (.Const n) (.error <| .OutOfBounds_Int64 n)
| ConstErr_ge {n : Int} (h : ¬ n >= -Int64.size)
: Eval (.Const n) (.error <| .OutOfBounds_Int64 n)
| Var (x : Locals k ctx ty) (env : Env ctx)
: Eval (.Var x) (.ok <| env.lookup x)
| Lam (d : Eval e (.ok v))
: Eval (.Lam e) (.ok <| fun _ => v)
| App (d₁ : Eval e₁ (.ok v₁)) (d₂ : Eval e₂ (.ok v₂))
: Eval (.App (.Lam e₁) e₂) (.ok <| v₁)
Comment on lines +10 to +22
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eval is not parameterized by an environment, but the Var constructor introduces an env : Env ctx anyway. This makes Eval (.Var x) ... provable for arbitrary environments and loses the intended dependence on runtime state, which will undermine determinism and later safety/correctness proofs. Consider changing the relation to Eval (env : Env ctx) (e : Expr ctx ty) : Except EvalError ty.interp → Prop (or similar) and thread env through all constructors.

Suggested change
inductive Eval : (Expr ctx ty) → Except EvalError ty.interp → Prop
| Const {n : Int} (hLt : n < Int64.size) (hGe : n >= -Int64.size)
: Eval (.Const n) (.ok <| Int64.mk ⟨n, hLt, hGe⟩)
| ConstErr_lt {n : Int} (h : ¬ n < Int64.size)
: Eval (.Const n) (.error <| .OutOfBounds_Int64 n)
| ConstErr_ge {n : Int} (h : ¬ n >= -Int64.size)
: Eval (.Const n) (.error <| .OutOfBounds_Int64 n)
| Var (x : Locals k ctx ty) (env : Env ctx)
: Eval (.Var x) (.ok <| env.lookup x)
| Lam (d : Eval e (.ok v))
: Eval (.Lam e) (.ok <| fun _ => v)
| App (d₁ : Eval e₁ (.ok v₁)) (d₂ : Eval e₂ (.ok v₂))
: Eval (.App (.Lam e₁) e₂) (.ok <| v₁)
inductive Eval (env : Env ctx) : (Expr ctx ty) → Except EvalError ty.interp → Prop
| Const {n : Int} (hLt : n < Int64.size) (hGe : n >= -Int64.size)
: Eval env (.Const n) (.ok <| Int64.mk ⟨n, hLt, hGe⟩)
| ConstErr_lt {n : Int} (h : ¬ n < Int64.size)
: Eval env (.Const n) (.error <| .OutOfBounds_Int64 n)
| ConstErr_ge {n : Int} (h : ¬ n >= -Int64.size)
: Eval env (.Const n) (.error <| .OutOfBounds_Int64 n)
| Var (x : Locals k ctx ty)
: Eval env (.Var x) (.ok <| env.lookup x)
| Lam (d : Eval env e (.ok v))
: Eval env (.Lam e) (.ok <| fun _ => v)
| App (d₁ : Eval env e₁ (.ok v₁)) (d₂ : Eval env e₂ (.ok v₂))
: Eval env (.App (.Lam e₁) e₂) (.ok <| v₁)

Copilot uses AI. Check for mistakes.
Comment on lines +19 to +22
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Lam/App rules currently ignore the argument: Lam returns fun _ => v after evaluating the body once, and App returns v₁ without relating it to the argument value (it even requires d₂ but never uses v₂). This makes function semantics incorrect (every lambda becomes a constant function) and makes App unrelated to substitution/application. Rework these constructors to model closures/application (likely using an explicit Env parameter and evaluating the body under an extended environment for the given argument).

Suggested change
| Lam (d : Eval e (.ok v))
: Eval (.Lam e) (.ok <| fun _ => v)
| App (d₁ : Eval e₁ (.ok v₁)) (d₂ : Eval e₂ (.ok v₂))
: Eval (.App (.Lam e₁) e₂) (.ok <| v₁)
| Lam {e v} (d : ∀ a, Eval e (.ok (v a)))
: Eval (.Lam e) (.ok v)
| App (d₁ : Eval e₁ (.ok v₁)) (d₂ : Eval e₂ (.ok v₂))
: Eval (.App (.Lam e₁) e₂) (.ok <| v₁ v₂)

Copilot uses AI. Check for mistakes.

def EvalError.toString : EvalError → String
| .OutOfBounds_Int64 n => s!"{n} is out of Int64 bounds, should be satisfied that -2{Nat.toSuperscriptString 63} ≤ n < 2{Nat.toSuperscriptString 63}"
Expand Down
14 changes: 14 additions & 0 deletions Tibi/Semantics/Env.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Tibi.Syntax
import Tibi.Util

namespace Tibi

inductive Env : Vec Ty n → Type where
| nil : Env Vec.nil
| cons : Ty.interp a → Env ctx → Env (a :: ctx)

infix:67 " :: " => Env.cons
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Env.lean redefines the global :: notation (previously Vec.cons) to mean Env.cons. Since notation isn’t type-directed, importing Tibi.Semantics.Env will overwrite Vec’s :: and can break/obscure code that constructs Vec contexts elsewhere. Consider using a different notation for environments (or a scoped notation) to avoid clobbering Vec’s constructor syntax.

Suggested change
infix:67 " :: " => Env.cons
scoped infixr:67 " :: " => Env.cons

Copilot uses AI. Check for mistakes.

def Env.lookup : Locals i ctx ty → Env ctx → (Ty.cls ty ty).interp
| .stop, x :: _ => x
| .pop k, _ :: xs => xs.lookup k
55 changes: 50 additions & 5 deletions Tibi/Syntax/Expr.lean
Original file line number Diff line number Diff line change
@@ -1,10 +1,55 @@
import Tibi.FinInt
import Tibi.Util

namespace Tibi

inductive Expr
| Const : Int → Expr
inductive Ty where
| int
| fn (a b : Ty)
| cls (a b : Ty)

instance : ToString Expr where
toString
| .Const n => s!"<Const {n}>"
@[reducible]
def Ty.interp : Ty → Type
| int => Int64
| fn a b => a.interp → b.interp
| cls _ b => b.interp

@[reducible]
def Ty.dom : Ty → Type
| int => Unit
| fn a _ => a.interp
| cls a _ => a.interp

def Ty.toString : Ty → String
| int => "Int"
| fn a b => s!"{a.toString} -> {b.toString}"
| cls _ b => s!"{b.toString}"

instance : ToString Ty where
toString := Ty.toString

inductive Locals : Fin n → Tibi.Vec Ty n → Ty → Type where
| stop : Locals 0 (ty :: ctx) ty
| pop : Locals k ctx ty → Locals k.succ (u :: ctx) ty

def Locals.idx : Locals n ctx ty → Fin n.succ
| stop => Fin.mk 0 (Fin.succ_pos _)
| pop k => k.idx.succ

instance : ToString (Locals n ctx ty) where
toString x := s!"${x.idx}"

inductive Expr : Vec Ty n → Ty → Type where
| Const : Int → Expr ctx .int
| Var : Locals i ctx ty → Expr ctx (.cls ty ty)
| Lam : Expr (dom :: ctx) (.cls dom ran) → Expr ctx (.fn dom ran)
| App : Expr ctx (.fn dom ran) → Expr ctx dom → Expr ctx ran

def Expr.toString : Expr ctx ty → String
| Const n => s!"<Const {n}>"
| Var x => s!"<Var {x} @{ty}>"
| Lam e => s!"<Lam {e.toString}>"
| App e₁ e₂ => s!"<App {e₁.toString} {e₂.toString}>"

instance : ToString (Expr ctx ty) where
toString := Expr.toString
4 changes: 2 additions & 2 deletions Tibi/Top.lean
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ partial def repl (stream : IO.FS.Stream) (n : Nat := 0) : IO UInt32 := do
| .ok (expr, []) =>
match expr.typeCheck with
| .found t _ =>
match expr.eval with
match expr.eval .nil () with
| .found (.ok v) _ => IO.println s!"- : {t} = {v}"
| .found (.error _) _ => panic! "TODO: prove that this branch cannot be reached (type-safe)"
| .unknown => panic! "TODO: prove that this branch cannot be reached (type-safe)"
Expand All @@ -37,7 +37,7 @@ def run (inStream : IO.FS.Stream) : IO ByteArray :=
| .some <| .error e => .throw e.toString
)
>>= (
fun (e : Option Expr) =>
fun (e : Option (Expr .nil .int)) =>
match e with
| .none => pure Wasm.empty
| .some e =>
Expand Down
33 changes: 30 additions & 3 deletions Tibi/Typing.lean
Original file line number Diff line number Diff line change
@@ -1,25 +1,52 @@
import Tibi.FinInt
import Tibi.Semantics
import Tibi.Syntax
import Tibi.Util

namespace Tibi

inductive Typ
| Var (idx : Nat)
| Int64
| Fn (a : Typ) (b : Typ)
deriving DecidableEq

def Typ.toString : Typ → String
| Int64 => "Int"
| Var idx => s!"α{idx}"
| Int64 => "Int"
| Fn a b => s!"{a.toString} -> {b.toString}"

instance : ToString Typ where
toString := Typ.toString

inductive HasType : Expr → Typ → Prop
def Ty.toTyp : Ty → Typ
| int => .Int64
| fn a b => .Fn a.toTyp b.toTyp
| cls _ b => b.toTyp

inductive HasType : Expr ctx ty → Typ → Prop
| Int64 {n : Int} (hLt : n < Int64.size) (hGe : n ≥ -Int64.size) : HasType (.Const n) .Int64
| Var {x : Locals i ctx ty} : HasType (.Var x) (Ty.cls ty ty).toTyp
| Lam (h : HasType e t) : HasType (.Lam e) (.Fn (.Var 0) t)
| App (hf : HasType f (.Fn dom ran)) (hv : HasType v dom) : HasType (.App f v) ran
Comment on lines +27 to +31
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HasType.Lam hard-codes the parameter type as Typ.Var 0, but this repository already has Ty.toTyp; using .Var 0 makes lambda types unrelated to the actual dom index on Expr (.fn dom ran) and effectively prevents well-typed applications of lambdas (since no expression can synthesize Typ.Var 0). Define the lambda type using the dom from the expression indices (e.g., Typ.Fn dom.toTyp t) so Lam/App interact consistently.

Copilot uses AI. Check for mistakes.

def Expr.typeCheck : (e : Expr) → {{ t | HasType e t }}
def Expr.typeCheck : (e : Expr ctx ty) → {{ t | HasType e t }}
| .Const n =>
if h : -Int64.size ≤ n ∧ n < Int64.size then
.found .Int64 <| .Int64 h.right h.left
else
.unknown
| .Var _ =>
.found _ .Var
| .Lam e =>
match e.typeCheck with
| .found t h => .found (.Fn (.Var 0) t) <| .Lam h
| .unknown => .unknown
Comment on lines +41 to +44
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expr.typeCheck’s Lam branch returns Fn (Var 0) t, which does not reflect the lambda’s indexed domain type (dom) and will not line up with Ty.toTyp/App’s argument-type equality check. This will cause typeCheck to return .unknown for applications that should type-check. Use the domain dom from the expression indices when constructing the function type.

Copilot uses AI. Check for mistakes.
| .App f v =>
match f.typeCheck, v.typeCheck with
| .found (.Fn a b) hf, .found a' hv =>
if h : a = a' then
.found b <| .App hf (h ▸ hv)
else
.unknown
| _, _ => .unknown
6 changes: 6 additions & 0 deletions Tibi/Util.lean
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,9 @@ inductive Maybe (p : α → Prop)
| found (a : α) : p a → Maybe p

notation "{{ " x " | " p " }}" => Maybe (fun x => p)

inductive Vec (α : Type u) : Nat → Type u
| nil : Vec α 0
| cons : α → Vec α n → Vec α n.succ

infixr:67 " :: " => Vec.cons