1use std::collections::{HashMap, HashSet};
12use std::path::Path;
13
14use super::{Type, parse_type_str_strict};
15use crate::ast::{BinOp, Expr, FnBody, FnDef, Literal, Module, Pattern, Stmt, TopLevel, TypeDef};
16use crate::source::{canonicalize_path, find_module_file, parse_source};
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}
38
39#[derive(Debug)]
41pub struct TypeCheckResult {
42 pub errors: Vec<TypeError>,
43 pub fn_sigs: HashMap<String, (Vec<Type>, Type, Vec<String>)>,
46 pub memo_safe_types: HashSet<String>,
48}
49
50pub fn run_type_check(items: &[TopLevel]) -> Vec<TypeError> {
51 run_type_check_with_base(items, None)
52}
53
54pub fn run_type_check_with_base(items: &[TopLevel], base_dir: Option<&str>) -> Vec<TypeError> {
55 run_type_check_full(items, base_dir).errors
56}
57
58pub fn run_type_check_full(items: &[TopLevel], base_dir: Option<&str>) -> TypeCheckResult {
59 let mut checker = TypeChecker::new();
60 checker.check(items, base_dir);
61
62 let fn_sigs: HashMap<String, (Vec<Type>, Type, Vec<String>)> = checker
64 .fn_sigs
65 .iter()
66 .map(|(k, v)| {
67 (
68 k.clone(),
69 (v.params.clone(), v.ret.clone(), v.effects.clone()),
70 )
71 })
72 .collect();
73
74 let memo_safe_types = checker.compute_memo_safe_types(items);
76
77 TypeCheckResult {
78 errors: checker.errors,
79 fn_sigs,
80 memo_safe_types,
81 }
82}
83
84#[derive(Debug, Clone)]
89struct FnSig {
90 params: Vec<Type>,
91 ret: Type,
92 effects: Vec<String>,
93}
94
95#[derive(Debug, Clone)]
96struct ModuleSigCache {
97 fn_entries: Vec<(String, FnSig)>,
98 value_entries: Vec<(String, Type)>,
99 record_field_entries: Vec<(String, Type)>,
100 type_variants: Vec<(String, Vec<String>)>,
101}
102
103struct TypeChecker {
104 fn_sigs: HashMap<String, FnSig>,
105 module_sig_cache: HashMap<String, ModuleSigCache>,
106 value_members: HashMap<String, Type>,
107 record_field_types: HashMap<String, Type>,
111 type_variants: HashMap<String, Vec<String>>,
114 effect_aliases: HashMap<String, Vec<String>>,
116 globals: HashMap<String, Type>,
118 locals: HashMap<String, Type>,
120 errors: Vec<TypeError>,
121 current_fn_ret: Option<Type>,
123 current_fn_line: Option<usize>,
125}
126
127impl TypeChecker {
128 fn new() -> Self {
129 let mut type_variants = HashMap::new();
130 type_variants.insert(
131 "Result".to_string(),
132 vec!["Ok".to_string(), "Err".to_string()],
133 );
134 type_variants.insert(
135 "Option".to_string(),
136 vec!["Some".to_string(), "None".to_string()],
137 );
138
139 let mut tc = TypeChecker {
140 fn_sigs: HashMap::new(),
141 module_sig_cache: HashMap::new(),
142 value_members: HashMap::new(),
143 record_field_types: HashMap::new(),
144 type_variants,
145 effect_aliases: HashMap::new(),
146 globals: HashMap::new(),
147 locals: HashMap::new(),
148 errors: Vec::new(),
149 current_fn_ret: None,
150 current_fn_line: None,
151 };
152 tc.register_builtins();
153 tc
154 }
155
156 fn expand_effects(&self, effects: &[String]) -> Vec<String> {
158 let mut result = Vec::new();
159 for e in effects {
160 if let Some(expanded) = self.effect_aliases.get(e) {
161 result.extend(expanded.iter().cloned());
162 } else {
163 result.push(e.clone());
164 }
165 }
166 result
167 }
168
169 fn caller_has_effect(&self, caller_effects: &[String], required_effect: &str) -> bool {
171 let expanded_caller = self.expand_effects(caller_effects);
172 let expanded_required = self.expand_effects(&[required_effect.to_string()]);
174 expanded_required
175 .iter()
176 .all(|e| expanded_caller.contains(e))
177 }
178
179 fn error(&mut self, msg: impl Into<String>) {
180 let line = self.current_fn_line.unwrap_or(1);
181 self.errors.push(TypeError {
182 message: msg.into(),
183 line,
184 col: 0,
185 });
186 }
187
188 fn error_at_line(&mut self, line: usize, msg: impl Into<String>) {
189 self.errors.push(TypeError {
190 message: msg.into(),
191 line,
192 col: 0,
193 });
194 }
195
196 fn insert_sig(&mut self, name: &str, params: &[Type], ret: Type, effects: &[&str]) {
197 self.fn_sigs.insert(
198 name.to_string(),
199 FnSig {
200 params: params.to_vec(),
201 ret,
202 effects: effects.iter().map(|s| s.to_string()).collect(),
203 },
204 );
205 }
206
207 fn fn_type_from_sig(sig: &FnSig) -> Type {
208 Type::Fn(
209 sig.params.clone(),
210 Box::new(sig.ret.clone()),
211 sig.effects.clone(),
212 )
213 }
214
215 fn sig_from_callable_type(ty: &Type) -> Option<FnSig> {
216 match ty {
217 Type::Fn(params, ret, effects) => Some(FnSig {
218 params: params.clone(),
219 ret: *ret.clone(),
220 effects: effects.clone(),
221 }),
222 _ => None,
223 }
224 }
225
226 fn binding_type(&self, name: &str) -> Option<Type> {
227 self.locals
228 .get(name)
229 .or_else(|| self.globals.get(name))
230 .cloned()
231 }
232
233 pub(super) fn constraint_compatible(actual: &Type, expected: &Type) -> bool {
240 if matches!(actual, Type::Unknown) && !matches!(expected, Type::Unknown) {
241 return false;
242 }
243 actual.compatible(expected)
244 }
245}