/home/noah/src/ruchy/src/middleend/environment.rs
Line | Count | Source |
1 | | //! Type environment for type inference |
2 | | |
3 | | use crate::middleend::types::{MonoType, TyVarGenerator, TypeScheme}; |
4 | | use std::collections::HashMap; |
5 | | |
6 | | /// Type environment mapping identifiers to type schemes |
7 | | #[derive(Debug, Clone)] |
8 | | pub struct TypeEnv { |
9 | | bindings: HashMap<String, TypeScheme>, |
10 | | } |
11 | | |
12 | | impl TypeEnv { |
13 | | #[must_use] |
14 | 55 | pub fn new() -> Self { |
15 | 55 | TypeEnv { |
16 | 55 | bindings: HashMap::new(), |
17 | 55 | } |
18 | 55 | } |
19 | | |
20 | | /// Create a standard environment with built-in functions |
21 | | #[must_use] |
22 | 42 | pub fn standard() -> Self { |
23 | 42 | let mut env = Self::new(); |
24 | | |
25 | | // Arithmetic functions |
26 | 42 | env.bind( |
27 | | "add", |
28 | 42 | TypeScheme::mono(MonoType::Function( |
29 | 42 | Box::new(MonoType::Int), |
30 | 42 | Box::new(MonoType::Function( |
31 | 42 | Box::new(MonoType::Int), |
32 | 42 | Box::new(MonoType::Int), |
33 | 42 | )), |
34 | 42 | )), |
35 | | ); |
36 | | |
37 | | // IO functions |
38 | 42 | env.bind( |
39 | | "print", |
40 | 42 | TypeScheme::mono(MonoType::Function( |
41 | 42 | Box::new(MonoType::String), |
42 | 42 | Box::new(MonoType::Unit), |
43 | 42 | )), |
44 | | ); |
45 | | |
46 | 42 | env.bind( |
47 | | "println", |
48 | 42 | TypeScheme::mono(MonoType::Function( |
49 | 42 | Box::new(MonoType::String), |
50 | 42 | Box::new(MonoType::Unit), |
51 | 42 | )), |
52 | | ); |
53 | | |
54 | | // Comparison functions |
55 | 42 | env.bind( |
56 | | "eq", |
57 | 42 | TypeScheme::mono(MonoType::Function( |
58 | 42 | Box::new(MonoType::Int), |
59 | 42 | Box::new(MonoType::Function( |
60 | 42 | Box::new(MonoType::Int), |
61 | 42 | Box::new(MonoType::Bool), |
62 | 42 | )), |
63 | 42 | )), |
64 | | ); |
65 | | |
66 | 42 | env |
67 | 42 | } |
68 | | |
69 | | /// Bind a name to a type scheme |
70 | 209 | pub fn bind(&mut self, name: impl Into<String>, scheme: TypeScheme) { |
71 | 209 | self.bindings.insert(name.into(), scheme); |
72 | 209 | } |
73 | | |
74 | | /// Look up a name in the environment |
75 | | #[must_use] |
76 | 46 | pub fn lookup(&self, name: &str) -> Option<&TypeScheme> { |
77 | 46 | self.bindings.get(name) |
78 | 46 | } |
79 | | |
80 | | /// Extend the environment with a new binding (functional style) |
81 | | #[must_use] |
82 | 31 | pub fn extend(&self, name: impl Into<String>, scheme: TypeScheme) -> Self { |
83 | 31 | let mut new_env = self.clone(); |
84 | 31 | new_env.bind(name, scheme); |
85 | 31 | new_env |
86 | 31 | } |
87 | | |
88 | | /// Get free type variables in the environment |
89 | | #[must_use] |
90 | 17 | pub fn free_vars(&self) -> Vec<crate::middleend::types::TyVar> { |
91 | 17 | let mut vars = Vec::new(); |
92 | 52 | for scheme in self.bindings17 .values17 () { |
93 | | // Only collect free variables not bound by the scheme |
94 | 52 | let scheme_free = scheme.ty.free_vars(); |
95 | 59 | for var7 in scheme_free { |
96 | 7 | if !scheme.vars.contains(&var) { |
97 | 2 | vars.push(var); |
98 | 5 | } |
99 | | } |
100 | | } |
101 | 17 | vars |
102 | 17 | } |
103 | | |
104 | | /// Generalize a monomorphic type to a type scheme |
105 | | #[must_use] |
106 | 15 | pub fn generalize(&self, ty: MonoType) -> TypeScheme { |
107 | 15 | let ty_vars = ty.free_vars(); |
108 | 15 | let env_vars = self.free_vars(); |
109 | | |
110 | | // Variables to generalize are those in ty but not in env |
111 | 15 | let gen_vars: Vec<_> = ty_vars |
112 | 15 | .into_iter() |
113 | 15 | .filter(|v| !env_vars11 .contains(v)) |
114 | 15 | .collect(); |
115 | | |
116 | 15 | TypeScheme { vars: gen_vars, ty } |
117 | 15 | } |
118 | | |
119 | | /// Instantiate a type scheme with fresh variables |
120 | 30 | pub fn instantiate(&self, scheme: &TypeScheme, gen: &mut TyVarGenerator) -> MonoType { |
121 | 30 | scheme.instantiate(gen) |
122 | 30 | } |
123 | | } |
124 | | |
125 | | impl Default for TypeEnv { |
126 | 1 | fn default() -> Self { |
127 | 1 | Self::new() |
128 | 1 | } |
129 | | } |
130 | | |
131 | | #[cfg(test)] |
132 | | #[allow(clippy::unwrap_used, clippy::panic)] |
133 | | mod tests { |
134 | | use super::*; |
135 | | use crate::middleend::types::TyVar; |
136 | | |
137 | | #[test] |
138 | 1 | fn test_env_lookup() { |
139 | 1 | let mut env = TypeEnv::new(); |
140 | 1 | env.bind("x", TypeScheme::mono(MonoType::Int)); |
141 | | |
142 | 1 | assert!(env.lookup("x").is_some()); |
143 | 1 | assert!(env.lookup("y").is_none()); |
144 | 1 | } |
145 | | |
146 | | #[test] |
147 | 1 | fn test_env_extend() { |
148 | 1 | let env = TypeEnv::new(); |
149 | 1 | let env2 = env.extend("x", TypeScheme::mono(MonoType::Bool)); |
150 | | |
151 | 1 | assert!(env.lookup("x").is_none()); |
152 | 1 | assert!(env2.lookup("x").is_some()); |
153 | 1 | } |
154 | | |
155 | | #[test] |
156 | 1 | fn test_generalization() { |
157 | 1 | let env = TypeEnv::new(); |
158 | 1 | let var = TyVar(0); |
159 | | |
160 | | // A type with a free variable |
161 | 1 | let ty = MonoType::Function( |
162 | 1 | Box::new(MonoType::Var(var.clone())), |
163 | 1 | Box::new(MonoType::Var(var.clone())), |
164 | 1 | ); |
165 | | |
166 | 1 | let scheme = env.generalize(ty); |
167 | | |
168 | | // The variable should be generalized |
169 | 1 | assert_eq!(scheme.vars.len(), 1); |
170 | 1 | assert!(scheme.vars.contains(&var)); |
171 | 1 | } |
172 | | |
173 | | #[test] |
174 | 1 | fn test_no_generalization_with_env_vars() { |
175 | 1 | let mut env = TypeEnv::new(); |
176 | 1 | let var = TyVar(0); |
177 | | |
178 | | // Add a binding with the same variable to the environment |
179 | 1 | env.bind("y", TypeScheme::mono(MonoType::Var(var.clone()))); |
180 | | |
181 | | // Try to generalize a type with the same variable |
182 | 1 | let ty = MonoType::Function(Box::new(MonoType::Var(var)), Box::new(MonoType::Int)); |
183 | | |
184 | 1 | let scheme = env.generalize(ty); |
185 | | |
186 | | // The variable should NOT be generalized (it's in the env) |
187 | 1 | assert_eq!(scheme.vars.len(), 0); |
188 | 1 | } |
189 | | |
190 | | #[test] |
191 | 1 | fn test_standard_env() { |
192 | 1 | let env = TypeEnv::standard(); |
193 | | |
194 | 1 | assert!(env.lookup("println").is_some()); |
195 | 1 | assert!(env.lookup("print").is_some()); |
196 | 1 | assert!(env.lookup("add").is_some()); |
197 | 1 | assert!(env.lookup("eq").is_some()); |
198 | 1 | } |
199 | | |
200 | | #[test] |
201 | 1 | fn test_default_env() { |
202 | 1 | let env = TypeEnv::default(); |
203 | 1 | assert!(env.lookup("nonexistent").is_none()); |
204 | 1 | assert_eq!(env.bindings.len(), 0); |
205 | 1 | } |
206 | | |
207 | | #[test] |
208 | 1 | fn test_multiple_bindings() { |
209 | 1 | let mut env = TypeEnv::new(); |
210 | 1 | env.bind("x", TypeScheme::mono(MonoType::Int)); |
211 | 1 | env.bind("y", TypeScheme::mono(MonoType::Bool)); |
212 | 1 | env.bind("z", TypeScheme::mono(MonoType::String)); |
213 | | |
214 | 1 | assert!(env.lookup("x").is_some()); |
215 | 1 | assert!(env.lookup("y").is_some()); |
216 | 1 | assert!(env.lookup("z").is_some()); |
217 | 1 | assert!(env.lookup("w").is_none()); |
218 | 1 | } |
219 | | |
220 | | #[test] |
221 | 1 | fn test_bind_overwrites() { |
222 | 1 | let mut env = TypeEnv::new(); |
223 | 1 | env.bind("x", TypeScheme::mono(MonoType::Int)); |
224 | 1 | env.bind("x", TypeScheme::mono(MonoType::Bool)); |
225 | | |
226 | 1 | let scheme = env.lookup("x").unwrap(); |
227 | 1 | match &scheme.ty { |
228 | 1 | MonoType::Bool => {}, // Expected |
229 | 0 | _ => panic!("Expected Bool type after overwrite"), |
230 | | } |
231 | 1 | } |
232 | | |
233 | | #[test] |
234 | 1 | fn test_env_clone() { |
235 | 1 | let mut env1 = TypeEnv::new(); |
236 | 1 | env1.bind("x", TypeScheme::mono(MonoType::Int)); |
237 | | |
238 | 1 | let env2 = env1.clone(); |
239 | 1 | assert!(env2.lookup("x").is_some()); |
240 | 1 | } |
241 | | |
242 | | #[test] |
243 | 1 | fn test_free_vars_empty() { |
244 | 1 | let env = TypeEnv::new(); |
245 | 1 | assert!(env.free_vars().is_empty()); |
246 | 1 | } |
247 | | |
248 | | #[test] |
249 | 1 | fn test_free_vars_with_schemes() { |
250 | 1 | let mut env = TypeEnv::new(); |
251 | 1 | let var1 = TyVar(1); |
252 | 1 | let var2 = TyVar(2); |
253 | | |
254 | | // Add a scheme with a free variable |
255 | 1 | let scheme1 = TypeScheme { |
256 | 1 | vars: vec![], |
257 | 1 | ty: MonoType::Var(var1.clone()), |
258 | 1 | }; |
259 | 1 | env.bind("x", scheme1); |
260 | | |
261 | | // Add a scheme with a bound variable |
262 | 1 | let scheme2 = TypeScheme { |
263 | 1 | vars: vec![var2.clone()], |
264 | 1 | ty: MonoType::Var(var2), |
265 | 1 | }; |
266 | 1 | env.bind("y", scheme2); |
267 | | |
268 | 1 | let free_vars = env.free_vars(); |
269 | 1 | assert!(free_vars.contains(&var1)); |
270 | 1 | assert!(!free_vars.contains(&TyVar(2))); // var2 is bound |
271 | 1 | } |
272 | | |
273 | | #[test] |
274 | 1 | fn test_generalize_empty_env() { |
275 | 1 | let env = TypeEnv::new(); |
276 | 1 | let var = TyVar(5); |
277 | 1 | let ty = MonoType::Var(var.clone()); |
278 | | |
279 | 1 | let scheme = env.generalize(ty); |
280 | 1 | assert_eq!(scheme.vars.len(), 1); |
281 | 1 | assert!(scheme.vars.contains(&var)); |
282 | 1 | } |
283 | | |
284 | | #[test] |
285 | 1 | fn test_generalize_complex_type() { |
286 | 1 | let env = TypeEnv::new(); |
287 | 1 | let var1 = TyVar(10); |
288 | 1 | let var2 = TyVar(11); |
289 | | |
290 | 1 | let ty = MonoType::Function( |
291 | 1 | Box::new(MonoType::Var(var1.clone())), |
292 | 1 | Box::new(MonoType::Function( |
293 | 1 | Box::new(MonoType::Var(var2.clone())), |
294 | 1 | Box::new(MonoType::Int), |
295 | 1 | )), |
296 | 1 | ); |
297 | | |
298 | 1 | let scheme = env.generalize(ty); |
299 | 1 | assert_eq!(scheme.vars.len(), 2); |
300 | 1 | assert!(scheme.vars.contains(&var1)); |
301 | 1 | assert!(scheme.vars.contains(&var2)); |
302 | 1 | } |
303 | | |
304 | | #[test] |
305 | 1 | fn test_instantiate_scheme() { |
306 | 1 | let env = TypeEnv::new(); |
307 | 1 | let mut gen = TyVarGenerator::new(); |
308 | | |
309 | | // Create a polymorphic scheme: forall a. a -> a |
310 | 1 | let var = TyVar(20); |
311 | 1 | let scheme = TypeScheme { |
312 | 1 | vars: vec![var.clone()], |
313 | 1 | ty: MonoType::Function( |
314 | 1 | Box::new(MonoType::Var(var.clone())), |
315 | 1 | Box::new(MonoType::Var(var)), |
316 | 1 | ), |
317 | 1 | }; |
318 | | |
319 | 1 | let instance = env.instantiate(&scheme, &mut gen); |
320 | | |
321 | | // Should get fresh variables |
322 | 1 | match instance { |
323 | 1 | MonoType::Function(arg, ret) => { |
324 | 1 | match (*arg, *ret) { |
325 | 1 | (MonoType::Var(v1), MonoType::Var(v2)) => { |
326 | 1 | assert_eq!(v1, v2); // Same fresh variable |
327 | 1 | assert_ne!(v1, TyVar(20)); // Different from original |
328 | | } |
329 | 0 | _ => panic!("Expected function with variable types"), |
330 | | } |
331 | | } |
332 | 0 | _ => panic!("Expected function type"), |
333 | | } |
334 | 1 | } |
335 | | |
336 | | #[test] |
337 | 1 | fn test_standard_env_function_types() { |
338 | 1 | let env = TypeEnv::standard(); |
339 | | |
340 | | // Test add function type |
341 | 1 | let add_scheme = env.lookup("add").unwrap(); |
342 | 1 | match &add_scheme.ty { |
343 | 1 | MonoType::Function(arg1, rest) => { |
344 | 1 | assert!(matches!0 (**arg1, MonoType::Int)); |
345 | 1 | match rest.as_ref() { |
346 | 1 | MonoType::Function(arg2, ret_type) => { |
347 | 1 | assert!(matches!0 (**arg2, MonoType::Int)); |
348 | 1 | assert!(matches!0 (**ret_type, MonoType::Int)); |
349 | | } |
350 | 0 | _ => panic!("Expected curried function type"), |
351 | | } |
352 | | } |
353 | 0 | _ => panic!("Expected function type for add"), |
354 | | } |
355 | | |
356 | | // Test print function type |
357 | 1 | let print_scheme = env.lookup("print").unwrap(); |
358 | 1 | match &print_scheme.ty { |
359 | 1 | MonoType::Function(arg, ret) => { |
360 | 1 | assert!(matches!0 (**arg, MonoType::String)); |
361 | 1 | assert!(matches!0 (**ret, MonoType::Unit)); |
362 | | } |
363 | 0 | _ => panic!("Expected function type for print"), |
364 | | } |
365 | 1 | } |
366 | | } |