Coverage Report

Created: 2025-09-05 15:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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 == v
20
=>
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
}