Skip to main content

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}