2026-04-19 04:17:45 +00:00
|
|
|
import BidirTT
|
|
|
|
|
|
|
|
|
|
open BidirTT
|
|
|
|
|
|
|
|
|
|
inductive Expectation where
|
|
|
|
|
| okTy : Tm → Expectation
|
2026-04-19 13:55:05 +00:00
|
|
|
| okTyNorm : Tm → Tm → Expectation
|
2026-04-19 04:17:45 +00:00
|
|
|
| errContains : String → Expectation
|
|
|
|
|
|
|
|
|
|
private def renderType (ty : Val) : Except String Tm :=
|
|
|
|
|
BidirTT.quote 0 ty
|
|
|
|
|
|
2026-04-19 13:55:05 +00:00
|
|
|
private def renderNormal (tm : Tm) : Except String Tm := do
|
|
|
|
|
let v ← eval [] tm
|
|
|
|
|
quote 0 v
|
|
|
|
|
|
2026-04-19 04:17:45 +00:00
|
|
|
private def containsText (haystack needle : String) : Bool :=
|
|
|
|
|
needle.isEmpty || (haystack.splitOn needle).length > 1
|
|
|
|
|
|
|
|
|
|
structure TestCase where
|
|
|
|
|
name : String
|
|
|
|
|
input : Raw
|
|
|
|
|
expect : Expectation
|
|
|
|
|
|
|
|
|
|
def cases : List TestCase := [
|
|
|
|
|
⟨"U0 is typed by U1", Examples.univ0,
|
|
|
|
|
.okTy (.univ 1)⟩,
|
2026-04-19 13:55:05 +00:00
|
|
|
⟨"U0 subsumes into U2", .ann (.univ 0) (.univ 2),
|
|
|
|
|
.okTy (.univ 2)⟩,
|
|
|
|
|
⟨"Nat is typed by U0", .nat,
|
|
|
|
|
.okTy (.univ 0)⟩,
|
|
|
|
|
⟨"succ zero infers Nat", (.succ .zero),
|
|
|
|
|
.okTy .nat⟩,
|
2026-04-19 04:17:45 +00:00
|
|
|
⟨"id typechecks", Examples.idAnn,
|
|
|
|
|
.okTy (.pi (.univ 0) (.pi (.var 0) (.var 1)))⟩,
|
2026-04-19 13:55:05 +00:00
|
|
|
⟨"Pi subsumption is contravariant in the domain", .ann
|
|
|
|
|
(.ann
|
|
|
|
|
(.lam "A" (.var "A"))
|
|
|
|
|
(.pi "A" (.univ 1) (.univ 1)))
|
|
|
|
|
(.pi "A" (.univ 0) (.univ 2)),
|
|
|
|
|
.okTy (.pi (.univ 0) (.univ 2))⟩,
|
2026-04-19 04:17:45 +00:00
|
|
|
⟨"const typechecks", Examples.constAnn,
|
|
|
|
|
.okTy (.pi (.univ 0) (.pi (.univ 0) (.pi (.var 1) (.pi (.var 1) (.var 3)))))⟩,
|
|
|
|
|
⟨"swap typechecks", Examples.swapAnn,
|
|
|
|
|
.okTy (.pi (.univ 0) (.pi (.univ 0) (.pi (.sig (.var 1) (.var 1)) (.sig (.var 1) (.var 3)))))⟩,
|
|
|
|
|
⟨"dependent pair typechecks", Examples.depPairAnn,
|
|
|
|
|
.okTy (.sig (.univ 2) (.var 0))⟩,
|
2026-04-19 13:55:05 +00:00
|
|
|
⟨"dependent pair subsumes into a lifted Sigma", .ann Examples.depPairAnn
|
|
|
|
|
(.sig "A" (.univ 3) (.var "A")),
|
|
|
|
|
.okTy (.sig (.univ 3) (.var 0))⟩,
|
|
|
|
|
⟨"natElim computes on numerals", Examples.natFoldId,
|
|
|
|
|
.okTyNorm .nat (.succ (.succ .zero))⟩,
|
|
|
|
|
⟨"unitElim computes on tt", Examples.unitToNat,
|
|
|
|
|
.okTyNorm .nat (.succ (.succ .zero))⟩,
|
|
|
|
|
⟨"emptyElim builds absurd maps", Examples.absurdNat,
|
|
|
|
|
.okTy (.pi .empty .nat)⟩,
|
|
|
|
|
⟨"refl inhabits reflexive identity", Examples.reflZero,
|
|
|
|
|
.okTyNorm (.id .nat .zero .zero) .refl⟩,
|
|
|
|
|
⟨"idElim computes on refl", Examples.idElimNat,
|
|
|
|
|
.okTyNorm .nat .zero⟩,
|
2026-04-19 04:17:45 +00:00
|
|
|
⟨"fst infers the first projection", Examples.fstDepPair,
|
|
|
|
|
.okTy (.univ 2)⟩,
|
|
|
|
|
⟨"snd infers the dependent second projection", Examples.sndDepPair,
|
|
|
|
|
.okTy (.univ 1)⟩,
|
|
|
|
|
⟨"let infers through definitions", Examples.letUniverse,
|
|
|
|
|
.okTy (.univ 1)⟩,
|
2026-04-19 13:55:05 +00:00
|
|
|
⟨"bad succ rejected", Examples.badSucc,
|
|
|
|
|
.errContains "type mismatch: expected Nat, got U1"⟩,
|
2026-04-19 04:17:45 +00:00
|
|
|
⟨"self application rejected", Examples.omegaAnn,
|
|
|
|
|
.errContains "expected Pi type in application"⟩,
|
|
|
|
|
⟨"unknown variable rejected", Examples.unknownVar,
|
|
|
|
|
.errContains "unknown variable nope"⟩,
|
|
|
|
|
⟨"pair mismatch rejected at the Sigma body", Examples.pairMismatch,
|
|
|
|
|
.errContains "type mismatch: expected U1, got U2"⟩,
|
|
|
|
|
⟨"bad fst rejected", Examples.badFst,
|
2026-04-19 13:55:05 +00:00
|
|
|
.errContains "expected Sigma type in .1, got U1"⟩,
|
|
|
|
|
⟨"bad refl rejected", Examples.badRefl,
|
|
|
|
|
.errContains "refl cannot inhabit"⟩
|
2026-04-19 04:17:45 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
|
|
def runCase (tc : TestCase) : IO Bool := do
|
|
|
|
|
match tc.expect, checkTop tc.input with
|
|
|
|
|
| .okTy expectedTy, .ok (_, ty) =>
|
|
|
|
|
match renderType ty with
|
|
|
|
|
| Except.ok actualTy =>
|
|
|
|
|
if actualTy == expectedTy then
|
2026-04-19 15:03:40 +00:00
|
|
|
IO.println s!"pass {tc.name}"
|
2026-04-19 04:17:45 +00:00
|
|
|
pure true
|
|
|
|
|
else
|
2026-04-19 15:03:40 +00:00
|
|
|
IO.println s!"fail {tc.name} (expected type {BidirTT.prettyTm expectedTy}, got {BidirTT.prettyTm actualTy})"
|
2026-04-19 04:17:45 +00:00
|
|
|
pure false
|
|
|
|
|
| Except.error err =>
|
2026-04-19 15:03:40 +00:00
|
|
|
IO.println s!"fail {tc.name} (could not quote type: {err})"
|
2026-04-19 04:17:45 +00:00
|
|
|
pure false
|
2026-04-19 13:55:05 +00:00
|
|
|
| .okTyNorm expectedTy expectedNf, .ok (tm, ty) =>
|
|
|
|
|
match renderType ty, renderNormal tm with
|
|
|
|
|
| Except.ok actualTy, Except.ok actualNf =>
|
|
|
|
|
if actualTy == expectedTy && actualNf == expectedNf then
|
2026-04-19 15:03:40 +00:00
|
|
|
IO.println s!"pass {tc.name}"
|
2026-04-19 13:55:05 +00:00
|
|
|
pure true
|
|
|
|
|
else if actualTy != expectedTy then
|
2026-04-19 15:03:40 +00:00
|
|
|
IO.println s!"fail {tc.name} (expected type {BidirTT.prettyTm expectedTy}, got {BidirTT.prettyTm actualTy})"
|
2026-04-19 13:55:05 +00:00
|
|
|
pure false
|
|
|
|
|
else
|
2026-04-19 15:03:40 +00:00
|
|
|
IO.println s!"fail {tc.name} (expected nf {BidirTT.prettyTm expectedNf}, got {BidirTT.prettyTm actualNf})"
|
2026-04-19 13:55:05 +00:00
|
|
|
pure false
|
|
|
|
|
| Except.error err, _ =>
|
2026-04-19 15:03:40 +00:00
|
|
|
IO.println s!"fail {tc.name} (could not quote type: {err})"
|
2026-04-19 13:55:05 +00:00
|
|
|
pure false
|
|
|
|
|
| _, Except.error err =>
|
2026-04-19 15:03:40 +00:00
|
|
|
IO.println s!"fail {tc.name} (could not normalize term: {err})"
|
2026-04-19 13:55:05 +00:00
|
|
|
pure false
|
2026-04-19 04:17:45 +00:00
|
|
|
| .okTy expectedTy, .error err =>
|
2026-04-19 15:03:40 +00:00
|
|
|
IO.println s!"fail {tc.name} (expected type {BidirTT.prettyTm expectedTy}, got error {err})"
|
2026-04-19 04:17:45 +00:00
|
|
|
pure false
|
2026-04-19 13:55:05 +00:00
|
|
|
| .okTyNorm expectedTy _, .error err =>
|
2026-04-19 15:03:40 +00:00
|
|
|
IO.println s!"fail {tc.name} (expected type {BidirTT.prettyTm expectedTy}, got error {err})"
|
2026-04-19 13:55:05 +00:00
|
|
|
pure false
|
2026-04-19 04:17:45 +00:00
|
|
|
| .errContains needle, .error err =>
|
|
|
|
|
if containsText err needle then
|
2026-04-19 15:03:40 +00:00
|
|
|
IO.println s!"pass {tc.name}"
|
2026-04-19 04:17:45 +00:00
|
|
|
pure true
|
|
|
|
|
else
|
2026-04-19 15:03:40 +00:00
|
|
|
IO.println s!"fail {tc.name} (expected error containing {needle}, got {err})"
|
2026-04-19 04:17:45 +00:00
|
|
|
pure false
|
|
|
|
|
| .errContains needle, .ok (_, ty) =>
|
|
|
|
|
match renderType ty with
|
|
|
|
|
| Except.ok actualTy =>
|
2026-04-19 15:03:40 +00:00
|
|
|
IO.println s!"fail {tc.name} (expected error containing {needle}, got type {BidirTT.prettyTm actualTy})"
|
2026-04-19 04:17:45 +00:00
|
|
|
pure false
|
|
|
|
|
| Except.error err =>
|
2026-04-19 15:03:40 +00:00
|
|
|
IO.println s!"fail {tc.name} (expected error containing {needle}, got quote failure {err})"
|
2026-04-19 04:17:45 +00:00
|
|
|
pure false
|
|
|
|
|
|
|
|
|
|
def runInternalSafetyChecks : IO Bool := do
|
|
|
|
|
let malformedEvalOk :=
|
|
|
|
|
match eval [] (.var 0) with
|
|
|
|
|
| Except.error err => containsText err "bad de Bruijn index 0"
|
|
|
|
|
| Except.ok _ => false
|
|
|
|
|
let malformedQuoteOk :=
|
2026-04-19 15:03:40 +00:00
|
|
|
match quote 0 (.neu (.var 0)) with
|
2026-04-19 04:17:45 +00:00
|
|
|
| Except.error err => containsText err "bad level 0"
|
|
|
|
|
| Except.ok _ => false
|
|
|
|
|
if malformedEvalOk && malformedQuoteOk then
|
2026-04-19 15:03:40 +00:00
|
|
|
IO.println "pass malformed core terms are rejected safely"
|
2026-04-19 04:17:45 +00:00
|
|
|
pure true
|
|
|
|
|
else
|
2026-04-19 15:03:40 +00:00
|
|
|
IO.println "fail malformed core terms are rejected safely"
|
|
|
|
|
pure false
|
|
|
|
|
|
|
|
|
|
def runNeutralRepresentationChecks : IO Bool := do
|
|
|
|
|
let stuckAppOk :=
|
|
|
|
|
match vApp (.neu (.var 0)) .zero with
|
|
|
|
|
| Except.ok (.neu (.app (.var 0) .zero)) => true
|
|
|
|
|
| _ => false
|
|
|
|
|
let stuckNatElimOk :=
|
|
|
|
|
match vNatElim .nat .zero (.neu (.var 1)) (.neu (.var 0)) with
|
|
|
|
|
| Except.ok (.neu (.natElim .nat .zero (.neu (.var 1)) (.var 0))) => true
|
|
|
|
|
| _ => false
|
|
|
|
|
if stuckAppOk && stuckNatElimOk then
|
|
|
|
|
IO.println "pass stuck eliminators stay in the neutral fragment"
|
|
|
|
|
pure true
|
|
|
|
|
else
|
|
|
|
|
IO.println "fail stuck eliminators stay in the neutral fragment"
|
2026-04-19 04:17:45 +00:00
|
|
|
pure false
|
|
|
|
|
|
2026-04-19 14:22:34 +00:00
|
|
|
def runPrettyPrinterChecks : IO Bool := do
|
|
|
|
|
match checkTop Examples.idAnn with
|
|
|
|
|
| .ok (tm, ty) =>
|
|
|
|
|
match renderType ty with
|
|
|
|
|
| Except.ok qty =>
|
|
|
|
|
let termText := BidirTT.prettyTm tm
|
|
|
|
|
let typeText := BidirTT.prettyTm qty
|
|
|
|
|
let ok :=
|
|
|
|
|
containsText termText "fun x0 =>" &&
|
|
|
|
|
containsText typeText "Pi (x0 : U0)" &&
|
|
|
|
|
!containsText typeText "#"
|
|
|
|
|
if ok then
|
2026-04-19 15:03:40 +00:00
|
|
|
IO.println "pass pretty printer rehydrates binder names"
|
2026-04-19 14:22:34 +00:00
|
|
|
pure true
|
|
|
|
|
else
|
2026-04-19 15:03:40 +00:00
|
|
|
IO.println s!"fail pretty printer rehydrates binder names (term: {termText}, type: {typeText})"
|
2026-04-19 14:22:34 +00:00
|
|
|
pure false
|
|
|
|
|
| Except.error err =>
|
2026-04-19 15:03:40 +00:00
|
|
|
IO.println s!"fail pretty printer rehydrates binder names (could not quote type: {err})"
|
2026-04-19 14:22:34 +00:00
|
|
|
pure false
|
|
|
|
|
| .error err =>
|
2026-04-19 15:03:40 +00:00
|
|
|
IO.println s!"fail pretty printer rehydrates binder names (could not elaborate fixture: {err})"
|
2026-04-19 14:22:34 +00:00
|
|
|
pure false
|
|
|
|
|
|
2026-04-19 04:17:45 +00:00
|
|
|
def main : IO UInt32 := do
|
|
|
|
|
let results ← cases.mapM runCase
|
|
|
|
|
let safetyOk ← runInternalSafetyChecks
|
2026-04-19 15:03:40 +00:00
|
|
|
let neutralOk ← runNeutralRepresentationChecks
|
2026-04-19 14:22:34 +00:00
|
|
|
let prettyOk ← runPrettyPrinterChecks
|
2026-04-19 15:03:40 +00:00
|
|
|
let allResults := results ++ [safetyOk, neutralOk, prettyOk]
|
2026-04-19 04:17:45 +00:00
|
|
|
let failed := allResults.countP (· == false)
|
|
|
|
|
if failed == 0 then
|
|
|
|
|
IO.println s!"\n{allResults.length} passed, 0 failed"
|
|
|
|
|
pure 0
|
|
|
|
|
else
|
|
|
|
|
IO.println s!"\n{allResults.length - failed} passed, {failed} failed"
|
|
|
|
|
pure 1
|