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
73 let fn_sigs: HashMap<String, (Vec<Type>, Type, Vec<String>)> = checker
75 .fn_sigs
76 .iter()
77 .map(|(k, v)| {
78 (
79 k.clone(),
80 (v.params.clone(), v.ret.clone(), v.effects.clone()),
81 )
82 })
83 .collect();
84
85 let memo_safe_types = checker.compute_memo_safe_types(items);
87
88 TypeCheckResult {
89 errors: checker.errors,
90 fn_sigs,
91 memo_safe_types,
92 unused_bindings: checker.unused_warnings,
93 }
94}
95
96#[derive(Debug, Clone)]
101struct FnSig {
102 params: Vec<Type>,
103 ret: Type,
104 effects: Vec<String>,
105}
106
107struct TypeChecker {
108 fn_sigs: HashMap<String, FnSig>,
109 value_members: HashMap<String, Type>,
110 record_field_types: HashMap<String, Type>,
114 sig_aliases: HashMap<String, String>,
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 value_members: HashMap::new(),
154 record_field_types: HashMap::new(),
155 sig_aliases: 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 find_fn_sig(&self, key: &str) -> Option<&FnSig> {
174 self.fn_sigs
175 .get(key)
176 .or_else(|| self.sig_aliases.get(key).and_then(|c| self.fn_sigs.get(c)))
177 }
178
179 fn find_value_member(&self, key: &str) -> Option<&Type> {
180 self.value_members.get(key).or_else(|| {
181 self.sig_aliases
182 .get(key)
183 .and_then(|c| self.value_members.get(c))
184 })
185 }
186
187 fn find_record_field_type(&self, key: &str) -> Option<&Type> {
188 self.record_field_types.get(key).or_else(|| {
189 self.sig_aliases
190 .get(key)
191 .and_then(|c| self.record_field_types.get(c))
192 })
193 }
194
195 fn caller_has_effect(&self, caller_effects: &[String], required_effect: &str) -> bool {
199 caller_effects
200 .iter()
201 .any(|declared| crate::effects::effect_satisfies(declared, required_effect))
202 }
203
204 fn error(&mut self, msg: impl Into<String>) {
205 let line = self.current_fn_line.unwrap_or(1);
206 self.errors.push(TypeError {
207 message: msg.into(),
208 line,
209 col: 0,
210 secondary: None,
211 });
212 }
213
214 fn error_at_line(&mut self, line: usize, msg: impl Into<String>) {
215 self.errors.push(TypeError {
216 message: msg.into(),
217 line,
218 col: 0,
219 secondary: None,
220 });
221 }
222
223 fn insert_sig(&mut self, name: &str, params: &[Type], ret: Type, effects: &[&str]) {
224 self.fn_sigs.insert(
225 name.to_string(),
226 FnSig {
227 params: params.to_vec(),
228 ret,
229 effects: effects.iter().map(|s| s.to_string()).collect(),
230 },
231 );
232 }
233
234 fn fn_type_from_sig(sig: &FnSig) -> Type {
235 Type::Fn(
236 sig.params.clone(),
237 Box::new(sig.ret.clone()),
238 sig.effects.clone(),
239 )
240 }
241
242 fn sig_from_callable_type(ty: &Type) -> Option<FnSig> {
243 match ty {
244 Type::Fn(params, ret, effects) => Some(FnSig {
245 params: params.clone(),
246 ret: *ret.clone(),
247 effects: effects.clone(),
248 }),
249 _ => None,
250 }
251 }
252
253 fn binding_type(&self, name: &str) -> Option<Type> {
254 self.locals
255 .get(name)
256 .or_else(|| self.globals.get(name))
257 .cloned()
258 }
259
260 pub(super) fn constraint_compatible(actual: &Type, expected: &Type) -> bool {
267 if matches!(actual, Type::Unknown) && !matches!(expected, Type::Unknown) {
268 return false;
269 }
270 actual.compatible(expected)
271 }
272}