use std::collections::HashMap;
use logicaffeine_kernel::prelude::StandardLibrary;
use logicaffeine_kernel::{
double_check, infer_type, instantiate_universes, normalize, Context, DoubleCheck, Term,
Universe,
};
fn pi(p: &str, ty: Term, body: Term) -> Term {
Term::Pi { param: p.to_string(), param_type: Box::new(ty), body_type: Box::new(body) }
}
fn arrow(a: Term, b: Term) -> Term {
pi("_", a, b)
}
fn const_at(name: &str, levels: Vec<Universe>) -> Term {
Term::Const { name: name.to_string(), levels }
}
fn g(n: &str) -> Term {
Term::Global(n.to_string())
}
fn var(n: &str) -> Term {
Term::Var(n.to_string())
}
fn sort(u: Universe) -> Term {
Term::Sort(u)
}
fn lam(p: &str, ty: Term, body: Term) -> Term {
Term::Lambda { param: p.to_string(), param_type: Box::new(ty), body: Box::new(body) }
}
fn app(f: Term, x: Term) -> Term {
Term::App(Box::new(f), Box::new(x))
}
fn subst(pairs: &[(&str, Universe)]) -> HashMap<String, Universe> {
pairs.iter().map(|(k, v)| (k.to_string(), v.clone())).collect()
}
fn std_ctx() -> Context {
let mut ctx = Context::new();
StandardLibrary::register(&mut ctx);
ctx
}
#[test]
fn universe_polymorphic_identity_instantiates_at_multiple_levels() {
let ctx = std_ctx();
let id_poly = lam("A", sort(Universe::Var("u".to_string())), lam("a", var("A"), var("a")));
let poly_ty = infer_type(&ctx, &id_poly).expect("polymorphic identity type-checks generically");
assert!(matches!(poly_ty, Term::Pi { .. }), "Π(A:Sort u). A → A, got {poly_ty}");
assert_eq!(
double_check(&ctx, &id_poly),
DoubleCheck::Agreed,
"the polymorphic identity must be two-kernel-verified, got {:?}",
double_check(&ctx, &id_poly)
);
let id0 = instantiate_universes(&id_poly, &subst(&[("u", Universe::Type(0))]));
assert!(infer_type(&ctx, &id0).is_ok(), "id.{{0}} type-checks");
assert!(
infer_type(&ctx, &app(id0.clone(), g("Nat"))).is_ok(),
"id.{{0}} Nat : Nat → Nat (Nat is a Type-0 value)"
);
let id1 = instantiate_universes(&id_poly, &subst(&[("u", Universe::Type(1))]));
assert!(infer_type(&ctx, &id1).is_ok(), "id.{{1}} type-checks");
assert!(
infer_type(&ctx, &app(id1.clone(), sort(Universe::Type(0)))).is_ok(),
"id.{{1}} (Type 0) : Type 0 → Type 0 — works on a type-of-types"
);
assert!(
infer_type(&ctx, &app(id0, sort(Universe::Type(0)))).is_err(),
"the Type-0 identity must reject Type 0 — the limitation universe polymorphism removes"
);
}
#[test]
fn instantiation_equals_the_hand_written_monomorphic_version() {
let id_poly = lam("A", sort(Universe::Var("u".to_string())), lam("a", var("A"), var("a")));
let id0 = instantiate_universes(&id_poly, &subst(&[("u", Universe::Type(0))]));
let hand_written = lam("A", sort(Universe::Type(0)), lam("a", var("A"), var("a")));
assert_eq!(id0, hand_written, "id.{{0}} must equal the hand-written Type-0 identity");
}
#[test]
fn a_polymorphic_constant_function_also_checks_and_specializes() {
let ctx = std_ctx();
let u = Universe::Var("u".to_string());
let k_poly = lam(
"A",
sort(u.clone()),
lam("B", sort(u.clone()), lam("a", var("A"), lam("b", var("B"), var("a")))),
);
assert!(infer_type(&ctx, &k_poly).is_ok(), "polymorphic K type-checks");
assert_eq!(double_check(&ctx, &k_poly), DoubleCheck::Agreed);
let k0 = instantiate_universes(&k_poly, &subst(&[("u", Universe::Type(0))]));
assert!(
infer_type(&ctx, &app(app(k0, g("Nat")), g("Bool"))).is_ok(),
"K.{{0}} Nat Bool : Nat → Bool → Nat"
);
}
#[test]
fn universe_polymorphic_global_is_stored_and_referenced_at_levels() {
let mut ctx = std_ctx();
let u = Universe::Var("u".to_string());
let id_ty = pi("A", sort(u.clone()), arrow(var("A"), var("A")));
let id_body = lam("A", sort(u.clone()), lam("a", var("A"), var("a")));
ctx.add_universe_poly("id", vec!["u".to_string()], id_ty, id_body);
let id0 = const_at("id", vec![Universe::Type(0)]);
let id1 = const_at("id", vec![Universe::Type(1)]);
assert!(infer_type(&ctx, &id0).is_ok(), "id.{{0}} type-checks");
assert_eq!(
double_check(&ctx, &id0),
DoubleCheck::Agreed,
"id.{{0}} must be two-kernel-verified, got {:?}",
double_check(&ctx, &id0)
);
assert!(infer_type(&ctx, &app(id0.clone(), g("Nat"))).is_ok(), "id.{{0}} Nat");
assert!(
infer_type(&ctx, &app(id1.clone(), sort(Universe::Type(0)))).is_ok(),
"id.{{1}} (Type 0)"
);
assert!(
infer_type(&ctx, &app(id0.clone(), sort(Universe::Type(0)))).is_err(),
"id.{{0}} must reject Type 0"
);
let applied = app(app(id0, g("Nat")), g("Zero"));
assert_eq!(normalize(&ctx, &applied), g("Zero"), "id.{{0}} Nat Zero = Zero");
let bad = const_at("id", vec![Universe::Type(0), Universe::Type(1)]);
assert!(infer_type(&ctx, &bad).is_err(), "arity mismatch on universe arguments is rejected");
}