use std::collections::{HashMap, HashSet};
use super::{Type, parse_type_str_strict};
use crate::ast::{
BinOp, Expr, FnDef, Literal, Module, Pattern, Spanned, Stmt, TailCallData, TopLevel, TypeDef,
};
mod builtins;
mod exhaustiveness;
mod flow;
mod infer;
mod memo;
mod modules;
#[cfg(test)]
mod tests;
#[derive(Debug, Clone)]
pub struct TypeError {
pub message: String,
pub line: usize,
pub col: usize,
pub secondary: Option<TypeErrorSpan>,
}
#[derive(Debug, Clone)]
pub struct TypeErrorSpan {
pub line: usize,
pub col: usize,
pub label: String,
}
#[derive(Debug)]
pub struct TypeCheckResult {
pub errors: Vec<TypeError>,
pub fn_sigs: HashMap<String, (Vec<Type>, Type, Vec<String>)>,
pub memo_safe_types: HashSet<String>,
pub unused_bindings: Vec<(String, String, usize)>,
}
pub fn run_type_check(items: &[TopLevel]) -> Vec<TypeError> {
run_type_check_with_base(items, None)
}
pub fn run_type_check_with_base(items: &[TopLevel], base_dir: Option<&str>) -> Vec<TypeError> {
run_type_check_full(items, base_dir).errors
}
pub fn run_type_check_full(items: &[TopLevel], base_dir: Option<&str>) -> TypeCheckResult {
let mut checker = TypeChecker::new();
checker.check(items, base_dir);
let fn_sigs: HashMap<String, (Vec<Type>, Type, Vec<String>)> = checker
.fn_sigs
.iter()
.map(|(k, v)| {
(
k.clone(),
(v.params.clone(), v.ret.clone(), v.effects.clone()),
)
})
.collect();
let memo_safe_types = checker.compute_memo_safe_types(items);
TypeCheckResult {
errors: checker.errors,
fn_sigs,
memo_safe_types,
unused_bindings: checker.unused_warnings,
}
}
#[derive(Debug, Clone)]
struct FnSig {
params: Vec<Type>,
ret: Type,
effects: Vec<String>,
}
struct TypeChecker {
fn_sigs: HashMap<String, FnSig>,
value_members: HashMap<String, Type>,
record_field_types: HashMap<String, Type>,
sig_aliases: HashMap<String, String>,
type_variants: HashMap<String, Vec<String>>,
globals: HashMap<String, Type>,
locals: HashMap<String, Type>,
errors: Vec<TypeError>,
current_fn_ret: Option<Type>,
current_fn_line: Option<usize>,
opaque_types: HashSet<String>,
used_names: HashSet<String>,
fn_bindings: Vec<(String, usize)>,
unused_warnings: Vec<(String, String, usize)>,
}
impl TypeChecker {
fn new() -> Self {
let mut type_variants = HashMap::new();
type_variants.insert(
"Result".to_string(),
vec!["Ok".to_string(), "Err".to_string()],
);
type_variants.insert(
"Option".to_string(),
vec!["Some".to_string(), "None".to_string()],
);
let mut tc = TypeChecker {
fn_sigs: HashMap::new(),
value_members: HashMap::new(),
record_field_types: HashMap::new(),
sig_aliases: HashMap::new(),
type_variants,
globals: HashMap::new(),
locals: HashMap::new(),
errors: Vec::new(),
current_fn_ret: None,
current_fn_line: None,
opaque_types: HashSet::new(),
used_names: HashSet::new(),
fn_bindings: Vec::new(),
unused_warnings: Vec::new(),
};
tc.register_builtins();
tc
}
fn find_fn_sig(&self, key: &str) -> Option<&FnSig> {
self.fn_sigs
.get(key)
.or_else(|| self.sig_aliases.get(key).and_then(|c| self.fn_sigs.get(c)))
}
fn find_value_member(&self, key: &str) -> Option<&Type> {
self.value_members.get(key).or_else(|| {
self.sig_aliases
.get(key)
.and_then(|c| self.value_members.get(c))
})
}
fn find_record_field_type(&self, key: &str) -> Option<&Type> {
self.record_field_types.get(key).or_else(|| {
self.sig_aliases
.get(key)
.and_then(|c| self.record_field_types.get(c))
})
}
fn caller_has_effect(&self, caller_effects: &[String], required_effect: &str) -> bool {
caller_effects
.iter()
.any(|declared| crate::effects::effect_satisfies(declared, required_effect))
}
fn error(&mut self, msg: impl Into<String>) {
let line = self.current_fn_line.unwrap_or(1);
self.errors.push(TypeError {
message: msg.into(),
line,
col: 0,
secondary: None,
});
}
fn error_at_line(&mut self, line: usize, msg: impl Into<String>) {
self.errors.push(TypeError {
message: msg.into(),
line,
col: 0,
secondary: None,
});
}
fn insert_sig(&mut self, name: &str, params: &[Type], ret: Type, effects: &[&str]) {
self.fn_sigs.insert(
name.to_string(),
FnSig {
params: params.to_vec(),
ret,
effects: effects.iter().map(|s| s.to_string()).collect(),
},
);
}
fn fn_type_from_sig(sig: &FnSig) -> Type {
Type::Fn(
sig.params.clone(),
Box::new(sig.ret.clone()),
sig.effects.clone(),
)
}
fn sig_from_callable_type(ty: &Type) -> Option<FnSig> {
match ty {
Type::Fn(params, ret, effects) => Some(FnSig {
params: params.clone(),
ret: *ret.clone(),
effects: effects.clone(),
}),
_ => None,
}
}
fn binding_type(&self, name: &str) -> Option<Type> {
self.locals
.get(name)
.or_else(|| self.globals.get(name))
.cloned()
}
pub(super) fn constraint_compatible(actual: &Type, expected: &Type) -> bool {
if matches!(actual, Type::Unknown) && !matches!(expected, Type::Unknown) {
return false;
}
actual.compatible(expected)
}
}