/home/noah/src/ruchy/src/middleend/unify.rs
Line | Count | Source |
1 | | //! Unification algorithm for type inference |
2 | | |
3 | | use crate::middleend::types::{MonoType, Substitution, TyVar}; |
4 | | use anyhow::{bail, Result}; |
5 | | use std::collections::HashMap; |
6 | | |
7 | | /// Unification engine for type inference |
8 | | pub struct Unifier { |
9 | | subst: Substitution, |
10 | | } |
11 | | |
12 | | impl Unifier { |
13 | | #[must_use] |
14 | 0 | pub fn new() -> Self { |
15 | 0 | Unifier { |
16 | 0 | subst: HashMap::new(), |
17 | 0 | } |
18 | 0 | } |
19 | | |
20 | | /// Get the current substitution |
21 | | #[must_use] |
22 | 0 | pub fn substitution(&self) -> &Substitution { |
23 | 0 | &self.subst |
24 | 0 | } |
25 | | |
26 | | /// Apply current substitution to a type |
27 | | #[must_use] |
28 | 0 | pub fn apply(&self, ty: &MonoType) -> MonoType { |
29 | 0 | ty.substitute(&self.subst) |
30 | 0 | } |
31 | | |
32 | | /// Unify two types, updating the substitution |
33 | | /// |
34 | | /// # Errors |
35 | | /// |
36 | | /// Returns an error if the types cannot be unified (type mismatch or occurs check failure) |
37 | | /// # Errors |
38 | | /// |
39 | | /// Returns an error if the operation fails |
40 | 0 | pub fn unify(&mut self, t1: &MonoType, t2: &MonoType) -> Result<()> { |
41 | 0 | let t1 = self.apply(t1); |
42 | 0 | let t2 = self.apply(t2); |
43 | | |
44 | 0 | match (t1, t2) { |
45 | 0 | (MonoType::Var(v1), MonoType::Var(v2)) if v1 == v2 => Ok(()), |
46 | 0 | (MonoType::Var(v), t) | (t, MonoType::Var(v)) => self.bind(&v, &t), |
47 | | (MonoType::Int, MonoType::Int) |
48 | | | (MonoType::Float, MonoType::Float) |
49 | | | (MonoType::Bool, MonoType::Bool) |
50 | | | (MonoType::String, MonoType::String) |
51 | 0 | | (MonoType::Unit, MonoType::Unit) => Ok(()), |
52 | 0 | (MonoType::Named(n1), MonoType::Named(n2)) if n1 == n2 => Ok(()), |
53 | 0 | (MonoType::Function(a1, r1), MonoType::Function(a2, r2)) => { |
54 | 0 | self.unify(&a1, &a2)?; |
55 | 0 | self.unify(&r1, &r2) |
56 | | } |
57 | 0 | (MonoType::List(e1), MonoType::List(e2)) => self.unify(&e1, &e2), |
58 | 0 | (MonoType::Optional(i1), MonoType::Optional(i2)) => self.unify(&i1, &i2), |
59 | 0 | (MonoType::Result(ok1, err1), MonoType::Result(ok2, err2)) => { |
60 | 0 | self.unify(&ok1, &ok2)?; |
61 | 0 | self.unify(&err1, &err2) |
62 | | } |
63 | 0 | (MonoType::DataFrame(cols1), MonoType::DataFrame(cols2)) => { |
64 | | // DataFrames unify if they have the same columns with the same types |
65 | 0 | if cols1.len() != cols2.len() { |
66 | 0 | bail!("Cannot unify DataFrames with different column counts"); |
67 | 0 | } |
68 | 0 | for ((name1, ty1), (name2, ty2)) in cols1.iter().zip(cols2.iter()) { |
69 | 0 | if name1 != name2 { |
70 | 0 | bail!( |
71 | 0 | "Cannot unify DataFrames with different column names: {} vs {}", |
72 | | name1, |
73 | | name2 |
74 | | ); |
75 | 0 | } |
76 | 0 | self.unify(ty1, ty2)?; |
77 | | } |
78 | 0 | Ok(()) |
79 | | } |
80 | 0 | (MonoType::Series(ty1), MonoType::Series(ty2)) => self.unify(&ty1, &ty2), |
81 | 0 | (t1, t2) => bail!("Cannot unify {} with {}", t1, t2), |
82 | | } |
83 | 0 | } |
84 | | |
85 | | /// Bind a type variable to a type |
86 | 0 | fn bind(&mut self, var: &TyVar, ty: &MonoType) -> Result<()> { |
87 | | // Occurs check: ensure var doesn't occur in ty |
88 | 0 | if Self::occurs(var, ty) { |
89 | 0 | bail!("Infinite type: {} occurs in {}", var, ty); |
90 | 0 | } |
91 | | |
92 | | // Apply the binding |
93 | 0 | self.subst.insert(var.clone(), ty.clone()); |
94 | | |
95 | | // Update existing substitutions |
96 | 0 | let updated: Substitution = self |
97 | 0 | .subst |
98 | 0 | .iter() |
99 | 0 | .map(|(k, v)| { |
100 | 0 | if k == var { |
101 | 0 | (k.clone(), ty.clone()) |
102 | | } else { |
103 | 0 | (k.clone(), v.substitute(&[(var.clone(), ty.clone())].into())) |
104 | | } |
105 | 0 | }) |
106 | 0 | .collect(); |
107 | 0 | self.subst = updated; |
108 | | |
109 | 0 | Ok(()) |
110 | 0 | } |
111 | | |
112 | | /// Check if a type variable occurs in a type (occurs check) |
113 | 0 | fn occurs(var: &TyVar, ty: &MonoType) -> bool { |
114 | 0 | match ty { |
115 | 0 | MonoType::Var(v) => v == var, |
116 | 0 | MonoType::Function(arg, ret) => Self::occurs(var, arg) || Self::occurs(var, ret), |
117 | 0 | MonoType::List(elem) => Self::occurs(var, elem), |
118 | 0 | MonoType::Optional(inner) | MonoType::Series(inner) | MonoType::Reference(inner) => { |
119 | 0 | Self::occurs(var, inner) |
120 | | } |
121 | 0 | MonoType::Result(ok, err) => Self::occurs(var, ok) || Self::occurs(var, err), |
122 | 0 | MonoType::DataFrame(columns) => { |
123 | 0 | columns.iter().any(|(_, col_ty)| Self::occurs(var, col_ty)) |
124 | | } |
125 | 0 | MonoType::Tuple(types) => types.iter().any(|ty| Self::occurs(var, ty)), |
126 | 0 | _ => false, |
127 | | } |
128 | 0 | } |
129 | | |
130 | | /// Solve a type variable to its final type |
131 | | #[must_use] |
132 | 0 | pub fn solve(&self, var: &TyVar) -> MonoType { |
133 | 0 | self.subst |
134 | 0 | .get(var) |
135 | 0 | .map_or_else(|| MonoType::Var(var.clone()), |ty| self.apply(ty)) |
136 | 0 | } |
137 | | } |
138 | | |
139 | | impl Default for Unifier { |
140 | 0 | fn default() -> Self { |
141 | 0 | Self::new() |
142 | 0 | } |
143 | | } |
144 | | |
145 | | #[cfg(test)] |
146 | | #[allow(clippy::unwrap_used, clippy::panic)] |
147 | | mod tests { |
148 | | use super::*; |
149 | | |
150 | | #[test] |
151 | | fn test_unify_same_types() { |
152 | | let mut unifier = Unifier::new(); |
153 | | assert!(unifier.unify(&MonoType::Int, &MonoType::Int).is_ok()); |
154 | | assert!(unifier.unify(&MonoType::Bool, &MonoType::Bool).is_ok()); |
155 | | assert!(unifier.unify(&MonoType::String, &MonoType::String).is_ok()); |
156 | | } |
157 | | |
158 | | #[test] |
159 | | fn test_unify_different_types() { |
160 | | let mut unifier = Unifier::new(); |
161 | | assert!(unifier.unify(&MonoType::Int, &MonoType::Bool).is_err()); |
162 | | assert!(unifier.unify(&MonoType::String, &MonoType::Int).is_err()); |
163 | | } |
164 | | |
165 | | #[test] |
166 | | fn test_unify_with_var() { |
167 | | let mut unifier = Unifier::new(); |
168 | | let var = TyVar(0); |
169 | | |
170 | | assert!(unifier |
171 | | .unify(&MonoType::Var(var.clone()), &MonoType::Int) |
172 | | .is_ok()); |
173 | | assert_eq!(unifier.solve(&var), MonoType::Int); |
174 | | } |
175 | | |
176 | | #[test] |
177 | | fn test_unify_functions() { |
178 | | let mut unifier = Unifier::new(); |
179 | | let var = TyVar(0); |
180 | | |
181 | | let f1 = MonoType::Function( |
182 | | Box::new(MonoType::Int), |
183 | | Box::new(MonoType::Var(var.clone())), |
184 | | ); |
185 | | let f2 = MonoType::Function(Box::new(MonoType::Int), Box::new(MonoType::Bool)); |
186 | | |
187 | | assert!(unifier.unify(&f1, &f2).is_ok()); |
188 | | assert_eq!(unifier.solve(&var), MonoType::Bool); |
189 | | } |
190 | | |
191 | | #[test] |
192 | | fn test_unify_lists() { |
193 | | let mut unifier = Unifier::new(); |
194 | | let var = TyVar(0); |
195 | | |
196 | | let l1 = MonoType::List(Box::new(MonoType::Var(var.clone()))); |
197 | | let l2 = MonoType::List(Box::new(MonoType::String)); |
198 | | |
199 | | assert!(unifier.unify(&l1, &l2).is_ok()); |
200 | | assert_eq!(unifier.solve(&var), MonoType::String); |
201 | | } |
202 | | |
203 | | #[test] |
204 | | fn test_occurs_check() { |
205 | | let mut unifier = Unifier::new(); |
206 | | let var = TyVar(0); |
207 | | |
208 | | // Try to unify τ0 with [τ0] - should fail (infinite type) |
209 | | let infinite = MonoType::List(Box::new(MonoType::Var(var.clone()))); |
210 | | assert!(unifier.unify(&MonoType::Var(var), &infinite).is_err()); |
211 | | } |
212 | | |
213 | | #[test] |
214 | | fn test_transitive_unification() { |
215 | | let mut unifier = Unifier::new(); |
216 | | let var1 = TyVar(0); |
217 | | let var2 = TyVar(1); |
218 | | |
219 | | // τ0 = τ1 |
220 | | assert!(unifier |
221 | | .unify(&MonoType::Var(var1.clone()), &MonoType::Var(var2.clone())) |
222 | | .is_ok()); |
223 | | |
224 | | // τ1 = Int |
225 | | assert!(unifier |
226 | | .unify(&MonoType::Var(var2.clone()), &MonoType::Int) |
227 | | .is_ok()); |
228 | | |
229 | | // Now τ0 should also be Int |
230 | | assert_eq!(unifier.solve(&var1), MonoType::Int); |
231 | | assert_eq!(unifier.solve(&var2), MonoType::Int); |
232 | | } |
233 | | } |