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