/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 | 47 | pub fn new() -> Self { |
15 | 47 | Unifier { |
16 | 47 | subst: HashMap::new(), |
17 | 47 | } |
18 | 47 | } |
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 | 257 | pub fn apply(&self, ty: &MonoType) -> MonoType { |
29 | 257 | ty.substitute(&self.subst) |
30 | 257 | } |
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 | 93 | pub fn unify(&mut self, t1: &MonoType, t2: &MonoType) -> Result<()> { |
41 | 93 | let t1 = self.apply(t1); |
42 | 93 | let t2 = self.apply(t2); |
43 | | |
44 | 93 | match (t1, t2) { |
45 | 3 | (MonoType::Var(v10 ), MonoType::Var(v20 )) if v1 == v20 => Ok(())0 , |
46 | 27 | (MonoType::Var(v22 ), t22 ) | (t5 , MonoType::Var(v5 )) => 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 | 53 | | (MonoType::Unit, MonoType::Unit) => Ok(()), |
52 | 0 | (MonoType::Named(n1), MonoType::Named(n2)) if n1 == n2 => Ok(()), |
53 | 7 | (MonoType::Function(a1, r1), MonoType::Function(a2, r2)) => { |
54 | 7 | self.unify(&a1, &a2)?0 ; |
55 | 7 | self.unify(&r1, &r2) |
56 | | } |
57 | 1 | (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 | 5 | (t1, t2) => bail!("Cannot unify {} with {}", t1, t2), |
82 | | } |
83 | 93 | } |
84 | | |
85 | | /// Bind a type variable to a type |
86 | 27 | fn bind(&mut self, var: &TyVar, ty: &MonoType) -> Result<()> { |
87 | | // Occurs check: ensure var doesn't occur in ty |
88 | 27 | if Self::occurs(var, ty) { |
89 | 1 | bail!("Infinite type: {} occurs in {}", var, ty); |
90 | 26 | } |
91 | | |
92 | | // Apply the binding |
93 | 26 | self.subst.insert(var.clone(), ty.clone()); |
94 | | |
95 | | // Update existing substitutions |
96 | 26 | let updated: Substitution = self |
97 | 26 | .subst |
98 | 26 | .iter() |
99 | 43 | .map26 (|(k, v)| { |
100 | 43 | if k == var { |
101 | 26 | (k.clone(), ty.clone()) |
102 | | } else { |
103 | 17 | (k.clone(), v.substitute(&[(var.clone(), ty.clone())].into())) |
104 | | } |
105 | 43 | }) |
106 | 26 | .collect(); |
107 | 26 | self.subst = updated; |
108 | | |
109 | 26 | Ok(()) |
110 | 27 | } |
111 | | |
112 | | /// Check if a type variable occurs in a type (occurs check) |
113 | 36 | fn occurs(var: &TyVar, ty: &MonoType) -> bool { |
114 | 36 | match ty { |
115 | 8 | MonoType::Var(v) => v == var, |
116 | 3 | MonoType::Function(arg, ret) => Self::occurs(var, arg) || Self::occurs(var, ret), |
117 | 3 | 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 | 22 | _ => false, |
127 | | } |
128 | 36 | } |
129 | | |
130 | | /// Solve a type variable to its final type |
131 | | #[must_use] |
132 | 5 | pub fn solve(&self, var: &TyVar) -> MonoType { |
133 | 5 | self.subst |
134 | 5 | .get(var) |
135 | 5 | .map_or_else(|| MonoType::Var(var0 .clone0 ()), |ty| self.apply(ty)) |
136 | 5 | } |
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 | 1 | fn test_unify_same_types() { |
152 | 1 | let mut unifier = Unifier::new(); |
153 | 1 | assert!(unifier.unify(&MonoType::Int, &MonoType::Int).is_ok()); |
154 | 1 | assert!(unifier.unify(&MonoType::Bool, &MonoType::Bool).is_ok()); |
155 | 1 | assert!(unifier.unify(&MonoType::String, &MonoType::String).is_ok()); |
156 | 1 | } |
157 | | |
158 | | #[test] |
159 | 1 | fn test_unify_different_types() { |
160 | 1 | let mut unifier = Unifier::new(); |
161 | 1 | assert!(unifier.unify(&MonoType::Int, &MonoType::Bool).is_err()); |
162 | 1 | assert!(unifier.unify(&MonoType::String, &MonoType::Int).is_err()); |
163 | 1 | } |
164 | | |
165 | | #[test] |
166 | 1 | fn test_unify_with_var() { |
167 | 1 | let mut unifier = Unifier::new(); |
168 | 1 | let var = TyVar(0); |
169 | | |
170 | 1 | assert!(unifier |
171 | 1 | .unify(&MonoType::Var(var.clone()), &MonoType::Int) |
172 | 1 | .is_ok()); |
173 | 1 | assert_eq!(unifier.solve(&var), MonoType::Int); |
174 | 1 | } |
175 | | |
176 | | #[test] |
177 | 1 | fn test_unify_functions() { |
178 | 1 | let mut unifier = Unifier::new(); |
179 | 1 | let var = TyVar(0); |
180 | | |
181 | 1 | let f1 = MonoType::Function( |
182 | 1 | Box::new(MonoType::Int), |
183 | 1 | Box::new(MonoType::Var(var.clone())), |
184 | 1 | ); |
185 | 1 | let f2 = MonoType::Function(Box::new(MonoType::Int), Box::new(MonoType::Bool)); |
186 | | |
187 | 1 | assert!(unifier.unify(&f1, &f2).is_ok()); |
188 | 1 | assert_eq!(unifier.solve(&var), MonoType::Bool); |
189 | 1 | } |
190 | | |
191 | | #[test] |
192 | 1 | fn test_unify_lists() { |
193 | 1 | let mut unifier = Unifier::new(); |
194 | 1 | let var = TyVar(0); |
195 | | |
196 | 1 | let l1 = MonoType::List(Box::new(MonoType::Var(var.clone()))); |
197 | 1 | let l2 = MonoType::List(Box::new(MonoType::String)); |
198 | | |
199 | 1 | assert!(unifier.unify(&l1, &l2).is_ok()); |
200 | 1 | assert_eq!(unifier.solve(&var), MonoType::String); |
201 | 1 | } |
202 | | |
203 | | #[test] |
204 | 1 | fn test_occurs_check() { |
205 | 1 | let mut unifier = Unifier::new(); |
206 | 1 | let var = TyVar(0); |
207 | | |
208 | | // Try to unify τ0 with [τ0] - should fail (infinite type) |
209 | 1 | let infinite = MonoType::List(Box::new(MonoType::Var(var.clone()))); |
210 | 1 | assert!(unifier.unify(&MonoType::Var(var), &infinite).is_err()); |
211 | 1 | } |
212 | | |
213 | | #[test] |
214 | 1 | fn test_transitive_unification() { |
215 | 1 | let mut unifier = Unifier::new(); |
216 | 1 | let var1 = TyVar(0); |
217 | 1 | let var2 = TyVar(1); |
218 | | |
219 | | // τ0 = τ1 |
220 | 1 | assert!(unifier |
221 | 1 | .unify(&MonoType::Var(var1.clone()), &MonoType::Var(var2.clone())) |
222 | 1 | .is_ok()); |
223 | | |
224 | | // τ1 = Int |
225 | 1 | assert!(unifier |
226 | 1 | .unify(&MonoType::Var(var2.clone()), &MonoType::Int) |
227 | 1 | .is_ok()); |
228 | | |
229 | | // Now τ0 should also be Int |
230 | 1 | assert_eq!(unifier.solve(&var1), MonoType::Int); |
231 | 1 | assert_eq!(unifier.solve(&var2), MonoType::Int); |
232 | 1 | } |
233 | | } |