1use std::collections::{HashMap, HashSet};
11
12use super::{Type, parse_type_str_strict};
13use crate::ast::{
14 BinOp, Expr, FnDef, Literal, Module, Pattern, Spanned, Stmt, TailCallData, TopLevel, TypeDef,
15};
16
17mod builtins;
18pub mod effect_classification;
19pub mod effect_lifting;
20mod exhaustiveness;
21mod flow;
22pub mod hostile_effects;
23pub mod hostile_values;
24mod infer;
25mod memo;
26mod modules;
27pub mod oracle_subtypes;
28pub mod proof_trust_header;
29
30#[cfg(test)]
31mod tests;
32
33#[derive(Debug, Clone)]
38pub struct TypeError {
39 pub message: String,
40 pub line: usize,
41 pub col: usize,
42 pub secondary: Option<TypeErrorSpan>,
44}
45
46#[derive(Debug, Clone)]
47pub struct TypeErrorSpan {
48 pub line: usize,
49 pub col: usize,
50 pub label: String,
51}
52
53#[derive(Debug)]
55pub struct TypeCheckResult {
56 pub errors: Vec<TypeError>,
57 pub fn_sigs: HashMap<String, (Vec<Type>, Type, Vec<String>)>,
60 pub memo_safe_types: HashSet<String>,
62 pub unused_bindings: Vec<(String, String, usize)>,
64}
65
66pub fn run_type_check(items: &[TopLevel]) -> Vec<TypeError> {
67 run_type_check_with_base(items, None)
68}
69
70pub fn run_type_check_with_base(items: &[TopLevel], base_dir: Option<&str>) -> Vec<TypeError> {
71 run_type_check_full(items, base_dir).errors
72}
73
74pub fn run_type_check_full(items: &[TopLevel], base_dir: Option<&str>) -> TypeCheckResult {
75 let mut checker = TypeChecker::new();
76 checker.check(items, base_dir);
77 finalize_check_result(checker, items)
78}
79
80pub fn run_type_check_with_loaded(
85 items: &[TopLevel],
86 loaded: &[crate::source::LoadedModule],
87) -> TypeCheckResult {
88 let mut checker = TypeChecker::new();
89 checker.check_with_loaded(items, loaded);
90 finalize_check_result(checker, items)
91}
92
93pub fn run_type_check_full_self_host(
101 items: &[TopLevel],
102 base_dir: Option<&str>,
103) -> TypeCheckResult {
104 let mut checker = TypeChecker::new();
105 checker.self_host_mode = true;
106 checker.check(items, base_dir);
107 finalize_check_result(checker, items)
108}
109
110pub fn run_type_check_with_loaded_self_host(
113 items: &[TopLevel],
114 loaded: &[crate::source::LoadedModule],
115) -> TypeCheckResult {
116 let mut checker = TypeChecker::new();
117 checker.self_host_mode = true;
118 checker.check_with_loaded(items, loaded);
119 finalize_check_result(checker, items)
120}
121
122fn finalize_check_result(mut checker: TypeChecker, items: &[TopLevel]) -> TypeCheckResult {
123 let fn_sigs: HashMap<String, (Vec<Type>, Type, Vec<String>)> = checker
124 .fn_sigs
125 .iter()
126 .map(|(k, v)| {
127 (
128 k.clone(),
129 (v.params.clone(), v.ret.clone(), v.effects.clone()),
130 )
131 })
132 .collect();
133
134 let memo_safe_types = checker.compute_memo_safe_types(items);
135
136 check_module_effect_boundary(items, &mut checker.errors);
137
138 TypeCheckResult {
139 errors: checker.errors,
140 fn_sigs,
141 memo_safe_types,
142 unused_bindings: checker.unused_warnings,
143 }
144}
145
146fn check_module_effect_boundary(items: &[TopLevel], errors: &mut Vec<TypeError>) {
156 let Some(allowed) = items.iter().find_map(|i| match i {
157 TopLevel::Module(m) => m.effects.as_ref().map(|e| (e, m)),
158 _ => None,
159 }) else {
160 return;
161 };
162 let (allowed_list, module) = allowed;
163
164 let allowed_namespaces: HashSet<&str> = allowed_list
165 .iter()
166 .filter(|e| !e.contains('.'))
167 .map(|e| e.as_str())
168 .collect();
169 let allowed_methods: HashSet<&str> = allowed_list.iter().map(|e| e.as_str()).collect();
170
171 for item in items {
172 let TopLevel::FnDef(fd) = item else { continue };
173 for eff in &fd.effects {
174 let method = eff.node.as_str();
175 if allowed_methods.contains(method) {
176 continue;
177 }
178 if let Some((ns, _)) = method.split_once('.')
179 && allowed_namespaces.contains(ns)
180 {
181 continue;
182 }
183 errors.push(TypeError {
184 message: format!(
185 "module '{}' declared `effects [{}]` but '{}' uses '{}' which is not in the declared boundary",
186 module.name,
187 allowed_list.join(", "),
188 fd.name,
189 method
190 ),
191 line: eff.line,
192 col: 1,
193 secondary: module.effects_line.map(|l| TypeErrorSpan {
194 line: l,
195 col: 1,
196 label: "module effects declared here".to_string(),
197 }),
198 });
199 }
200 }
201}
202
203#[derive(Debug, Clone)]
208struct FnSig {
209 params: Vec<Type>,
210 ret: Type,
211 effects: Vec<String>,
212}
213
214struct TypeChecker {
215 fn_sigs: HashMap<String, FnSig>,
216 value_members: HashMap<String, Type>,
217 record_field_types: HashMap<String, Type>,
221 sig_aliases: HashMap<String, String>,
224 type_variants: HashMap<String, Vec<String>>,
227 globals: HashMap<String, Type>,
229 locals: HashMap<String, Type>,
231 errors: Vec<TypeError>,
232 current_fn_ret: Option<Type>,
234 current_fn_line: Option<usize>,
236 opaque_types: HashSet<String>,
238 self_host_mode: bool,
252 used_names: HashSet<String>,
254 fn_bindings: Vec<(String, usize)>,
256 unused_warnings: Vec<(String, String, usize)>,
258 in_verify_trace_context: bool,
265}
266
267impl TypeChecker {
268 fn new() -> Self {
269 let mut type_variants = HashMap::new();
270 type_variants.insert(
271 "Result".to_string(),
272 vec!["Ok".to_string(), "Err".to_string()],
273 );
274 type_variants.insert(
275 "Option".to_string(),
276 vec!["Some".to_string(), "None".to_string()],
277 );
278
279 let mut tc = TypeChecker {
280 fn_sigs: HashMap::new(),
281 value_members: HashMap::new(),
282 record_field_types: HashMap::new(),
283 sig_aliases: HashMap::new(),
284 type_variants,
285 globals: HashMap::new(),
286 locals: HashMap::new(),
287 errors: Vec::new(),
288 current_fn_ret: None,
289 current_fn_line: None,
290 opaque_types: HashSet::new(),
291 self_host_mode: false,
292 used_names: HashSet::new(),
293 fn_bindings: Vec::new(),
294 unused_warnings: Vec::new(),
295 in_verify_trace_context: false,
296 };
297 tc.register_builtins();
298 tc
299 }
300
301 fn find_fn_sig(&self, key: &str) -> Option<&FnSig> {
304 self.fn_sigs
305 .get(key)
306 .or_else(|| self.sig_aliases.get(key).and_then(|c| self.fn_sigs.get(c)))
307 }
308
309 fn find_value_member(&self, key: &str) -> Option<&Type> {
310 self.value_members.get(key).or_else(|| {
311 self.sig_aliases
312 .get(key)
313 .and_then(|c| self.value_members.get(c))
314 })
315 }
316
317 fn find_record_field_type(&self, key: &str) -> Option<&Type> {
318 self.record_field_types.get(key).or_else(|| {
319 self.sig_aliases
320 .get(key)
321 .and_then(|c| self.record_field_types.get(c))
322 })
323 }
324
325 fn caller_has_effect(&self, caller_effects: &[String], required_effect: &str) -> bool {
329 caller_effects
330 .iter()
331 .any(|declared| crate::effects::effect_satisfies(declared, required_effect))
332 }
333
334 fn error(&mut self, msg: impl Into<String>) {
335 let line = self.current_fn_line.unwrap_or(1);
336 self.errors.push(TypeError {
337 message: msg.into(),
338 line,
339 col: 0,
340 secondary: None,
341 });
342 }
343
344 fn error_at_line(&mut self, line: usize, msg: impl Into<String>) {
345 self.errors.push(TypeError {
346 message: msg.into(),
347 line,
348 col: 0,
349 secondary: None,
350 });
351 }
352
353 fn insert_sig(&mut self, name: &str, params: &[Type], ret: Type, effects: &[&str]) {
354 self.fn_sigs.insert(
355 name.to_string(),
356 FnSig {
357 params: params.to_vec(),
358 ret,
359 effects: effects.iter().map(|s| s.to_string()).collect(),
360 },
361 );
362 }
363
364 fn fn_type_from_sig(sig: &FnSig) -> Type {
365 Type::Fn(
366 sig.params.clone(),
367 Box::new(sig.ret.clone()),
368 sig.effects.clone(),
369 )
370 }
371
372 fn sig_from_callable_type(ty: &Type) -> Option<FnSig> {
373 match ty {
374 Type::Fn(params, ret, effects) => Some(FnSig {
375 params: params.clone(),
376 ret: *ret.clone(),
377 effects: effects.clone(),
378 }),
379 _ => None,
380 }
381 }
382
383 fn binding_type(&self, name: &str) -> Option<Type> {
384 self.locals
385 .get(name)
386 .or_else(|| self.globals.get(name))
387 .cloned()
388 }
389
390 pub(super) fn constraint_compatible(actual: &Type, expected: &Type) -> bool {
395 let mut subst = HashMap::new();
396 Self::match_expected_type(actual, expected, &mut subst)
397 }
398
399 pub(super) fn match_expected_type(
400 actual: &Type,
401 expected: &Type,
402 subst: &mut HashMap<String, Type>,
403 ) -> bool {
404 match expected {
405 Type::Var(name) => Self::bind_expected_var(name, actual, subst),
406 Type::Invalid => false,
407 Type::Int => matches!(actual, Type::Int),
408 Type::Float => matches!(actual, Type::Float),
409 Type::Str => matches!(actual, Type::Str),
410 Type::Bool => matches!(actual, Type::Bool),
411 Type::Unit => matches!(actual, Type::Unit),
412 Type::Named(expected_name) => match actual {
413 Type::Named(actual_name) => {
414 actual_name == expected_name
415 || actual_name.ends_with(&format!(".{}", expected_name))
416 || expected_name.ends_with(&format!(".{}", actual_name))
417 }
418 _ => false,
419 },
420 Type::Option(expected_inner) => match actual {
421 Type::Option(actual_inner) => {
422 Self::match_expected_type(actual_inner, expected_inner, subst)
423 }
424 _ => false,
425 },
426 Type::List(expected_inner) => match actual {
427 Type::List(actual_inner) => {
428 Self::match_expected_type(actual_inner, expected_inner, subst)
429 }
430 _ => false,
431 },
432 Type::Vector(expected_inner) => match actual {
433 Type::Vector(actual_inner) => {
434 Self::match_expected_type(actual_inner, expected_inner, subst)
435 }
436 _ => false,
437 },
438 Type::Result(expected_ok, expected_err) => match actual {
439 Type::Result(actual_ok, actual_err) => {
440 Self::match_expected_type(actual_ok, expected_ok, subst)
441 && Self::match_expected_type(actual_err, expected_err, subst)
442 }
443 _ => false,
444 },
445 Type::Map(expected_k, expected_v) => match actual {
446 Type::Map(actual_k, actual_v) => {
447 Self::match_expected_type(actual_k, expected_k, subst)
448 && Self::match_expected_type(actual_v, expected_v, subst)
449 }
450 _ => false,
451 },
452 Type::Tuple(expected_items) => match actual {
453 Type::Tuple(actual_items) if actual_items.len() == expected_items.len() => {
454 actual_items.iter().zip(expected_items.iter()).all(
455 |(actual_item, expected_item)| {
456 Self::match_expected_type(actual_item, expected_item, subst)
457 },
458 )
459 }
460 _ => false,
461 },
462 Type::Fn(expected_params, expected_ret, expected_effects) => match actual {
463 Type::Fn(actual_params, actual_ret, actual_effects)
464 if actual_params.len() == expected_params.len() =>
465 {
466 actual_params.iter().zip(expected_params.iter()).all(
467 |(actual_param, expected_param)| {
468 Self::match_expected_type(actual_param, expected_param, subst)
469 },
470 ) && Self::match_expected_type(actual_ret, expected_ret, subst)
471 && actual_effects.iter().all(|actual| {
472 expected_effects
473 .iter()
474 .any(|expected| crate::effects::effect_satisfies(expected, actual))
475 })
476 }
477 _ => false,
478 },
479 }
480 }
481
482 fn bind_expected_var(name: &str, actual: &Type, subst: &mut HashMap<String, Type>) -> bool {
483 match actual {
484 Type::Var(actual_name) => return actual_name == name,
485 Type::Invalid => return false,
486 _ => {}
487 }
488 if let Some(bound) = subst.get(name).cloned() {
489 return Self::match_expected_type(actual, &bound, subst)
490 && Self::match_expected_type(&bound, actual, subst);
491 }
492 subst.insert(name.to_string(), actual.clone());
493 true
494 }
495
496 pub(super) fn instantiate_type(ty: &Type, subst: &HashMap<String, Type>) -> Type {
497 match ty {
498 Type::Var(name) => subst.get(name).cloned().unwrap_or_else(|| ty.clone()),
499 Type::Result(ok, err) => Type::Result(
500 Box::new(Self::instantiate_type(ok, subst)),
501 Box::new(Self::instantiate_type(err, subst)),
502 ),
503 Type::Option(inner) => Type::Option(Box::new(Self::instantiate_type(inner, subst))),
504 Type::List(inner) => Type::List(Box::new(Self::instantiate_type(inner, subst))),
505 Type::Vector(inner) => Type::Vector(Box::new(Self::instantiate_type(inner, subst))),
506 Type::Map(k, v) => Type::Map(
507 Box::new(Self::instantiate_type(k, subst)),
508 Box::new(Self::instantiate_type(v, subst)),
509 ),
510 Type::Tuple(items) => Type::Tuple(
511 items
512 .iter()
513 .map(|item| Self::instantiate_type(item, subst))
514 .collect(),
515 ),
516 Type::Fn(params, ret, effects) => Type::Fn(
517 params
518 .iter()
519 .map(|param| Self::instantiate_type(param, subst))
520 .collect(),
521 Box::new(Self::instantiate_type(ret, subst)),
522 effects.clone(),
523 ),
524 Type::Int
525 | Type::Float
526 | Type::Str
527 | Type::Bool
528 | Type::Unit
529 | Type::Invalid
530 | Type::Named(_) => ty.clone(),
531 }
532 }
533}