#![allow(clippy::unwrap_used)]
use proptest::prelude::*;
use type_lang::{TyCon, Type, TypeError, Unifier};
#[derive(Clone, Debug)]
enum Skel {
Var(usize),
Con(u32),
App(u32, Vec<Skel>),
}
fn skel() -> impl Strategy<Value = Skel> {
let leaf = prop_oneof![
(0usize..6).prop_map(Skel::Var),
(0u32..4).prop_map(Skel::Con),
];
leaf.prop_recursive(4, 24, 3, |inner| {
(0u32..4, prop::collection::vec(inner, 0..3)).prop_map(|(tag, args)| Skel::App(tag, args))
})
}
fn realize(skel: &Skel, vars: &[type_lang::TyVar]) -> Type {
match skel {
Skel::Var(i) => {
if vars.is_empty() {
Type::con(TyCon::new(0))
} else {
Type::var(vars[i % vars.len()])
}
}
Skel::Con(tag) => Type::con(TyCon::new(*tag)),
Skel::App(tag, args) => Type::app(
TyCon::new(*tag),
args.iter().map(|s| realize(s, vars)).collect::<Vec<_>>(),
),
}
}
fn unifier_with_vars(n: usize) -> (Unifier, Vec<type_lang::TyVar>) {
let mut u = Unifier::new();
let vars = (0..n).map(|_| u.fresh()).collect();
(u, vars)
}
proptest! {
#[test]
fn unification_makes_types_equal(a in skel(), b in skel(), n in 1usize..6) {
let (mut u, vars) = unifier_with_vars(n);
let a = realize(&a, &vars);
let b = realize(&b, &vars);
if u.unify(&a, &b).is_ok() {
prop_assert_eq!(u.resolve(&a), u.resolve(&b));
}
}
#[test]
fn unification_is_symmetric(a in skel(), b in skel(), n in 1usize..6) {
let (mut forward, vf) = unifier_with_vars(n);
let (mut backward, vb) = unifier_with_vars(n);
let af = realize(&a, &vf);
let bf = realize(&b, &vf);
let ab = realize(&a, &vb);
let bb = realize(&b, &vb);
prop_assert_eq!(forward.unify(&af, &bf).is_ok(), backward.unify(&bb, &ab).is_ok());
}
#[test]
fn resolution_is_idempotent(a in skel(), b in skel(), n in 1usize..6) {
let (mut u, vars) = unifier_with_vars(n);
let a = realize(&a, &vars);
let b = realize(&b, &vars);
let _ = u.unify(&a, &b);
let once = u.resolve(&a);
let twice = u.resolve(&once);
prop_assert_eq!(once, twice);
}
#[test]
fn a_type_unifies_with_itself(a in skel(), n in 1usize..6) {
let (mut u, vars) = unifier_with_vars(n);
let a = realize(&a, &vars);
prop_assert!(u.unify(&a, &a).is_ok());
}
#[test]
fn a_variable_never_unifies_inside_its_own_term(tag in 0u32..4, n in 1usize..4) {
let (mut u, vars) = unifier_with_vars(n);
let v = vars[0];
let term = Type::app(TyCon::new(tag), [Type::var(v)]);
let err = u.unify(&Type::var(v), &term).unwrap_err();
let is_occurs = matches!(err, TypeError::Occurs { .. });
prop_assert!(is_occurs);
}
}