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}