1use std::collections::{HashMap, HashSet};
12use std::path::Path;
13
14use super::{Type, parse_type_str_strict};
15use crate::ast::{BinOp, Expr, FnDef, Literal, Module, Pattern, Stmt, TopLevel, TypeDef};
16use crate::source::{
17 canonicalize_path, find_module_file, parse_source, require_module_declaration,
18};
19
20mod builtins;
21mod exhaustiveness;
22mod flow;
23mod infer;
24mod memo;
25mod modules;
26
27#[cfg(test)]
28mod tests;
29
30#[derive(Debug, Clone)]
35pub struct TypeError {
36 pub message: String,
37 pub line: usize,
38 pub col: usize,
39}
40
41#[derive(Debug)]
43pub struct TypeCheckResult {
44 pub errors: Vec<TypeError>,
45 pub fn_sigs: HashMap<String, (Vec<Type>, Type, Vec<String>)>,
48 pub memo_safe_types: HashSet<String>,
50 pub unused_bindings: Vec<(String, String, usize)>,
52}
53
54pub fn run_type_check(items: &[TopLevel]) -> Vec<TypeError> {
55 run_type_check_with_base(items, None)
56}
57
58pub fn run_type_check_with_base(items: &[TopLevel], base_dir: Option<&str>) -> Vec<TypeError> {
59 run_type_check_full(items, base_dir).errors
60}
61
62pub fn run_type_check_full(items: &[TopLevel], base_dir: Option<&str>) -> TypeCheckResult {
63 let mut checker = TypeChecker::new();
64 checker.check(items, base_dir);
65
66 let fn_sigs: HashMap<String, (Vec<Type>, Type, Vec<String>)> = checker
68 .fn_sigs
69 .iter()
70 .map(|(k, v)| {
71 (
72 k.clone(),
73 (v.params.clone(), v.ret.clone(), v.effects.clone()),
74 )
75 })
76 .collect();
77
78 let memo_safe_types = checker.compute_memo_safe_types(items);
80
81 TypeCheckResult {
82 errors: checker.errors,
83 fn_sigs,
84 memo_safe_types,
85 unused_bindings: checker.unused_warnings,
86 }
87}
88
89#[derive(Debug, Clone)]
94struct FnSig {
95 params: Vec<Type>,
96 ret: Type,
97 effects: Vec<String>,
98}
99
100#[derive(Debug, Clone)]
101struct ModuleSigCache {
102 fn_entries: Vec<(String, FnSig)>,
103 value_entries: Vec<(String, Type)>,
104 record_field_entries: Vec<(String, Type)>,
105 type_variants: Vec<(String, Vec<String>)>,
106 opaque_types: Vec<String>,
107}
108
109struct TypeChecker {
110 fn_sigs: HashMap<String, FnSig>,
111 module_sig_cache: HashMap<String, ModuleSigCache>,
112 value_members: HashMap<String, Type>,
113 record_field_types: HashMap<String, Type>,
117 type_variants: HashMap<String, Vec<String>>,
120 globals: HashMap<String, Type>,
122 locals: HashMap<String, Type>,
124 errors: Vec<TypeError>,
125 current_fn_ret: Option<Type>,
127 current_fn_line: Option<usize>,
129 opaque_types: HashSet<String>,
131 used_names: HashSet<String>,
133 fn_bindings: Vec<(String, usize)>,
135 unused_warnings: Vec<(String, String, usize)>,
137}
138
139impl TypeChecker {
140 fn new() -> Self {
141 let mut type_variants = HashMap::new();
142 type_variants.insert(
143 "Result".to_string(),
144 vec!["Ok".to_string(), "Err".to_string()],
145 );
146 type_variants.insert(
147 "Option".to_string(),
148 vec!["Some".to_string(), "None".to_string()],
149 );
150
151 let mut tc = TypeChecker {
152 fn_sigs: HashMap::new(),
153 module_sig_cache: HashMap::new(),
154 value_members: HashMap::new(),
155 record_field_types: HashMap::new(),
156 type_variants,
157 globals: HashMap::new(),
158 locals: HashMap::new(),
159 errors: Vec::new(),
160 current_fn_ret: None,
161 current_fn_line: None,
162 opaque_types: HashSet::new(),
163 used_names: HashSet::new(),
164 fn_bindings: Vec::new(),
165 unused_warnings: Vec::new(),
166 };
167 tc.register_builtins();
168 tc
169 }
170
171 fn caller_has_effect(&self, caller_effects: &[String], required_effect: &str) -> bool {
173 caller_effects
174 .iter()
175 .any(|declared| crate::effects::effect_satisfies(declared, required_effect))
176 }
177
178 fn error(&mut self, msg: impl Into<String>) {
179 let line = self.current_fn_line.unwrap_or(1);
180 self.errors.push(TypeError {
181 message: msg.into(),
182 line,
183 col: 0,
184 });
185 }
186
187 fn error_at_line(&mut self, line: usize, msg: impl Into<String>) {
188 self.errors.push(TypeError {
189 message: msg.into(),
190 line,
191 col: 0,
192 });
193 }
194
195 fn insert_sig(&mut self, name: &str, params: &[Type], ret: Type, effects: &[&str]) {
196 self.fn_sigs.insert(
197 name.to_string(),
198 FnSig {
199 params: params.to_vec(),
200 ret,
201 effects: effects.iter().map(|s| s.to_string()).collect(),
202 },
203 );
204 }
205
206 fn fn_type_from_sig(sig: &FnSig) -> Type {
207 Type::Fn(
208 sig.params.clone(),
209 Box::new(sig.ret.clone()),
210 sig.effects.clone(),
211 )
212 }
213
214 fn sig_from_callable_type(ty: &Type) -> Option<FnSig> {
215 match ty {
216 Type::Fn(params, ret, effects) => Some(FnSig {
217 params: params.clone(),
218 ret: *ret.clone(),
219 effects: effects.clone(),
220 }),
221 _ => None,
222 }
223 }
224
225 fn binding_type(&self, name: &str) -> Option<Type> {
226 self.locals
227 .get(name)
228 .or_else(|| self.globals.get(name))
229 .cloned()
230 }
231
232 pub(super) fn constraint_compatible(actual: &Type, expected: &Type) -> bool {
239 if matches!(actual, Type::Unknown) && !matches!(expected, Type::Unknown) {
240 return false;
241 }
242 actual.compatible(expected)
243 }
244}