Prove Expr.typeCheck_complete for full language with Var, Lam, App#8
Prove Expr.typeCheck_complete for full language with Var, Lam, App#8
Conversation
…eck_complete - Add Vec type and :: notation to Tibi/Util.lean - Restore Ty, Locals, Var, Lam, App in Tibi/Syntax/Expr.lean - Create Tibi/Semantics/Env.lean with Env type and lookup - Update Tibi/Semantics.lean with Eval for full Expr ctx ty - Restore full HasType and typeCheck in Tibi/Typing.lean - Update Tibi/Interpreter.lean for Expr ctx ty with Env - Update Tibi/Compiler.lean for Expr ctx ty - Update Tibi/Parser.lean to return Expr .nil .int - Update Tibi/Top.lean for new types - Prove typeCheck_correct (strong form), typeCheck_complete, HasType.det, Decidable instance, and type_safe in Props/Typing.lean - Update Props/Compiler.lean for new Expr ctx ty type Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ype.det Co-authored-by: nahcnuj <2093896+nahcnuj@users.noreply.github.com>
|
@copilot |
Co-authored-by: nahcnuj <2093896+nahcnuj@users.noreply.github.com>
Fixed in 85630ab. There were three CI errors:
|
|
@copilot |
Co-authored-by: nahcnuj <2093896+nahcnuj@users.noreply.github.com>
Fixed in 3ed9b48. The previous fix placed |
|
@copilot |
…ard Eval in App case Co-authored-by: nahcnuj <2093896+nahcnuj@users.noreply.github.com>
Fixed in e8938c0. There were two errors in
|
|
@copilot |
…n infer ctx Co-authored-by: nahcnuj <2093896+nahcnuj@users.noreply.github.com>
Fixed in aba9c0c. The CI failure was Regarding the rebase: I'm not able to perform |
There was a problem hiding this comment.
Pull request overview
This PR restores an intrinsically-typed expression language (Expr ctx ty) with variables and functions, threads indices through typing/semantics/interpreter/compiler, and updates the typing proofs to include typeCheck_complete for the expanded language.
Changes:
- Reintroduced
Vec, addedEnv, and restored indexed syntax (Ty,Locals,Expr) withVar/Lam/App. - Updated
Typing,Semantics,Interpreter,Compiler,Parser, andTopto use the new indexedExpr. - Expanded typing metatheory proofs (
typeCheck_correct,typeCheck_complete, decidability, andtype_safe) for the new constructors.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| Tibi/Util.lean | Adds Vec to support indexed contexts. |
| Tibi/Syntax/Expr.lean | Restores indexed Ty, Locals, and Expr with Var/Lam/App. |
| Tibi/Semantics/Env.lean | Introduces Env and lookup for Locals. |
| Tibi/Semantics.lean | Updates Eval relation to indexed expressions and adds Var/Lam/App cases. |
| Tibi/Typing.lean | Restores Typ, HasType, and Expr.typeCheck for the full syntax. |
| Tibi/Interpreter.lean | Updates evaluator to accept an environment and indexed expressions. |
| Tibi/Compiler.lean | Updates compiler signature to indexed expressions; adds Unimplemented error cases. |
| Tibi/Parser.lean | Narrows parser result type to Expr .nil .int. |
| Tibi/Top.lean | Updates REPL/run pipeline to use new evaluator/compiler signatures. |
| Tibi/Props/Typing.lean | Updates typing proofs (correctness/completeness/decidability/type safety) for new constructors. |
| Tibi/Props/Compiler.lean | Adjusts compiler correctness proof to the new indexed Expr. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| | .Lam _, .Lam .. => ⟨_, rfl⟩ | ||
| | .App _ _, .App .. => ⟨_, rfl⟩ |
There was a problem hiding this comment.
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.
| | .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⟩ |
| 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 |
There was a problem hiding this comment.
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.
| 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₁) |
There was a problem hiding this comment.
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.
| 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₁) |
| | 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₁) |
There was a problem hiding this comment.
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).
| | 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₂) |
| | nil : Env Vec.nil | ||
| | cons : Ty.interp a → Env ctx → Env (a :: ctx) | ||
|
|
||
| infix:67 " :: " => Env.cons |
There was a problem hiding this comment.
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.
| infix:67 " :: " => Env.cons | |
| scoped infixr:67 " :: " => Env.cons |
| | .Lam e => | ||
| match e.typeCheck with | ||
| | .found t h => .found (.Fn (.Var 0) t) <| .Lam h | ||
| | .unknown => .unknown |
There was a problem hiding this comment.
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.
| | _, .Lam e', x => | ||
| match e'.eval (x :: env) x with | ||
| | .found (.ok v) h => .found (.ok <| fun _ => v) <| .Lam h | ||
| | .unknown => .unknown |
There was a problem hiding this comment.
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.
| | _, .Lam e', x => | |
| match e'.eval (x :: env) x with | |
| | .found (.ok v) h => .found (.ok <| fun _ => v) <| .Lam h | |
| | .unknown => .unknown | |
| | _, .Lam _ , _ => | |
| .unknown |
| | .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 |
There was a problem hiding this comment.
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.
| 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 |
There was a problem hiding this comment.
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).
| | .App _ _, _, _, hc => by simp [Expr.compile] at hc | |
| | .App _ _, _, _, hc => by | |
| simp [Expr.compile] at hc | |
| cases hc |
nahcnuj
left a comment
There was a problem hiding this comment.
SyntaxとしてはVar, Lam, Appまで受け付けるようにした、という理解で良いですか?
READMEに構文定義を書いているのでそちらもアップデートしてください。
…Lam/App Co-authored-by: nahcnuj <2093896+nahcnuj@users.noreply.github.com>
Yes, that understanding is correct — Updated the README syntax section in 329808f to include all three constructs, each marked with |
Vectype inTibi/Util.leanExpr ctx ty(withTy,Locals,Var,Lam,App) inTibi/Syntax/Expr.leanTibi/Semantics/Env.leanwithEnvenvironment typeTibi/Semantics.leanto use indexedExpr ctx tyandty.interpHasTypeandtypeCheck(forVar,Lam,App) inTibi/Typing.leanTibi/Interpreter.leanfor new indexedExpr ctx tyTibi/Compiler.leanfor new indexedExpr ctx ty; add-- TODOto unimplemented casesTibi/Parser.leanto returnExpr .nil .intTibi/Top.leanfor new typesREADME.mdsyntax section to include Var/Lam/App (marked as not yet implemented in parser)Tibi/Props/Typing.leanTibi/Props/Compiler.leanOriginal prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.