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    /// # Examples
104    ///
105    /// ```
106    /// use type_lang::Unifier;
107    ///
108    /// let mut unifier = Unifier::new();
109    /// let a = unifier.fresh();
110    /// let b = unifier.fresh();
111    /// assert_ne!(a, b);
112    /// assert_eq!(unifier.var_count(), 2);
113    /// ```
114    #[inline]
115    #[must_use]
116    pub fn fresh(&mut self) -> TyVar {
117        let index = self.bindings.len() as u32;
118        self.bindings.push(None);
119        TyVar::from_index(index)
120    }
121
122    /// Returns the number of variables this unifier has minted.
123    #[inline]
124    #[must_use]
125    pub fn var_count(&self) -> usize {
126        self.bindings.len()
127    }
128
129    /// Returns `true` if no variables have been minted.
130    #[inline]
131    #[must_use]
132    pub fn is_empty(&self) -> bool {
133        self.bindings.is_empty()
134    }
135
136    /// Unifies two types, binding variables as needed to make them equal.
137    ///
138    /// On success the unifier's substitution is extended so that `a` and `b`
139    /// [`resolve`](Self::resolve) to the same type. On failure the call returns a
140    /// [`TypeError`] describing why, and **any bindings made before the conflict
141    /// was reached are kept** — unification is not transactional. A caller that
142    /// needs to try a unification speculatively should [`clone`](Clone::clone) the
143    /// unifier first and keep the clone only if it succeeds.
144    ///
145    /// # Errors
146    ///
147    /// - [`TypeError::Mismatch`] if the two types are built from different
148    ///   constructors or the same constructor at a different arity.
149    /// - [`TypeError::Occurs`] if making them equal would require binding a
150    ///   variable to a type that contains it (an infinite type).
151    ///
152    /// # Examples
153    ///
154    /// ```
155    /// use type_lang::{TyCon, Type, TypeError, Unifier};
156    ///
157    /// const INT: TyCon = TyCon::new(0);
158    /// const BOOL: TyCon = TyCon::new(1);
159    ///
160    /// let mut unifier = Unifier::new();
161    /// let v = unifier.fresh();
162    ///
163    /// // A variable unifies with a concrete type by being bound to it.
164    /// unifier.unify(&Type::var(v), &Type::con(INT)).unwrap();
165    /// assert_eq!(unifier.resolve(&Type::var(v)), Type::con(INT));
166    ///
167    /// // Two different constructors do not unify.
168    /// let err = unifier.unify(&Type::con(INT), &Type::con(BOOL)).unwrap_err();
169    /// assert!(matches!(err, TypeError::Mismatch { .. }));
170    /// ```
171    pub fn unify(&mut self, a: &Type, b: &Type) -> Result<(), TypeError> {
172        let mut work = Vec::new();
173        work.push((a.clone(), b.clone()));
174
175        while let Some((lhs, rhs)) = work.pop() {
176            let lhs = self.shallow(lhs);
177            let rhs = self.shallow(rhs);
178            match (lhs, rhs) {
179                (Type::Var(x), Type::Var(y)) if x == y => {}
180                (Type::Var(x), other) | (other, Type::Var(x)) => self.bind(x, other)?,
181                (Type::App(head1, args1), Type::App(head2, args2)) => {
182                    if head1 != head2 || args1.len() != args2.len() {
183                        return Err(TypeError::Mismatch {
184                            expected: self.resolve(&Type::App(head1, args1)),
185                            found: self.resolve(&Type::App(head2, args2)),
186                        });
187                    }
188                    work.extend(args1.into_iter().zip(args2));
189                }
190            }
191        }
192        Ok(())
193    }
194
195    /// Resolves a type fully under the current substitution.
196    ///
197    /// Every bound variable in `ty` is replaced by what it was bound to, all the
198    /// way down, leaving a type whose only variables are still unbound. A type with
199    /// no bound variables resolves to an equal copy of itself.
200    ///
201    /// The walk is proportional to the size of the resolved result, which — for a
202    /// substitution that maps a variable to a type mentioning it twice — can be
203    /// larger than the input. Resolve once you are done constraining a type, rather
204    /// than after every step.
205    ///
206    /// # Examples
207    ///
208    /// ```
209    /// use type_lang::{TyCon, Type, Unifier};
210    ///
211    /// const LIST: TyCon = TyCon::new(0);
212    /// const INT: TyCon = TyCon::new(1);
213    ///
214    /// let mut unifier = Unifier::new();
215    /// let element = unifier.fresh();
216    /// let collection = unifier.fresh();
217    ///
218    /// // ?collection = List<?element>, then ?element = int.
219    /// unifier
220    ///     .unify(&Type::var(collection), &Type::app(LIST, [Type::var(element)]))
221    ///     .unwrap();
222    /// unifier.unify(&Type::var(element), &Type::con(INT)).unwrap();
223    ///
224    /// // Resolving the outer variable substitutes all the way down.
225    /// assert_eq!(
226    ///     unifier.resolve(&Type::var(collection)),
227    ///     Type::app(LIST, [Type::con(INT)]),
228    /// );
229    ///
230    /// // An unbound variable resolves to itself.
231    /// let free = unifier.fresh();
232    /// assert_eq!(unifier.resolve(&Type::var(free)), Type::var(free));
233    /// ```
234    #[must_use]
235    pub fn resolve(&self, ty: &Type) -> Type {
236        match ty {
237            Type::Var(v) => match self.binding(*v) {
238                Some(bound) => self.resolve(bound),
239                None => Type::Var(*v),
240            },
241            Type::App(head, args) => {
242                Type::App(*head, args.iter().map(|arg| self.resolve(arg)).collect())
243            }
244        }
245    }
246
247    /// Returns the direct binding of `var`, or `None` if it is unbound (or not from
248    /// this unifier). The binding is shallow — it may itself be another variable.
249    #[inline]
250    fn binding(&self, var: TyVar) -> Option<&Type> {
251        self.bindings.get(var.to_u32() as usize)?.as_ref()
252    }
253
254    /// Follows variable bindings at the top level until the term is either an
255    /// unbound variable or a constructor. Does not descend into arguments.
256    fn shallow(&self, mut ty: Type) -> Type {
257        while let Type::Var(v) = ty {
258            match self.binding(v) {
259                Some(bound) => ty = bound.clone(),
260                None => return Type::Var(v),
261            }
262        }
263        ty
264    }
265
266    /// Binds an unbound variable to a type, after the occurs check.
267    ///
268    /// `var` is known to be unbound and `ty` is shallow-resolved (not a bound
269    /// variable), because both come straight out of [`shallow`](Self::shallow).
270    fn bind(&mut self, var: TyVar, ty: Type) -> Result<(), TypeError> {
271        if self.occurs(var, &ty) {
272            return Err(TypeError::Occurs {
273                var,
274                ty: self.resolve(&ty),
275            });
276        }
277        // The slot exists for every variable this unifier minted; a variable from
278        // another unifier is out of contract and is left untracked rather than
279        // panicking.
280        if let Some(slot) = self.bindings.get_mut(var.to_u32() as usize) {
281            *slot = Some(ty);
282        }
283        Ok(())
284    }
285
286    /// Reports whether `var` occurs anywhere within `ty`, looking through bindings.
287    ///
288    /// Iterative, with an explicit work stack, so an arbitrarily deep type cannot
289    /// overflow the call stack.
290    fn occurs(&self, var: TyVar, ty: &Type) -> bool {
291        let mut stack = Vec::new();
292        stack.push(ty);
293        while let Some(term) = stack.pop() {
294            match term {
295                Type::Var(other) => {
296                    if *other == var {
297                        return true;
298                    }
299                    if let Some(bound) = self.binding(*other) {
300                        stack.push(bound);
301                    }
302                }
303                Type::App(_, args) => stack.extend(args.iter()),
304            }
305        }
306        false
307    }
308}
309
310#[cfg(test)]
311mod tests {
312    extern crate alloc;
313    use alloc::vec;
314
315    use super::*;
316    use crate::ty::TyCon;
317
318    const INT: TyCon = TyCon::new(0);
319    const BOOL: TyCon = TyCon::new(1);
320    const LIST: TyCon = TyCon::new(2);
321    const PAIR: TyCon = TyCon::new(3);
322
323    #[test]
324    fn test_fresh_variables_are_distinct_and_counted() {
325        let mut u = Unifier::new();
326        let a = u.fresh();
327        let b = u.fresh();
328        assert_ne!(a, b);
329        assert_eq!(u.var_count(), 2);
330        assert!(!u.is_empty());
331    }
332
333    #[test]
334    fn test_unify_variable_with_constructor_binds_it() {
335        let mut u = Unifier::new();
336        let v = u.fresh();
337        u.unify(&Type::var(v), &Type::con(INT)).unwrap();
338        assert_eq!(u.resolve(&Type::var(v)), Type::con(INT));
339    }
340
341    #[test]
342    fn test_unify_propagates_through_a_variable_chain() {
343        let mut u = Unifier::new();
344        let x = u.fresh();
345        let y = u.fresh();
346        u.unify(&Type::var(x), &Type::var(y)).unwrap();
347        u.unify(&Type::var(y), &Type::con(BOOL)).unwrap();
348        // Binding y is visible through x.
349        assert_eq!(u.resolve(&Type::var(x)), Type::con(BOOL));
350    }
351
352    #[test]
353    fn test_unify_matching_constructors_unifies_arguments() {
354        let mut u = Unifier::new();
355        let a = u.fresh();
356        let b = u.fresh();
357        let lhs = Type::app(PAIR, vec![Type::var(a), Type::con(INT)]);
358        let rhs = Type::app(PAIR, vec![Type::con(BOOL), Type::var(b)]);
359        u.unify(&lhs, &rhs).unwrap();
360        assert_eq!(u.resolve(&Type::var(a)), Type::con(BOOL));
361        assert_eq!(u.resolve(&Type::var(b)), Type::con(INT));
362    }
363
364    #[test]
365    fn test_unify_different_heads_is_mismatch() {
366        let mut u = Unifier::new();
367        let err = u.unify(&Type::con(INT), &Type::con(BOOL)).unwrap_err();
368        assert!(matches!(err, TypeError::Mismatch { .. }));
369    }
370
371    #[test]
372    fn test_unify_same_head_different_arity_is_mismatch() {
373        let mut u = Unifier::new();
374        let one = Type::app(PAIR, vec![Type::con(INT)]);
375        let two = Type::app(PAIR, vec![Type::con(INT), Type::con(INT)]);
376        let err = u.unify(&one, &two).unwrap_err();
377        assert!(matches!(err, TypeError::Mismatch { .. }));
378    }
379
380    #[test]
381    fn test_occurs_check_rejects_infinite_type() {
382        let mut u = Unifier::new();
383        let v = u.fresh();
384        let recursive = Type::app(LIST, vec![Type::var(v)]);
385        let err = u.unify(&Type::var(v), &recursive).unwrap_err();
386        match err {
387            TypeError::Occurs { var, .. } => assert_eq!(var, v),
388            other => panic!("expected occurs error, got {other:?}"),
389        }
390    }
391
392    #[test]
393    fn test_occurs_check_sees_through_a_binding() {
394        let mut u = Unifier::new();
395        let x = u.fresh();
396        let y = u.fresh();
397        // y = List<x>, then x = y would make x = List<x>.
398        u.unify(&Type::var(y), &Type::app(LIST, vec![Type::var(x)]))
399            .unwrap();
400        let err = u.unify(&Type::var(x), &Type::var(y)).unwrap_err();
401        assert!(matches!(err, TypeError::Occurs { .. }));
402    }
403
404    #[test]
405    fn test_unify_identical_terms_always_succeeds() {
406        let mut u = Unifier::new();
407        let v = u.fresh();
408        let term = Type::app(PAIR, vec![Type::var(v), Type::con(INT)]);
409        u.unify(&term, &term).unwrap();
410    }
411
412    #[test]
413    fn test_resolve_is_idempotent() {
414        let mut u = Unifier::new();
415        let x = u.fresh();
416        u.unify(&Type::var(x), &Type::app(LIST, vec![Type::con(INT)]))
417            .unwrap();
418        let once = u.resolve(&Type::var(x));
419        let twice = u.resolve(&once);
420        assert_eq!(once, twice);
421    }
422
423    #[test]
424    fn test_default_matches_new() {
425        let a = Unifier::default();
426        let b = Unifier::new();
427        assert_eq!(a.var_count(), b.var_count());
428    }
429}