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