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
93fn finalize_check_result(mut checker: TypeChecker, items: &[TopLevel]) -> TypeCheckResult {
94 let fn_sigs: HashMap<String, (Vec<Type>, Type, Vec<String>)> = checker
95 .fn_sigs
96 .iter()
97 .map(|(k, v)| {
98 (
99 k.clone(),
100 (v.params.clone(), v.ret.clone(), v.effects.clone()),
101 )
102 })
103 .collect();
104
105 let memo_safe_types = checker.compute_memo_safe_types(items);
106
107 check_module_effect_boundary(items, &mut checker.errors);
108
109 TypeCheckResult {
110 errors: checker.errors,
111 fn_sigs,
112 memo_safe_types,
113 unused_bindings: checker.unused_warnings,
114 }
115}
116
117fn check_module_effect_boundary(items: &[TopLevel], errors: &mut Vec<TypeError>) {
127 let Some(allowed) = items.iter().find_map(|i| match i {
128 TopLevel::Module(m) => m.effects.as_ref().map(|e| (e, m)),
129 _ => None,
130 }) else {
131 return;
132 };
133 let (allowed_list, module) = allowed;
134
135 let allowed_namespaces: HashSet<&str> = allowed_list
136 .iter()
137 .filter(|e| !e.contains('.'))
138 .map(|e| e.as_str())
139 .collect();
140 let allowed_methods: HashSet<&str> = allowed_list.iter().map(|e| e.as_str()).collect();
141
142 for item in items {
143 let TopLevel::FnDef(fd) = item else { continue };
144 for eff in &fd.effects {
145 let method = eff.node.as_str();
146 if allowed_methods.contains(method) {
147 continue;
148 }
149 if let Some((ns, _)) = method.split_once('.')
150 && allowed_namespaces.contains(ns)
151 {
152 continue;
153 }
154 errors.push(TypeError {
155 message: format!(
156 "module '{}' declared `effects [{}]` but '{}' uses '{}' which is not in the declared boundary",
157 module.name,
158 allowed_list.join(", "),
159 fd.name,
160 method
161 ),
162 line: eff.line,
163 col: 1,
164 secondary: module.effects_line.map(|l| TypeErrorSpan {
165 line: l,
166 col: 1,
167 label: "module effects declared here".to_string(),
168 }),
169 });
170 }
171 }
172}
173
174#[derive(Debug, Clone)]
179struct FnSig {
180 params: Vec<Type>,
181 ret: Type,
182 effects: Vec<String>,
183}
184
185struct TypeChecker {
186 fn_sigs: HashMap<String, FnSig>,
187 value_members: HashMap<String, Type>,
188 record_field_types: HashMap<String, Type>,
192 sig_aliases: HashMap<String, String>,
195 type_variants: HashMap<String, Vec<String>>,
198 globals: HashMap<String, Type>,
200 locals: HashMap<String, Type>,
202 errors: Vec<TypeError>,
203 current_fn_ret: Option<Type>,
205 current_fn_line: Option<usize>,
207 opaque_types: HashSet<String>,
209 used_names: HashSet<String>,
211 fn_bindings: Vec<(String, usize)>,
213 unused_warnings: Vec<(String, String, usize)>,
215 in_verify_trace_context: bool,
222}
223
224impl TypeChecker {
225 fn new() -> Self {
226 let mut type_variants = HashMap::new();
227 type_variants.insert(
228 "Result".to_string(),
229 vec!["Ok".to_string(), "Err".to_string()],
230 );
231 type_variants.insert(
232 "Option".to_string(),
233 vec!["Some".to_string(), "None".to_string()],
234 );
235
236 let mut tc = TypeChecker {
237 fn_sigs: HashMap::new(),
238 value_members: HashMap::new(),
239 record_field_types: HashMap::new(),
240 sig_aliases: HashMap::new(),
241 type_variants,
242 globals: HashMap::new(),
243 locals: HashMap::new(),
244 errors: Vec::new(),
245 current_fn_ret: None,
246 current_fn_line: None,
247 opaque_types: HashSet::new(),
248 used_names: HashSet::new(),
249 fn_bindings: Vec::new(),
250 unused_warnings: Vec::new(),
251 in_verify_trace_context: false,
252 };
253 tc.register_builtins();
254 tc
255 }
256
257 fn find_fn_sig(&self, key: &str) -> Option<&FnSig> {
260 self.fn_sigs
261 .get(key)
262 .or_else(|| self.sig_aliases.get(key).and_then(|c| self.fn_sigs.get(c)))
263 }
264
265 fn find_value_member(&self, key: &str) -> Option<&Type> {
266 self.value_members.get(key).or_else(|| {
267 self.sig_aliases
268 .get(key)
269 .and_then(|c| self.value_members.get(c))
270 })
271 }
272
273 fn find_record_field_type(&self, key: &str) -> Option<&Type> {
274 self.record_field_types.get(key).or_else(|| {
275 self.sig_aliases
276 .get(key)
277 .and_then(|c| self.record_field_types.get(c))
278 })
279 }
280
281 fn caller_has_effect(&self, caller_effects: &[String], required_effect: &str) -> bool {
285 caller_effects
286 .iter()
287 .any(|declared| crate::effects::effect_satisfies(declared, required_effect))
288 }
289
290 fn error(&mut self, msg: impl Into<String>) {
291 let line = self.current_fn_line.unwrap_or(1);
292 self.errors.push(TypeError {
293 message: msg.into(),
294 line,
295 col: 0,
296 secondary: None,
297 });
298 }
299
300 fn error_at_line(&mut self, line: usize, msg: impl Into<String>) {
301 self.errors.push(TypeError {
302 message: msg.into(),
303 line,
304 col: 0,
305 secondary: None,
306 });
307 }
308
309 fn insert_sig(&mut self, name: &str, params: &[Type], ret: Type, effects: &[&str]) {
310 self.fn_sigs.insert(
311 name.to_string(),
312 FnSig {
313 params: params.to_vec(),
314 ret,
315 effects: effects.iter().map(|s| s.to_string()).collect(),
316 },
317 );
318 }
319
320 fn fn_type_from_sig(sig: &FnSig) -> Type {
321 Type::Fn(
322 sig.params.clone(),
323 Box::new(sig.ret.clone()),
324 sig.effects.clone(),
325 )
326 }
327
328 fn sig_from_callable_type(ty: &Type) -> Option<FnSig> {
329 match ty {
330 Type::Fn(params, ret, effects) => Some(FnSig {
331 params: params.clone(),
332 ret: *ret.clone(),
333 effects: effects.clone(),
334 }),
335 _ => None,
336 }
337 }
338
339 fn binding_type(&self, name: &str) -> Option<Type> {
340 self.locals
341 .get(name)
342 .or_else(|| self.globals.get(name))
343 .cloned()
344 }
345
346 pub(super) fn constraint_compatible(actual: &Type, expected: &Type) -> bool {
351 let mut subst = HashMap::new();
352 Self::match_expected_type(actual, expected, &mut subst)
353 }
354
355 pub(super) fn match_expected_type(
356 actual: &Type,
357 expected: &Type,
358 subst: &mut HashMap<String, Type>,
359 ) -> bool {
360 match expected {
361 Type::Var(name) => Self::bind_expected_var(name, actual, subst),
362 Type::Invalid => false,
363 Type::Int => matches!(actual, Type::Int),
364 Type::Float => matches!(actual, Type::Float),
365 Type::Str => matches!(actual, Type::Str),
366 Type::Bool => matches!(actual, Type::Bool),
367 Type::Unit => matches!(actual, Type::Unit),
368 Type::Named(expected_name) => match actual {
369 Type::Named(actual_name) => {
370 actual_name == expected_name
371 || actual_name.ends_with(&format!(".{}", expected_name))
372 || expected_name.ends_with(&format!(".{}", actual_name))
373 }
374 _ => false,
375 },
376 Type::Option(expected_inner) => match actual {
377 Type::Option(actual_inner) => {
378 Self::match_expected_type(actual_inner, expected_inner, subst)
379 }
380 _ => false,
381 },
382 Type::List(expected_inner) => match actual {
383 Type::List(actual_inner) => {
384 Self::match_expected_type(actual_inner, expected_inner, subst)
385 }
386 _ => false,
387 },
388 Type::Vector(expected_inner) => match actual {
389 Type::Vector(actual_inner) => {
390 Self::match_expected_type(actual_inner, expected_inner, subst)
391 }
392 _ => false,
393 },
394 Type::Result(expected_ok, expected_err) => match actual {
395 Type::Result(actual_ok, actual_err) => {
396 Self::match_expected_type(actual_ok, expected_ok, subst)
397 && Self::match_expected_type(actual_err, expected_err, subst)
398 }
399 _ => false,
400 },
401 Type::Map(expected_k, expected_v) => match actual {
402 Type::Map(actual_k, actual_v) => {
403 Self::match_expected_type(actual_k, expected_k, subst)
404 && Self::match_expected_type(actual_v, expected_v, subst)
405 }
406 _ => false,
407 },
408 Type::Tuple(expected_items) => match actual {
409 Type::Tuple(actual_items) if actual_items.len() == expected_items.len() => {
410 actual_items.iter().zip(expected_items.iter()).all(
411 |(actual_item, expected_item)| {
412 Self::match_expected_type(actual_item, expected_item, subst)
413 },
414 )
415 }
416 _ => false,
417 },
418 Type::Fn(expected_params, expected_ret, expected_effects) => match actual {
419 Type::Fn(actual_params, actual_ret, actual_effects)
420 if actual_params.len() == expected_params.len() =>
421 {
422 actual_params.iter().zip(expected_params.iter()).all(
423 |(actual_param, expected_param)| {
424 Self::match_expected_type(actual_param, expected_param, subst)
425 },
426 ) && Self::match_expected_type(actual_ret, expected_ret, subst)
427 && actual_effects.iter().all(|actual| {
428 expected_effects
429 .iter()
430 .any(|expected| crate::effects::effect_satisfies(expected, actual))
431 })
432 }
433 _ => false,
434 },
435 }
436 }
437
438 fn bind_expected_var(name: &str, actual: &Type, subst: &mut HashMap<String, Type>) -> bool {
439 match actual {
440 Type::Var(actual_name) => return actual_name == name,
441 Type::Invalid => return false,
442 _ => {}
443 }
444 if let Some(bound) = subst.get(name).cloned() {
445 return Self::match_expected_type(actual, &bound, subst)
446 && Self::match_expected_type(&bound, actual, subst);
447 }
448 subst.insert(name.to_string(), actual.clone());
449 true
450 }
451
452 pub(super) fn instantiate_type(ty: &Type, subst: &HashMap<String, Type>) -> Type {
453 match ty {
454 Type::Var(name) => subst.get(name).cloned().unwrap_or_else(|| ty.clone()),
455 Type::Result(ok, err) => Type::Result(
456 Box::new(Self::instantiate_type(ok, subst)),
457 Box::new(Self::instantiate_type(err, subst)),
458 ),
459 Type::Option(inner) => Type::Option(Box::new(Self::instantiate_type(inner, subst))),
460 Type::List(inner) => Type::List(Box::new(Self::instantiate_type(inner, subst))),
461 Type::Vector(inner) => Type::Vector(Box::new(Self::instantiate_type(inner, subst))),
462 Type::Map(k, v) => Type::Map(
463 Box::new(Self::instantiate_type(k, subst)),
464 Box::new(Self::instantiate_type(v, subst)),
465 ),
466 Type::Tuple(items) => Type::Tuple(
467 items
468 .iter()
469 .map(|item| Self::instantiate_type(item, subst))
470 .collect(),
471 ),
472 Type::Fn(params, ret, effects) => Type::Fn(
473 params
474 .iter()
475 .map(|param| Self::instantiate_type(param, subst))
476 .collect(),
477 Box::new(Self::instantiate_type(ret, subst)),
478 effects.clone(),
479 ),
480 Type::Int
481 | Type::Float
482 | Type::Str
483 | Type::Bool
484 | Type::Unit
485 | Type::Invalid
486 | Type::Named(_) => ty.clone(),
487 }
488 }
489}