1use std::collections::{HashMap, HashSet};
12
13use super::{Type, parse_type_str_strict};
14use crate::ast::{
15 BinOp, Expr, FnDef, Literal, Module, Pattern, Spanned, Stmt, TailCallData, TopLevel, TypeDef,
16};
17
18mod builtins;
19mod exhaustiveness;
20mod flow;
21mod infer;
22mod memo;
23mod modules;
24
25#[cfg(test)]
26mod tests;
27
28#[derive(Debug, Clone)]
33pub struct TypeError {
34 pub message: String,
35 pub line: usize,
36 pub col: usize,
37 pub secondary: Option<TypeErrorSpan>,
39}
40
41#[derive(Debug, Clone)]
42pub struct TypeErrorSpan {
43 pub line: usize,
44 pub col: usize,
45 pub label: String,
46}
47
48#[derive(Debug)]
50pub struct TypeCheckResult {
51 pub errors: Vec<TypeError>,
52 pub fn_sigs: HashMap<String, (Vec<Type>, Type, Vec<String>)>,
55 pub memo_safe_types: HashSet<String>,
57 pub unused_bindings: Vec<(String, String, usize)>,
59}
60
61pub fn run_type_check(items: &[TopLevel]) -> Vec<TypeError> {
62 run_type_check_with_base(items, None)
63}
64
65pub fn run_type_check_with_base(items: &[TopLevel], base_dir: Option<&str>) -> Vec<TypeError> {
66 run_type_check_full(items, base_dir).errors
67}
68
69pub fn run_type_check_full(items: &[TopLevel], base_dir: Option<&str>) -> TypeCheckResult {
70 let mut checker = TypeChecker::new();
71 checker.check(items, base_dir);
72 finalize_check_result(checker, items)
73}
74
75pub fn run_type_check_with_loaded(
80 items: &[TopLevel],
81 loaded: &[crate::source::LoadedModule],
82) -> TypeCheckResult {
83 let mut checker = TypeChecker::new();
84 checker.check_with_loaded(items, loaded);
85 finalize_check_result(checker, items)
86}
87
88fn finalize_check_result(checker: TypeChecker, items: &[TopLevel]) -> TypeCheckResult {
89 let fn_sigs: HashMap<String, (Vec<Type>, Type, Vec<String>)> = checker
90 .fn_sigs
91 .iter()
92 .map(|(k, v)| {
93 (
94 k.clone(),
95 (v.params.clone(), v.ret.clone(), v.effects.clone()),
96 )
97 })
98 .collect();
99
100 let memo_safe_types = checker.compute_memo_safe_types(items);
101
102 TypeCheckResult {
103 errors: checker.errors,
104 fn_sigs,
105 memo_safe_types,
106 unused_bindings: checker.unused_warnings,
107 }
108}
109
110#[derive(Debug, Clone)]
115struct FnSig {
116 params: Vec<Type>,
117 ret: Type,
118 effects: Vec<String>,
119}
120
121struct TypeChecker {
122 fn_sigs: HashMap<String, FnSig>,
123 value_members: HashMap<String, Type>,
124 record_field_types: HashMap<String, Type>,
128 sig_aliases: HashMap<String, String>,
131 type_variants: HashMap<String, Vec<String>>,
134 globals: HashMap<String, Type>,
136 locals: HashMap<String, Type>,
138 errors: Vec<TypeError>,
139 current_fn_ret: Option<Type>,
141 current_fn_line: Option<usize>,
143 opaque_types: HashSet<String>,
145 used_names: HashSet<String>,
147 fn_bindings: Vec<(String, usize)>,
149 unused_warnings: Vec<(String, String, usize)>,
151}
152
153impl TypeChecker {
154 fn new() -> Self {
155 let mut type_variants = HashMap::new();
156 type_variants.insert(
157 "Result".to_string(),
158 vec!["Ok".to_string(), "Err".to_string()],
159 );
160 type_variants.insert(
161 "Option".to_string(),
162 vec!["Some".to_string(), "None".to_string()],
163 );
164
165 let mut tc = TypeChecker {
166 fn_sigs: HashMap::new(),
167 value_members: HashMap::new(),
168 record_field_types: HashMap::new(),
169 sig_aliases: HashMap::new(),
170 type_variants,
171 globals: HashMap::new(),
172 locals: HashMap::new(),
173 errors: Vec::new(),
174 current_fn_ret: None,
175 current_fn_line: None,
176 opaque_types: HashSet::new(),
177 used_names: HashSet::new(),
178 fn_bindings: Vec::new(),
179 unused_warnings: Vec::new(),
180 };
181 tc.register_builtins();
182 tc
183 }
184
185 fn find_fn_sig(&self, key: &str) -> Option<&FnSig> {
188 self.fn_sigs
189 .get(key)
190 .or_else(|| self.sig_aliases.get(key).and_then(|c| self.fn_sigs.get(c)))
191 }
192
193 fn find_value_member(&self, key: &str) -> Option<&Type> {
194 self.value_members.get(key).or_else(|| {
195 self.sig_aliases
196 .get(key)
197 .and_then(|c| self.value_members.get(c))
198 })
199 }
200
201 fn find_record_field_type(&self, key: &str) -> Option<&Type> {
202 self.record_field_types.get(key).or_else(|| {
203 self.sig_aliases
204 .get(key)
205 .and_then(|c| self.record_field_types.get(c))
206 })
207 }
208
209 fn caller_has_effect(&self, caller_effects: &[String], required_effect: &str) -> bool {
213 caller_effects
214 .iter()
215 .any(|declared| crate::effects::effect_satisfies(declared, required_effect))
216 }
217
218 fn error(&mut self, msg: impl Into<String>) {
219 let line = self.current_fn_line.unwrap_or(1);
220 self.errors.push(TypeError {
221 message: msg.into(),
222 line,
223 col: 0,
224 secondary: None,
225 });
226 }
227
228 fn error_at_line(&mut self, line: usize, msg: impl Into<String>) {
229 self.errors.push(TypeError {
230 message: msg.into(),
231 line,
232 col: 0,
233 secondary: None,
234 });
235 }
236
237 fn insert_sig(&mut self, name: &str, params: &[Type], ret: Type, effects: &[&str]) {
238 self.fn_sigs.insert(
239 name.to_string(),
240 FnSig {
241 params: params.to_vec(),
242 ret,
243 effects: effects.iter().map(|s| s.to_string()).collect(),
244 },
245 );
246 }
247
248 fn fn_type_from_sig(sig: &FnSig) -> Type {
249 Type::Fn(
250 sig.params.clone(),
251 Box::new(sig.ret.clone()),
252 sig.effects.clone(),
253 )
254 }
255
256 fn sig_from_callable_type(ty: &Type) -> Option<FnSig> {
257 match ty {
258 Type::Fn(params, ret, effects) => Some(FnSig {
259 params: params.clone(),
260 ret: *ret.clone(),
261 effects: effects.clone(),
262 }),
263 _ => None,
264 }
265 }
266
267 fn binding_type(&self, name: &str) -> Option<Type> {
268 self.locals
269 .get(name)
270 .or_else(|| self.globals.get(name))
271 .cloned()
272 }
273
274 pub(super) fn constraint_compatible(actual: &Type, expected: &Type) -> bool {
281 if matches!(actual, Type::Unknown) && !matches!(expected, Type::Unknown) {
282 return false;
283 }
284 actual.compatible(expected)
285 }
286}