type_lang/error.rs
1//! The error returned when two types cannot be unified.
2
3use core::fmt;
4
5use crate::ty::{TyVar, Type};
6
7/// The reason a [`unify`](crate::Unifier::unify) call failed.
8///
9/// Unification fails in exactly two ways, each a distinct, defined outcome rather
10/// than a panic:
11///
12/// - the two types are built from different constructors, or the same constructor
13/// applied to a different number of arguments ([`Mismatch`](Self::Mismatch)), or
14/// - binding a variable would make it refer to itself, producing an infinite type
15/// ([`Occurs`](Self::Occurs)).
16///
17/// Both variants carry the offending types, fully resolved under the substitution
18/// at the point of failure, so the failure is actionable when it is rendered far
19/// from the call that produced it — typically by mapping it onto a `diag-lang`
20/// diagnostic with the consumer's own type names.
21///
22/// The enum is `#[non_exhaustive]`: a downstream `match` must include a wildcard
23/// arm, so a later addition is a minor change, not a breaking one.
24///
25/// # Examples
26///
27/// ```
28/// use type_lang::{TyCon, Type, TypeError, Unifier};
29///
30/// const INT: TyCon = TyCon::new(0);
31/// const BOOL: TyCon = TyCon::new(1);
32///
33/// let mut unifier = Unifier::new();
34/// let err = unifier
35/// .unify(&Type::con(INT), &Type::con(BOOL))
36/// .unwrap_err();
37/// assert!(matches!(err, TypeError::Mismatch { .. }));
38/// ```
39#[derive(Clone, Debug, PartialEq, Eq)]
40#[non_exhaustive]
41pub enum TypeError {
42 /// The two types do not share a constructor.
43 ///
44 /// Returned when the heads differ (`Int` versus `Bool`) or when the same head
45 /// is applied to a different arity (`Pair<A>` versus `Pair<A, B>`). Both sides
46 /// are resolved under the current substitution before being stored, so they
47 /// show the most concrete form known at the point of failure.
48 ///
49 /// `unify(a, b)` records `a` as `expected` and `b` as `found`. Unification
50 /// itself is symmetric — the labels only reflect the argument order, to match
51 /// the common "expected this, found that" phrasing of a type error.
52 Mismatch {
53 /// The first type given to `unify`, resolved.
54 expected: Type,
55 /// The second type given to `unify`, resolved.
56 found: Type,
57 },
58
59 /// Binding a variable would make it occur within its own definition.
60 ///
61 /// Returned when a variable would be bound to a type that already contains it —
62 /// for example unifying `?0` with `List<?0>`. Such a binding describes an
63 /// infinitely deep type, which the occurs check rejects so that resolution
64 /// always terminates.
65 Occurs {
66 /// The variable that would refer to itself.
67 var: TyVar,
68 /// The type it would have been bound to, resolved.
69 ty: Type,
70 },
71}
72
73impl fmt::Display for TypeError {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 match self {
76 Self::Mismatch { expected, found } => {
77 write!(f, "type mismatch: expected `{expected}`, found `{found}`")
78 }
79 Self::Occurs { var, ty } => {
80 write!(
81 f,
82 "recursive type: variable `?{}` occurs in `{ty}`",
83 var.to_u32(),
84 )
85 }
86 }
87 }
88}
89
90impl core::error::Error for TypeError {}
91
92#[cfg(test)]
93mod tests {
94 extern crate alloc;
95 use alloc::string::ToString;
96 use alloc::vec;
97
98 use super::*;
99 use crate::ty::TyCon;
100
101 const INT: TyCon = TyCon::new(0);
102 const LIST: TyCon = TyCon::new(7);
103
104 #[test]
105 fn test_mismatch_display_names_both_types() {
106 let err = TypeError::Mismatch {
107 expected: Type::con(INT),
108 found: Type::app(LIST, vec![Type::con(INT)]),
109 };
110 let text = err.to_string();
111 assert!(text.contains("#0"), "{text}");
112 assert!(text.contains("#7(#0)"), "{text}");
113 }
114
115 #[test]
116 fn test_occurs_display_names_variable() {
117 let err = TypeError::Occurs {
118 var: TyVar::from_index(3),
119 ty: Type::app(LIST, vec![Type::var(TyVar::from_index(3))]),
120 };
121 let text = err.to_string();
122 assert!(text.contains("?3"), "{text}");
123 }
124
125 #[test]
126 fn test_error_is_clonable_and_equatable() {
127 let a = TypeError::Mismatch {
128 expected: Type::con(INT),
129 found: Type::con(LIST),
130 };
131 let b = a.clone();
132 assert_eq!(a, b);
133 }
134}