use logicaffeine_kernel::interface::Repl;
#[test]
fn implicit_argument_is_inferred_at_the_surface() {
let mut repl = Repl::new();
repl.execute("Definition id : {A : Type} -> A -> A := fun A : Type => fun a : A => a.")
.expect("define id with an implicit type argument");
assert_eq!(
repl.execute("Eval (id 0).").expect("eval id 0").trim(),
"0",
"id 0 elaborates to `id Int 0` and computes to 0"
);
let checked = repl.execute("Check (id 0).").expect("check id 0");
assert!(checked.contains("Int"), "the inferred implicit `Int` is visible: {checked}");
}
#[test]
fn the_same_implicit_function_works_at_different_types() {
let mut repl = Repl::new();
repl.execute("Definition id : {A : Type} -> A -> A := fun A : Type => fun a : A => a.")
.expect("define id");
assert_eq!(repl.execute("Eval (id 0).").unwrap().trim(), "0", "id 0 ⇝ 0 (A := Int)");
assert_eq!(
repl.execute("Eval (id true).").unwrap().trim(),
"true",
"id true ⇝ true (A := Bool)"
);
}
#[test]
fn a_definition_body_may_apply_an_implicit_function() {
let mut repl = Repl::new();
repl.execute("Definition id : {A : Type} -> A -> A := fun A : Type => fun a : A => a.")
.expect("define id");
repl.execute("Definition five : Int := id 5.").expect("define five via id");
assert_eq!(repl.execute("Eval five.").unwrap().trim(), "5", "five = id 5 ⇝ 5");
}
#[test]
fn ordinary_explicit_definitions_are_unaffected() {
let mut repl = Repl::new();
repl.execute("Definition twice : Int -> Int := fun n : Int => n.").expect("define twice");
assert_eq!(repl.execute("Eval (twice 7).").unwrap().trim(), "7");
}
#[test]
fn a_two_argument_implicit_function_infers_from_the_first_use() {
let mut repl = Repl::new();
repl.execute(
"Definition const : {A : Type} -> {B : Type} -> A -> B -> A := \
fun A : Type => fun B : Type => fun a : A => fun b : B => a.",
)
.expect("define const");
assert_eq!(
repl.execute("Eval (const 0 true).").unwrap().trim(),
"0",
"const 0 true ⇝ 0 (A := Int, B := Bool, both inferred)"
);
}
#[test]
fn an_implicit_is_inferred_from_the_expected_type() {
let mut repl = Repl::new();
repl.execute("Definition nil : {A : Type} -> TList A := fun A : Type => TNil A.")
.expect("define nil");
repl.execute("Definition empty : TList Int := nil.")
.expect("nil's A is inferred from the declared TList Int");
let result = repl.execute("Eval empty.").unwrap();
assert!(result.contains("TNil"), "empty = nil Int ⇝ TNil Int: {result}");
}
#[test]
fn the_expected_type_drives_the_same_definition_to_different_implicits() {
let mut repl = Repl::new();
repl.execute("Definition nil : {A : Type} -> TList A := fun A : Type => TNil A.")
.expect("define nil");
repl.execute("Definition ints : TList Int := nil.").expect("A := Int from context");
repl.execute("Definition bools : TList Bool := nil.").expect("A := Bool from context");
assert!(repl.execute("Eval ints.").is_ok());
assert!(repl.execute("Eval bools.").is_ok());
}
#[test]
fn an_expected_type_that_cannot_match_is_a_type_error() {
let mut repl = Repl::new();
repl.execute("Definition nil : {A : Type} -> TList A := fun A : Type => TNil A.")
.expect("define nil");
assert!(
repl.execute("Definition bad : Int := nil.").is_err(),
"nil : Int is a type error (TList A cannot unify with Int)"
);
}
#[test]
fn a_bare_implicit_global_without_an_expected_type_stays_the_function() {
let mut repl = Repl::new();
repl.execute("Definition nil : {A : Type} -> TList A := fun A : Type => TNil A.")
.expect("define nil");
assert!(repl.execute("Check nil.").is_ok(), "bare `nil` is the polymorphic function");
}
#[test]
fn a_free_type_variable_is_auto_bound_as_an_implicit() {
let mut repl = Repl::new();
repl.execute("Definition id : A -> A := fun a : A => a.")
.expect("A is auto-bound as an implicit");
assert_eq!(repl.execute("Eval (id 0).").unwrap().trim(), "0", "id 0 ⇝ 0 (A := Int)");
assert_eq!(repl.execute("Eval (id true).").unwrap().trim(), "true", "id true ⇝ true (A := Bool)");
}
#[test]
fn multiple_free_variables_are_auto_bound_in_order() {
let mut repl = Repl::new();
repl.execute("Definition const : A -> B -> A := fun a : A => fun b : B => a.")
.expect("A and B auto-bound");
assert_eq!(
repl.execute("Eval (const 0 true).").unwrap().trim(),
"0",
"const 0 true ⇝ 0 (A := Int, B := Bool)"
);
}
#[test]
fn an_auto_bound_implicit_is_inferred_from_the_expected_type() {
let mut repl = Repl::new();
repl.execute("Definition nil : TList A := TNil A.").expect("A auto-bound");
repl.execute("Definition empty : TList Int := nil.").expect("A inferred from TList Int");
assert!(repl.execute("Eval empty.").unwrap().contains("TNil"), "empty ⇝ TNil Int");
}
#[test]
fn explicitly_bound_parameters_are_not_auto_bound() {
let mut repl = Repl::new();
repl.execute("Definition idT : forall A : Type, A -> A := fun A : Type => fun a : A => a.")
.expect("explicitly-bound A is untouched");
assert_eq!(repl.execute("Eval (idT Int 0).").unwrap().trim(), "0");
}
#[test]
fn declaring_an_inductive_auto_derives_a_computing_recursor() {
let mut repl = Repl::new();
repl.execute("Inductive Color := red : Color | green : Color | blue : Color.")
.expect("declare Color");
let checked = repl.execute("Check Color_rec.").expect("Color_rec was auto-derived");
assert!(checked.contains("Color"), "Color_rec mentions Color: {checked}");
let r = repl
.execute(
"Eval (Color_rec (fun c : Color => Nat) Zero (Succ Zero) (Succ (Succ Zero)) green).",
)
.expect("the recursor computes");
assert!(r.contains("Succ") && r.contains("Zero"), "green case ⇝ 1: {r}");
}
#[test]
fn the_auto_derived_nat_like_recursor_does_real_recursion() {
let mut repl = Repl::new();
repl.execute("Inductive Cnt := z : Cnt | s : Cnt -> Cnt.").expect("declare Cnt");
let r = repl
.execute("Eval (Cnt_rec (fun c : Cnt => Nat) Zero (fun c : Cnt => fun ih : Nat => Succ ih) (s (s z))).")
.expect("Cnt_rec recurses");
assert!(r.matches("Succ").count() == 2, "depth (s (s z)) = 2: {r}");
}
#[test]
fn match_without_a_return_clause_infers_a_constant_motive() {
let mut repl = Repl::new();
repl.execute("Inductive Color := red : Color | green : Color | blue : Color.")
.expect("declare Color");
repl.execute(
"Definition rank : Color -> Nat := fun c : Color => \
match c with | red => Zero | green => Succ Zero | blue => Succ (Succ Zero) end.",
)
.expect("match motive inferred from the declared Color -> Nat");
assert_eq!(repl.execute("Eval (rank green).").unwrap().trim(), "(Succ Zero)", "rank green = 1");
assert_eq!(
repl.execute("Eval (rank blue).").unwrap().trim(),
"(Succ (Succ Zero))",
"rank blue = 2"
);
}
#[test]
fn bare_match_infers_its_motive_from_a_nullary_first_branch() {
let mut repl = Repl::new();
repl.execute("Inductive Color := red : Color | green : Color | blue : Color.")
.expect("declare Color");
let r = repl
.execute("Eval (match green with | red => Zero | green => Succ Zero | blue => Zero end).")
.expect("motive inferred from the first nullary branch");
assert_eq!(r.trim(), "(Succ Zero)", "the green branch ⇝ 1");
}
#[test]
fn match_sugar_works_on_a_recursive_inductive_with_binders() {
let mut repl = Repl::new();
repl.execute("Inductive Cnt := z : Cnt | s : Cnt -> Cnt.").expect("declare Cnt");
repl.execute(
"Definition pred : Cnt -> Cnt := fun n : Cnt => match n with | z => z | s k => k end.",
)
.expect("match with a binder, motive inferred");
assert_eq!(repl.execute("Eval (pred (s (s z))).").unwrap().trim(), "(s z)", "pred (s (s z)) = s z");
}
#[test]
fn a_dependent_match_motive_is_inferred_from_the_expected_type() {
let mut repl = Repl::new();
repl.execute("Inductive Cnt := z : Cnt | s : Cnt -> Cnt.").expect("declare Cnt");
repl.execute(
"Definition P : Cnt -> Type := fun n : Cnt => match n with | z => Cnt | s k => Bool end.",
)
.expect("declare the dependent type family P");
repl.execute(
"Definition elim_p : forall n : Cnt, P n := \
fun n : Cnt => match n with | z => z | s k => true end.",
)
.expect("dependent motive λn. P n inferred — branches z:Cnt, s k:Bool");
assert_eq!(repl.execute("Eval (elim_p z).").unwrap().trim(), "z", "P z = Cnt branch");
assert_eq!(repl.execute("Eval (elim_p (s z)).").unwrap().trim(), "true", "P (s z) = Bool branch");
}
#[test]
fn let_expression_binds_a_local() {
let mut repl = Repl::new();
assert_eq!(repl.execute("Eval (let x := 2 in add x x).").unwrap().trim(), "4");
assert_eq!(repl.execute("Eval (let y : Int := 5 in mul y y).").unwrap().trim(), "25");
assert_eq!(repl.execute("Eval (let a := 3 in let b := 4 in add a b).").unwrap().trim(), "7");
}
#[test]
fn let_in_a_definition_body() {
let mut repl = Repl::new();
repl.execute("Definition nine : Int := let three := 3 in mul three three.")
.expect("let inside a definition body");
assert_eq!(repl.execute("Eval nine.").unwrap().trim(), "9");
}
#[test]
fn recursive_definition_via_explicit_fix() {
let mut repl = Repl::new();
repl.execute(
"Definition add : Nat -> Nat -> Nat := \
fix add => fun n : Nat => fun m : Nat => \
match n with | Zero => m | Succ k => Succ (add k m) end.",
)
.expect("explicit-fix recursive add");
assert_eq!(
repl.execute("Eval (add (Succ (Succ Zero)) (Succ Zero)).").unwrap().trim(),
"(Succ (Succ (Succ Zero)))",
"2 + 1 = 3",
);
}
#[test]
fn recursive_definition_auto_wraps_in_fix() {
let mut repl = Repl::new();
repl.execute(
"Definition add : Nat -> Nat -> Nat := \
fun n : Nat => fun m : Nat => \
match n with | Zero => m | Succ k => Succ (add k m) end.",
)
.expect("recursive add with no explicit fix");
assert_eq!(
repl.execute("Eval (add (Succ (Succ Zero)) (Succ Zero)).").unwrap().trim(),
"(Succ (Succ (Succ Zero)))",
"2 + 1 = 3",
);
repl.execute(
"Definition mulAdd : Nat -> Nat -> Nat := \
fun n : Nat => fun m : Nat => \
match n with | Zero => Zero | Succ k => add m (mulAdd k m) end.",
)
.expect("recursive multiply");
assert_eq!(
repl.execute(
"Eval (mulAdd (Succ (Succ Zero)) (Succ (Succ (Succ Zero)))).",
)
.unwrap()
.trim(),
"(Succ (Succ (Succ (Succ (Succ (Succ Zero))))))",
"2 * 3 = 6",
);
}
#[test]
fn recursive_definition_rejects_nonterminating_recursion() {
let mut repl = Repl::new();
let result = repl.execute("Definition loop : Nat -> Nat := fun n : Nat => loop n.");
assert!(result.is_err(), "non-decreasing recursion must be rejected, got {result:?}");
}
#[test]
fn recursive_definition_is_two_kernel_verified() {
use logicaffeine_kernel::{double_check, DoubleCheck};
let mut repl = Repl::new();
repl.execute(
"Definition add : Nat -> Nat -> Nat := \
fun n : Nat => fun m : Nat => \
match n with | Zero => m | Succ k => Succ (add k m) end.",
)
.expect("recursive add");
let body = repl.context().get_definition_body("add").expect("add is defined").clone();
match double_check(repl.context(), &body) {
DoubleCheck::Agreed => {}
other => panic!("re-checker must agree on the recursive definition, got {other:?}"),
}
}