use alloc::vec::Vec;
use crate::error::TypeError;
use crate::ty::{TyVar, Type};
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default)]
pub struct Unifier {
bindings: Vec<Option<Type>>,
}
impl Unifier {
#[inline]
#[must_use]
pub const fn new() -> Self {
Self {
bindings: Vec::new(),
}
}
#[inline]
#[must_use]
pub fn with_capacity(vars: usize) -> Self {
Self {
bindings: Vec::with_capacity(vars),
}
}
#[inline]
#[must_use]
pub fn fresh(&mut self) -> TyVar {
debug_assert!(
self.bindings.len() < u32::MAX as usize,
"Unifier variable count exceeded the u32 id space",
);
let index = self.bindings.len() as u32;
self.bindings.push(None);
TyVar::from_index(index)
}
#[inline]
#[must_use]
pub fn var_count(&self) -> usize {
self.bindings.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.bindings.is_empty()
}
pub fn unify(&mut self, a: &Type, b: &Type) -> Result<(), TypeError> {
let mut work = Vec::new();
work.push((a.clone(), b.clone()));
while let Some((lhs, rhs)) = work.pop() {
let lhs = self.shallow(lhs);
let rhs = self.shallow(rhs);
match (lhs, rhs) {
(Type::Var(x), Type::Var(y)) if x == y => {}
(Type::Var(x), other) | (other, Type::Var(x)) => self.bind(x, other)?,
(Type::App(head1, args1), Type::App(head2, args2)) => {
if head1 != head2 || args1.len() != args2.len() {
return Err(TypeError::Mismatch {
expected: self.resolve(&Type::App(head1, args1)),
found: self.resolve(&Type::App(head2, args2)),
});
}
work.extend(args1.into_iter().zip(args2));
}
}
}
Ok(())
}
#[must_use]
pub fn resolve(&self, ty: &Type) -> Type {
match ty {
Type::Var(v) => match self.binding(*v) {
Some(bound) => self.resolve(bound),
None => Type::Var(*v),
},
Type::App(head, args) => {
Type::App(*head, args.iter().map(|arg| self.resolve(arg)).collect())
}
}
}
#[inline]
fn binding(&self, var: TyVar) -> Option<&Type> {
self.bindings.get(var.to_u32() as usize)?.as_ref()
}
fn shallow(&self, mut ty: Type) -> Type {
while let Type::Var(v) = ty {
match self.binding(v) {
Some(bound) => ty = bound.clone(),
None => return Type::Var(v),
}
}
ty
}
fn bind(&mut self, var: TyVar, ty: Type) -> Result<(), TypeError> {
if self.occurs(var, &ty) {
return Err(TypeError::Occurs {
var,
ty: self.resolve(&ty),
});
}
let index = var.to_u32() as usize;
if index >= self.bindings.len() {
self.bindings.resize(index + 1, None);
}
self.bindings[index] = Some(ty);
Ok(())
}
fn occurs(&self, var: TyVar, ty: &Type) -> bool {
let mut stack = Vec::new();
stack.push(ty);
while let Some(term) = stack.pop() {
match term {
Type::Var(other) => {
if *other == var {
return true;
}
if let Some(bound) = self.binding(*other) {
stack.push(bound);
}
}
Type::App(_, args) => stack.extend(args.iter()),
}
}
false
}
}
#[cfg(test)]
mod tests {
extern crate alloc;
use alloc::vec;
use super::*;
use crate::ty::TyCon;
const INT: TyCon = TyCon::new(0);
const BOOL: TyCon = TyCon::new(1);
const LIST: TyCon = TyCon::new(2);
const PAIR: TyCon = TyCon::new(3);
#[test]
fn test_fresh_variables_are_distinct_and_counted() {
let mut u = Unifier::new();
let a = u.fresh();
let b = u.fresh();
assert_ne!(a, b);
assert_eq!(u.var_count(), 2);
assert!(!u.is_empty());
}
#[test]
fn test_unify_variable_with_constructor_binds_it() {
let mut u = Unifier::new();
let v = u.fresh();
u.unify(&Type::var(v), &Type::con(INT)).unwrap();
assert_eq!(u.resolve(&Type::var(v)), Type::con(INT));
}
#[test]
fn test_unify_propagates_through_a_variable_chain() {
let mut u = Unifier::new();
let x = u.fresh();
let y = u.fresh();
u.unify(&Type::var(x), &Type::var(y)).unwrap();
u.unify(&Type::var(y), &Type::con(BOOL)).unwrap();
assert_eq!(u.resolve(&Type::var(x)), Type::con(BOOL));
}
#[test]
fn test_unify_matching_constructors_unifies_arguments() {
let mut u = Unifier::new();
let a = u.fresh();
let b = u.fresh();
let lhs = Type::app(PAIR, vec![Type::var(a), Type::con(INT)]);
let rhs = Type::app(PAIR, vec![Type::con(BOOL), Type::var(b)]);
u.unify(&lhs, &rhs).unwrap();
assert_eq!(u.resolve(&Type::var(a)), Type::con(BOOL));
assert_eq!(u.resolve(&Type::var(b)), Type::con(INT));
}
#[test]
fn test_unify_different_heads_is_mismatch() {
let mut u = Unifier::new();
let err = u.unify(&Type::con(INT), &Type::con(BOOL)).unwrap_err();
assert!(matches!(err, TypeError::Mismatch { .. }));
}
#[test]
fn test_unify_same_head_different_arity_is_mismatch() {
let mut u = Unifier::new();
let one = Type::app(PAIR, vec![Type::con(INT)]);
let two = Type::app(PAIR, vec![Type::con(INT), Type::con(INT)]);
let err = u.unify(&one, &two).unwrap_err();
assert!(matches!(err, TypeError::Mismatch { .. }));
}
#[test]
fn test_occurs_check_rejects_infinite_type() {
let mut u = Unifier::new();
let v = u.fresh();
let recursive = Type::app(LIST, vec![Type::var(v)]);
let err = u.unify(&Type::var(v), &recursive).unwrap_err();
match err {
TypeError::Occurs { var, .. } => assert_eq!(var, v),
other => panic!("expected occurs error, got {other:?}"),
}
}
#[test]
fn test_occurs_check_sees_through_a_binding() {
let mut u = Unifier::new();
let x = u.fresh();
let y = u.fresh();
u.unify(&Type::var(y), &Type::app(LIST, vec![Type::var(x)]))
.unwrap();
let err = u.unify(&Type::var(x), &Type::var(y)).unwrap_err();
assert!(matches!(err, TypeError::Occurs { .. }));
}
#[test]
fn test_unify_identical_terms_always_succeeds() {
let mut u = Unifier::new();
let v = u.fresh();
let term = Type::app(PAIR, vec![Type::var(v), Type::con(INT)]);
u.unify(&term, &term).unwrap();
}
#[test]
fn test_resolve_is_idempotent() {
let mut u = Unifier::new();
let x = u.fresh();
u.unify(&Type::var(x), &Type::app(LIST, vec![Type::con(INT)]))
.unwrap();
let once = u.resolve(&Type::var(x));
let twice = u.resolve(&once);
assert_eq!(once, twice);
}
#[test]
fn test_default_matches_new() {
let a = Unifier::default();
let b = Unifier::new();
assert_eq!(a.var_count(), b.var_count());
}
#[test]
fn test_two_unbound_variables_share_a_representative() {
let mut u = Unifier::new();
let x = u.fresh();
let y = u.fresh();
u.unify(&Type::var(x), &Type::var(y)).unwrap();
assert_eq!(u.resolve(&Type::var(x)), u.resolve(&Type::var(y)));
}
#[test]
fn test_variable_from_another_unifier_binds_soundly() {
let mut origin = Unifier::new();
let v = origin.fresh();
let mut other = Unifier::new(); other.unify(&Type::var(v), &Type::con(INT)).unwrap();
assert_eq!(other.resolve(&Type::var(v)), Type::con(INT));
assert_eq!(other.var_count(), 1);
}
#[test]
fn test_resolve_handles_a_deep_chain() {
let mut u = Unifier::new();
let depth = 1_000usize;
let vars: Vec<_> = (0..depth).map(|_| u.fresh()).collect();
for window in vars.windows(2) {
u.unify(
&Type::var(window[0]),
&Type::app(LIST, vec![Type::var(window[1])]),
)
.unwrap();
}
u.unify(&Type::var(vars[depth - 1]), &Type::con(INT))
.unwrap();
let resolved = u.resolve(&Type::var(vars[0]));
let mut cur = &resolved;
let mut wrappers = 0usize;
while cur.head() == Some(LIST) {
wrappers += 1;
cur = &cur.args()[0];
}
assert_eq!(wrappers, depth - 1);
assert_eq!(*cur, Type::con(INT));
}
}