type_lang/unify.rs
1//! The unifier: inference variables, their substitution, and unification.
2
3use alloc::vec::Vec;
4
5use crate::error::TypeError;
6use crate::ty::{TyVar, Type};
7
8/// Holds the inference variables of a type problem and the substitution that
9/// unification builds over them.
10///
11/// A `Unifier` is the working state of type inference. It mints fresh
12/// [`TyVar`]s with [`fresh`](Self::fresh), makes two types equal with
13/// [`unify`](Self::unify) — recording the variable bindings that equality
14/// requires — and reads a type back under those bindings with
15/// [`resolve`](Self::resolve). A variable belongs to the unifier that minted it.
16///
17/// Unification is the standard first-order algorithm: two constructors match only
18/// if their heads and arity agree, in which case their arguments are unified
19/// pairwise; a variable unifies with any type by being bound to it, guarded by an
20/// occurs check so a variable can never be bound to a type that contains it. The
21/// substitution it produces is a most-general unifier — it constrains a variable
22/// only as far as making the two types equal demands.
23///
24/// # Examples
25///
26/// ```
27/// use type_lang::{TyCon, Type, Unifier};
28///
29/// const FUNCTION: TyCon = TyCon::new(0);
30/// const INT: TyCon = TyCon::new(1);
31///
32/// let mut unifier = Unifier::new();
33/// let arg = unifier.fresh();
34/// let ret = unifier.fresh();
35///
36/// // Unify the inferred signature (?arg) -> ?ret with (int) -> int.
37/// let inferred = Type::app(FUNCTION, [Type::var(arg), Type::var(ret)]);
38/// let known = Type::app(FUNCTION, [Type::con(INT), Type::con(INT)]);
39/// unifier.unify(&inferred, &known).expect("same constructor and arity");
40///
41/// // Both variables are now known to be int.
42/// assert_eq!(unifier.resolve(&Type::var(arg)), Type::con(INT));
43/// assert_eq!(unifier.resolve(&Type::var(ret)), Type::con(INT));
44/// ```
45#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
46#[derive(Clone, Debug, Default)]
47pub struct Unifier {
48 /// Indexed by [`TyVar`]: `None` is unbound, `Some(ty)` is the variable's
49 /// current binding (which may itself be another variable).
50 bindings: Vec<Option<Type>>,
51}
52
53impl Unifier {
54 /// Creates an empty unifier with no variables.
55 ///
56 /// # Examples
57 ///
58 /// ```
59 /// use type_lang::Unifier;
60 ///
61 /// let unifier = Unifier::new();
62 /// assert!(unifier.is_empty());
63 /// ```
64 #[inline]
65 #[must_use]
66 pub const fn new() -> Self {
67 Self {
68 bindings: Vec::new(),
69 }
70 }
71
72 /// Creates an empty unifier with room for `vars` variables preallocated.
73 ///
74 /// A hint only: it sizes the internal table so that minting up to `vars`
75 /// variables does not reallocate. Use it when the variable count is known up
76 /// front — for instance, one per binding in the scope being checked.
77 ///
78 /// # Examples
79 ///
80 /// ```
81 /// use type_lang::Unifier;
82 ///
83 /// let mut unifier = Unifier::with_capacity(8);
84 /// for _ in 0..8 {
85 /// let _ = unifier.fresh();
86 /// }
87 /// assert_eq!(unifier.var_count(), 8);
88 /// ```
89 #[inline]
90 #[must_use]
91 pub fn with_capacity(vars: usize) -> Self {
92 Self {
93 bindings: Vec::with_capacity(vars),
94 }
95 }
96
97 /// Mints a fresh, unbound inference variable.
98 ///
99 /// Variables are numbered in creation order from `0` and stay valid for the
100 /// life of the unifier. A fresh variable stands for "some type not yet known";
101 /// it gains a binding only when [`unify`](Self::unify) requires one.
102 ///
103 /// Variable ids are 32-bit, so a unifier addresses up to `u32::MAX` variables —
104 /// far beyond any realistic inference problem, since the binding table alone
105 /// would need tens of gigabytes long before the id space ran out. The bound is
106 /// asserted in debug builds.
107 ///
108 /// # Examples
109 ///
110 /// ```
111 /// use type_lang::Unifier;
112 ///
113 /// let mut unifier = Unifier::new();
114 /// let a = unifier.fresh();
115 /// let b = unifier.fresh();
116 /// assert_ne!(a, b);
117 /// assert_eq!(unifier.var_count(), 2);
118 /// ```
119 #[inline]
120 #[must_use]
121 pub fn fresh(&mut self) -> TyVar {
122 debug_assert!(
123 self.bindings.len() < u32::MAX as usize,
124 "Unifier variable count exceeded the u32 id space",
125 );
126 let index = self.bindings.len() as u32;
127 self.bindings.push(None);
128 TyVar::from_index(index)
129 }
130
131 /// Returns the number of variables this unifier has minted.
132 #[inline]
133 #[must_use]
134 pub fn var_count(&self) -> usize {
135 self.bindings.len()
136 }
137
138 /// Returns `true` if no variables have been minted.
139 #[inline]
140 #[must_use]
141 pub fn is_empty(&self) -> bool {
142 self.bindings.is_empty()
143 }
144
145 /// Unifies two types, binding variables as needed to make them equal.
146 ///
147 /// On success the unifier's substitution is extended so that `a` and `b`
148 /// [`resolve`](Self::resolve) to the same type. On failure the call returns a
149 /// [`TypeError`] describing why, and **any bindings made before the conflict
150 /// was reached are kept** — unification is not transactional. A caller that
151 /// needs to try a unification speculatively should [`clone`](Clone::clone) the
152 /// unifier first and keep the clone only if it succeeds.
153 ///
154 /// # Errors
155 ///
156 /// - [`TypeError::Mismatch`] if the two types are built from different
157 /// constructors or the same constructor at a different arity.
158 /// - [`TypeError::Occurs`] if making them equal would require binding a
159 /// variable to a type that contains it (an infinite type).
160 ///
161 /// # Examples
162 ///
163 /// ```
164 /// use type_lang::{TyCon, Type, TypeError, Unifier};
165 ///
166 /// const INT: TyCon = TyCon::new(0);
167 /// const BOOL: TyCon = TyCon::new(1);
168 ///
169 /// let mut unifier = Unifier::new();
170 /// let v = unifier.fresh();
171 ///
172 /// // A variable unifies with a concrete type by being bound to it.
173 /// unifier.unify(&Type::var(v), &Type::con(INT)).unwrap();
174 /// assert_eq!(unifier.resolve(&Type::var(v)), Type::con(INT));
175 ///
176 /// // Two different constructors do not unify.
177 /// let err = unifier.unify(&Type::con(INT), &Type::con(BOOL)).unwrap_err();
178 /// assert!(matches!(err, TypeError::Mismatch { .. }));
179 /// ```
180 pub fn unify(&mut self, a: &Type, b: &Type) -> Result<(), TypeError> {
181 let mut work = Vec::new();
182 work.push((a.clone(), b.clone()));
183
184 while let Some((lhs, rhs)) = work.pop() {
185 let lhs = self.shallow(lhs);
186 let rhs = self.shallow(rhs);
187 match (lhs, rhs) {
188 (Type::Var(x), Type::Var(y)) if x == y => {}
189 (Type::Var(x), other) | (other, Type::Var(x)) => self.bind(x, other)?,
190 (Type::App(head1, args1), Type::App(head2, args2)) => {
191 if head1 != head2 || args1.len() != args2.len() {
192 return Err(TypeError::Mismatch {
193 expected: self.resolve(&Type::App(head1, args1)),
194 found: self.resolve(&Type::App(head2, args2)),
195 });
196 }
197 work.extend(args1.into_iter().zip(args2));
198 }
199 }
200 }
201 Ok(())
202 }
203
204 /// Resolves a type fully under the current substitution.
205 ///
206 /// Every bound variable in `ty` is replaced by what it was bound to, all the
207 /// way down, leaving a type whose only variables are still unbound. A type with
208 /// no bound variables resolves to an equal copy of itself.
209 ///
210 /// The walk is proportional to the size of the resolved result, which — for a
211 /// substitution that maps a variable to a type mentioning it twice — can be
212 /// larger than the input. Resolve once you are done constraining a type, rather
213 /// than after every step.
214 ///
215 /// # Examples
216 ///
217 /// ```
218 /// use type_lang::{TyCon, Type, Unifier};
219 ///
220 /// const LIST: TyCon = TyCon::new(0);
221 /// const INT: TyCon = TyCon::new(1);
222 ///
223 /// let mut unifier = Unifier::new();
224 /// let element = unifier.fresh();
225 /// let collection = unifier.fresh();
226 ///
227 /// // ?collection = List<?element>, then ?element = int.
228 /// unifier
229 /// .unify(&Type::var(collection), &Type::app(LIST, [Type::var(element)]))
230 /// .unwrap();
231 /// unifier.unify(&Type::var(element), &Type::con(INT)).unwrap();
232 ///
233 /// // Resolving the outer variable substitutes all the way down.
234 /// assert_eq!(
235 /// unifier.resolve(&Type::var(collection)),
236 /// Type::app(LIST, [Type::con(INT)]),
237 /// );
238 ///
239 /// // An unbound variable resolves to itself.
240 /// let free = unifier.fresh();
241 /// assert_eq!(unifier.resolve(&Type::var(free)), Type::var(free));
242 /// ```
243 #[must_use]
244 pub fn resolve(&self, ty: &Type) -> Type {
245 match ty {
246 Type::Var(v) => match self.binding(*v) {
247 Some(bound) => self.resolve(bound),
248 None => Type::Var(*v),
249 },
250 Type::App(head, args) => {
251 Type::App(*head, args.iter().map(|arg| self.resolve(arg)).collect())
252 }
253 }
254 }
255
256 /// Returns the direct binding of `var`, or `None` if it is unbound (or not from
257 /// this unifier). The binding is shallow — it may itself be another variable.
258 #[inline]
259 fn binding(&self, var: TyVar) -> Option<&Type> {
260 self.bindings.get(var.to_u32() as usize)?.as_ref()
261 }
262
263 /// Follows variable bindings at the top level until the term is either an
264 /// unbound variable or a constructor. Does not descend into arguments.
265 fn shallow(&self, mut ty: Type) -> Type {
266 while let Type::Var(v) = ty {
267 match self.binding(v) {
268 Some(bound) => ty = bound.clone(),
269 None => return Type::Var(v),
270 }
271 }
272 ty
273 }
274
275 /// Binds an unbound variable to a type, after the occurs check.
276 ///
277 /// `var` is known to be unbound and `ty` is shallow-resolved (not a bound
278 /// variable), because both come straight out of [`shallow`](Self::shallow).
279 fn bind(&mut self, var: TyVar, ty: Type) -> Result<(), TypeError> {
280 if self.occurs(var, &ty) {
281 return Err(TypeError::Occurs {
282 var,
283 ty: self.resolve(&ty),
284 });
285 }
286 // A variable this unifier has not minted (one passed in from elsewhere)
287 // joins its id space here as a freshly bound variable. Growing the table
288 // keeps `unify`'s postcondition sound — the binding is always recorded —
289 // rather than dropping it and reporting a success that did not happen. In
290 // normal single-unifier use the index is always in range and no growth
291 // occurs.
292 let index = var.to_u32() as usize;
293 if index >= self.bindings.len() {
294 self.bindings.resize(index + 1, None);
295 }
296 self.bindings[index] = Some(ty);
297 Ok(())
298 }
299
300 /// Reports whether `var` occurs anywhere within `ty`, looking through bindings.
301 ///
302 /// Iterative, with an explicit work stack, so an arbitrarily deep type cannot
303 /// overflow the call stack.
304 fn occurs(&self, var: TyVar, ty: &Type) -> bool {
305 let mut stack = Vec::new();
306 stack.push(ty);
307 while let Some(term) = stack.pop() {
308 match term {
309 Type::Var(other) => {
310 if *other == var {
311 return true;
312 }
313 if let Some(bound) = self.binding(*other) {
314 stack.push(bound);
315 }
316 }
317 Type::App(_, args) => stack.extend(args.iter()),
318 }
319 }
320 false
321 }
322}
323
324#[cfg(test)]
325mod tests {
326 extern crate alloc;
327 use alloc::vec;
328
329 use super::*;
330 use crate::ty::TyCon;
331
332 const INT: TyCon = TyCon::new(0);
333 const BOOL: TyCon = TyCon::new(1);
334 const LIST: TyCon = TyCon::new(2);
335 const PAIR: TyCon = TyCon::new(3);
336
337 #[test]
338 fn test_fresh_variables_are_distinct_and_counted() {
339 let mut u = Unifier::new();
340 let a = u.fresh();
341 let b = u.fresh();
342 assert_ne!(a, b);
343 assert_eq!(u.var_count(), 2);
344 assert!(!u.is_empty());
345 }
346
347 #[test]
348 fn test_unify_variable_with_constructor_binds_it() {
349 let mut u = Unifier::new();
350 let v = u.fresh();
351 u.unify(&Type::var(v), &Type::con(INT)).unwrap();
352 assert_eq!(u.resolve(&Type::var(v)), Type::con(INT));
353 }
354
355 #[test]
356 fn test_unify_propagates_through_a_variable_chain() {
357 let mut u = Unifier::new();
358 let x = u.fresh();
359 let y = u.fresh();
360 u.unify(&Type::var(x), &Type::var(y)).unwrap();
361 u.unify(&Type::var(y), &Type::con(BOOL)).unwrap();
362 // Binding y is visible through x.
363 assert_eq!(u.resolve(&Type::var(x)), Type::con(BOOL));
364 }
365
366 #[test]
367 fn test_unify_matching_constructors_unifies_arguments() {
368 let mut u = Unifier::new();
369 let a = u.fresh();
370 let b = u.fresh();
371 let lhs = Type::app(PAIR, vec![Type::var(a), Type::con(INT)]);
372 let rhs = Type::app(PAIR, vec![Type::con(BOOL), Type::var(b)]);
373 u.unify(&lhs, &rhs).unwrap();
374 assert_eq!(u.resolve(&Type::var(a)), Type::con(BOOL));
375 assert_eq!(u.resolve(&Type::var(b)), Type::con(INT));
376 }
377
378 #[test]
379 fn test_unify_different_heads_is_mismatch() {
380 let mut u = Unifier::new();
381 let err = u.unify(&Type::con(INT), &Type::con(BOOL)).unwrap_err();
382 assert!(matches!(err, TypeError::Mismatch { .. }));
383 }
384
385 #[test]
386 fn test_unify_same_head_different_arity_is_mismatch() {
387 let mut u = Unifier::new();
388 let one = Type::app(PAIR, vec![Type::con(INT)]);
389 let two = Type::app(PAIR, vec![Type::con(INT), Type::con(INT)]);
390 let err = u.unify(&one, &two).unwrap_err();
391 assert!(matches!(err, TypeError::Mismatch { .. }));
392 }
393
394 #[test]
395 fn test_occurs_check_rejects_infinite_type() {
396 let mut u = Unifier::new();
397 let v = u.fresh();
398 let recursive = Type::app(LIST, vec![Type::var(v)]);
399 let err = u.unify(&Type::var(v), &recursive).unwrap_err();
400 match err {
401 TypeError::Occurs { var, .. } => assert_eq!(var, v),
402 other => panic!("expected occurs error, got {other:?}"),
403 }
404 }
405
406 #[test]
407 fn test_occurs_check_sees_through_a_binding() {
408 let mut u = Unifier::new();
409 let x = u.fresh();
410 let y = u.fresh();
411 // y = List<x>, then x = y would make x = List<x>.
412 u.unify(&Type::var(y), &Type::app(LIST, vec![Type::var(x)]))
413 .unwrap();
414 let err = u.unify(&Type::var(x), &Type::var(y)).unwrap_err();
415 assert!(matches!(err, TypeError::Occurs { .. }));
416 }
417
418 #[test]
419 fn test_unify_identical_terms_always_succeeds() {
420 let mut u = Unifier::new();
421 let v = u.fresh();
422 let term = Type::app(PAIR, vec![Type::var(v), Type::con(INT)]);
423 u.unify(&term, &term).unwrap();
424 }
425
426 #[test]
427 fn test_resolve_is_idempotent() {
428 let mut u = Unifier::new();
429 let x = u.fresh();
430 u.unify(&Type::var(x), &Type::app(LIST, vec![Type::con(INT)]))
431 .unwrap();
432 let once = u.resolve(&Type::var(x));
433 let twice = u.resolve(&once);
434 assert_eq!(once, twice);
435 }
436
437 #[test]
438 fn test_default_matches_new() {
439 let a = Unifier::default();
440 let b = Unifier::new();
441 assert_eq!(a.var_count(), b.var_count());
442 }
443
444 #[test]
445 fn test_two_unbound_variables_share_a_representative() {
446 let mut u = Unifier::new();
447 let x = u.fresh();
448 let y = u.fresh();
449 u.unify(&Type::var(x), &Type::var(y)).unwrap();
450 // Neither is bound to a constructor, but both now resolve to one variable.
451 assert_eq!(u.resolve(&Type::var(x)), u.resolve(&Type::var(y)));
452 }
453
454 #[test]
455 fn test_variable_from_another_unifier_binds_soundly() {
456 // Regression: a variable this unifier did not mint must still be recorded
457 // when bound — `unify` may never report success while leaving the two sides
458 // unequal. The variable joins this unifier's id space.
459 let mut origin = Unifier::new();
460 let v = origin.fresh(); // belongs to `origin`
461
462 let mut other = Unifier::new(); // has no variables of its own yet
463 other.unify(&Type::var(v), &Type::con(INT)).unwrap();
464
465 // The postcondition holds: both sides resolve to the same type.
466 assert_eq!(other.resolve(&Type::var(v)), Type::con(INT));
467 assert_eq!(other.var_count(), 1);
468 }
469
470 #[test]
471 fn test_resolve_handles_a_deep_chain() {
472 // v0 = List<v1>, v1 = List<v2>, ..., v_{n-1} = int. Resolving v0 nests the
473 // whole chain; check it terminates correctly without misbehaving.
474 let mut u = Unifier::new();
475 let depth = 1_000usize;
476 let vars: Vec<_> = (0..depth).map(|_| u.fresh()).collect();
477 for window in vars.windows(2) {
478 u.unify(
479 &Type::var(window[0]),
480 &Type::app(LIST, vec![Type::var(window[1])]),
481 )
482 .unwrap();
483 }
484 u.unify(&Type::var(vars[depth - 1]), &Type::con(INT))
485 .unwrap();
486
487 let resolved = u.resolve(&Type::var(vars[0]));
488
489 // Walk the resolved structure iteratively: depth-1 List wrappers, then int.
490 let mut cur = &resolved;
491 let mut wrappers = 0usize;
492 while cur.head() == Some(LIST) {
493 wrappers += 1;
494 cur = &cur.args()[0];
495 }
496 assert_eq!(wrappers, depth - 1);
497 assert_eq!(*cur, Type::con(INT));
498 }
499}