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, Spanned, 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 pub secondary: Option<TypeErrorSpan>,
41}
42
43#[derive(Debug, Clone)]
44pub struct TypeErrorSpan {
45 pub line: usize,
46 pub col: usize,
47 pub label: String,
48}
49
50#[derive(Debug)]
52pub struct TypeCheckResult {
53 pub errors: Vec<TypeError>,
54 pub fn_sigs: HashMap<String, (Vec<Type>, Type, Vec<String>)>,
57 pub memo_safe_types: HashSet<String>,
59 pub unused_bindings: Vec<(String, String, usize)>,
61}
62
63pub fn run_type_check(items: &[TopLevel]) -> Vec<TypeError> {
64 run_type_check_with_base(items, None)
65}
66
67pub fn run_type_check_with_base(items: &[TopLevel], base_dir: Option<&str>) -> Vec<TypeError> {
68 run_type_check_full(items, base_dir).errors
69}
70
71pub fn run_type_check_full(items: &[TopLevel], base_dir: Option<&str>) -> TypeCheckResult {
72 let mut checker = TypeChecker::new();
73 checker.check(items, base_dir);
74
75 let fn_sigs: HashMap<String, (Vec<Type>, Type, Vec<String>)> = checker
77 .fn_sigs
78 .iter()
79 .map(|(k, v)| {
80 (
81 k.clone(),
82 (v.params.clone(), v.ret.clone(), v.effects.clone()),
83 )
84 })
85 .collect();
86
87 let memo_safe_types = checker.compute_memo_safe_types(items);
89
90 TypeCheckResult {
91 errors: checker.errors,
92 fn_sigs,
93 memo_safe_types,
94 unused_bindings: checker.unused_warnings,
95 }
96}
97
98#[derive(Debug, Clone)]
103struct FnSig {
104 params: Vec<Type>,
105 ret: Type,
106 effects: Vec<String>,
107}
108
109#[derive(Debug, Clone)]
110struct ModuleSigCache {
111 fn_entries: Vec<(String, FnSig)>,
112 value_entries: Vec<(String, Type)>,
113 record_field_entries: Vec<(String, Type)>,
114 type_variants: Vec<(String, Vec<String>)>,
115 opaque_types: Vec<String>,
116}
117
118struct TypeChecker {
119 fn_sigs: HashMap<String, FnSig>,
120 module_sig_cache: HashMap<String, ModuleSigCache>,
121 value_members: HashMap<String, Type>,
122 record_field_types: HashMap<String, Type>,
126 type_variants: HashMap<String, Vec<String>>,
129 globals: HashMap<String, Type>,
131 locals: HashMap<String, Type>,
133 errors: Vec<TypeError>,
134 current_fn_ret: Option<Type>,
136 current_fn_line: Option<usize>,
138 opaque_types: HashSet<String>,
140 used_names: HashSet<String>,
142 fn_bindings: Vec<(String, usize)>,
144 unused_warnings: Vec<(String, String, usize)>,
146}
147
148impl TypeChecker {
149 fn new() -> Self {
150 let mut type_variants = HashMap::new();
151 type_variants.insert(
152 "Result".to_string(),
153 vec!["Ok".to_string(), "Err".to_string()],
154 );
155 type_variants.insert(
156 "Option".to_string(),
157 vec!["Some".to_string(), "None".to_string()],
158 );
159
160 let mut tc = TypeChecker {
161 fn_sigs: HashMap::new(),
162 module_sig_cache: HashMap::new(),
163 value_members: HashMap::new(),
164 record_field_types: HashMap::new(),
165 type_variants,
166 globals: HashMap::new(),
167 locals: HashMap::new(),
168 errors: Vec::new(),
169 current_fn_ret: None,
170 current_fn_line: None,
171 opaque_types: HashSet::new(),
172 used_names: HashSet::new(),
173 fn_bindings: Vec::new(),
174 unused_warnings: Vec::new(),
175 };
176 tc.register_builtins();
177 tc
178 }
179
180 fn caller_has_effect(&self, caller_effects: &[String], required_effect: &str) -> bool {
182 caller_effects
183 .iter()
184 .any(|declared| crate::effects::effect_satisfies(declared, required_effect))
185 }
186
187 fn error(&mut self, msg: impl Into<String>) {
188 let line = self.current_fn_line.unwrap_or(1);
189 self.errors.push(TypeError {
190 message: msg.into(),
191 line,
192 col: 0,
193 secondary: None,
194 });
195 }
196
197 fn error_at_line(&mut self, line: usize, msg: impl Into<String>) {
198 self.errors.push(TypeError {
199 message: msg.into(),
200 line,
201 col: 0,
202 secondary: None,
203 });
204 }
205
206 fn insert_sig(&mut self, name: &str, params: &[Type], ret: Type, effects: &[&str]) {
207 self.fn_sigs.insert(
208 name.to_string(),
209 FnSig {
210 params: params.to_vec(),
211 ret,
212 effects: effects.iter().map(|s| s.to_string()).collect(),
213 },
214 );
215 }
216
217 fn fn_type_from_sig(sig: &FnSig) -> Type {
218 Type::Fn(
219 sig.params.clone(),
220 Box::new(sig.ret.clone()),
221 sig.effects.clone(),
222 )
223 }
224
225 fn sig_from_callable_type(ty: &Type) -> Option<FnSig> {
226 match ty {
227 Type::Fn(params, ret, effects) => Some(FnSig {
228 params: params.clone(),
229 ret: *ret.clone(),
230 effects: effects.clone(),
231 }),
232 _ => None,
233 }
234 }
235
236 fn binding_type(&self, name: &str) -> Option<Type> {
237 self.locals
238 .get(name)
239 .or_else(|| self.globals.get(name))
240 .cloned()
241 }
242
243 pub(super) fn constraint_compatible(actual: &Type, expected: &Type) -> bool {
250 if matches!(actual, Type::Unknown) && !matches!(expected, Type::Unknown) {
251 return false;
252 }
253 actual.compatible(expected)
254 }
255}