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,
};
use crate::ir::{FnId, FnKey, SymbolTable, TypeId, TypeKey};
mod builtins;
mod check;
pub mod effect_classification;
pub mod effect_lifting;
mod exhaustiveness;
mod flow;
pub mod hostile_effects;
pub mod hostile_values;
mod infer;
mod modules;
pub mod oracle_subtypes;
pub mod proof_trust_header;
#[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 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_with_symbols(build_symbols_for_items(items, base_dir));
checker.check(items, base_dir);
finalize_check_result(checker, items)
}
fn build_symbols_for_items(items: &[TopLevel], base_dir: Option<&str>) -> SymbolTable {
let dep_modules = base_dir
.and_then(|base| {
TypeChecker::module_decl(items).and_then(|m| {
crate::source::load_module_tree(&m.depends, base)
.ok()
.map(|loaded| symbols_dep_modules_from_loaded(&loaded))
})
})
.unwrap_or_default();
SymbolTable::build(items, &dep_modules)
}
fn build_symbols_with_loaded(
items: &[TopLevel],
loaded: &[crate::source::LoadedModule],
) -> SymbolTable {
let dep_modules = symbols_dep_modules_from_loaded(loaded);
SymbolTable::build(items, &dep_modules)
}
fn symbols_dep_modules_from_loaded(
loaded: &[crate::source::LoadedModule],
) -> Vec<crate::codegen::ModuleInfo> {
loaded
.iter()
.map(|m| crate::codegen::ModuleInfo {
prefix: m.dep_name.clone(),
depends: Vec::new(),
type_defs: m
.items
.iter()
.filter_map(|i| match i {
TopLevel::TypeDef(td) => Some(td.clone()),
_ => None,
})
.collect(),
fn_defs: m
.items
.iter()
.filter_map(|i| match i {
TopLevel::FnDef(fd) => Some(fd.clone()),
_ => None,
})
.collect(),
analysis: None,
})
.collect()
}
pub fn run_type_check_with_loaded(
items: &[TopLevel],
loaded: &[crate::source::LoadedModule],
) -> TypeCheckResult {
let mut checker = TypeChecker::new_with_symbols(build_symbols_with_loaded(items, loaded));
checker.check_with_loaded(items, loaded);
finalize_check_result(checker, items)
}
pub fn run_type_check_full_self_host(
items: &[TopLevel],
base_dir: Option<&str>,
) -> TypeCheckResult {
let mut checker = TypeChecker::new_with_symbols(build_symbols_for_items(items, base_dir));
checker.self_host_mode = true;
checker.check(items, base_dir);
finalize_check_result(checker, items)
}
pub fn run_type_check_with_loaded_self_host(
items: &[TopLevel],
loaded: &[crate::source::LoadedModule],
) -> TypeCheckResult {
let mut checker = TypeChecker::new_with_symbols(build_symbols_with_loaded(items, loaded));
checker.self_host_mode = true;
checker.check_with_loaded(items, loaded);
finalize_check_result(checker, items)
}
fn finalize_check_result(mut checker: TypeChecker, items: &[TopLevel]) -> TypeCheckResult {
let entry_prefix = checker.current_module_prefix.clone();
let mut fn_sigs: HashMap<String, (Vec<Type>, Type, Vec<String>)> = HashMap::new();
let mut ordered_user: Vec<(FnId, &FnSig)> =
checker.fn_sigs.iter().map(|(id, sig)| (*id, sig)).collect();
ordered_user.sort_by_key(|(id, _)| id.0);
let mut bare_entry_owner: HashMap<String, FnId> = HashMap::new();
let mut bare_dep_owners: HashMap<String, FnId> = HashMap::new();
let mut bare_dep_ambiguous: HashSet<String> = HashSet::new();
for (id, _) in &ordered_user {
let entry = checker.symbol_table.fn_entry(*id);
let bare = entry.key.name.as_str();
if entry.module.is_entry() {
bare_entry_owner.insert(bare.to_string(), *id);
continue;
}
match bare_dep_owners.get(bare) {
None => {
bare_dep_owners.insert(bare.to_string(), *id);
}
Some(prior) if prior == id => {}
Some(_) => {
bare_dep_ambiguous.insert(bare.to_string());
}
}
}
for (id, sig) in &ordered_user {
let entry = checker.symbol_table.fn_entry(*id);
let canonical = if entry.module.is_entry() {
match entry_prefix.as_deref() {
Some(prefix) => crate::visibility::qualified_name(prefix, &entry.key.name),
None => entry.key.name.clone(),
}
} else {
entry.key.canonical()
};
let triple = (sig.params.clone(), sig.ret.clone(), sig.effects.clone());
fn_sigs.insert(canonical.clone(), triple.clone());
if entry.key.name == canonical {
continue;
}
let is_entry_owner = bare_entry_owner.get(&entry.key.name) == Some(id);
let mut emit_bare = false;
if entry.module.is_entry() {
emit_bare = is_entry_owner;
} else if !bare_entry_owner.contains_key(&entry.key.name)
&& !bare_dep_ambiguous.contains(&entry.key.name)
{
emit_bare = true;
}
if emit_bare {
fn_sigs.entry(entry.key.name.clone()).or_insert(triple);
}
}
for (k, sig) in &checker.extra_sigs {
fn_sigs
.entry(k.clone())
.or_insert_with(|| (sig.params.clone(), sig.ret.clone(), sig.effects.clone()));
}
check_module_effect_boundary(items, &mut checker.errors);
TypeCheckResult {
errors: checker.errors,
fn_sigs,
unused_bindings: checker.unused_warnings,
}
}
fn check_module_effect_boundary(items: &[TopLevel], errors: &mut Vec<TypeError>) {
let Some(allowed) = items.iter().find_map(|i| match i {
TopLevel::Module(m) => m.effects.as_ref().map(|e| (e, m)),
_ => None,
}) else {
return;
};
let (allowed_list, module) = allowed;
let allowed_namespaces: HashSet<&str> = allowed_list
.iter()
.filter(|e| !e.contains('.'))
.map(|e| e.as_str())
.collect();
let allowed_methods: HashSet<&str> = allowed_list.iter().map(|e| e.as_str()).collect();
for item in items {
let TopLevel::FnDef(fd) = item else { continue };
for eff in &fd.effects {
let method = eff.node.as_str();
if allowed_methods.contains(method) {
continue;
}
if let Some((ns, _)) = method.split_once('.')
&& allowed_namespaces.contains(ns)
{
continue;
}
errors.push(TypeError {
message: format!(
"module '{}' declared `effects [{}]` but '{}' uses '{}' which is not in the declared boundary",
module.name,
allowed_list.join(", "),
fd.name,
method
),
line: eff.line,
col: 1,
secondary: module.effects_line.map(|l| TypeErrorSpan {
line: l,
col: 1,
label: "module effects declared here".to_string(),
}),
});
}
}
}
#[derive(Debug, Clone)]
struct FnSig {
params: Vec<Type>,
ret: Type,
effects: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct RecordFieldKey {
pub(crate) type_name: String,
pub(crate) field_name: String,
}
impl RecordFieldKey {
pub(crate) fn new(type_name: impl Into<String>, field_name: impl Into<String>) -> Self {
Self {
type_name: type_name.into(),
field_name: field_name.into(),
}
}
}
#[derive(Debug, Clone)]
enum Resolution<T> {
Single(T),
Ambiguous(Vec<T>),
}
impl<T: Copy + PartialEq> Resolution<T> {
fn merge(&mut self, candidate: T) {
match self {
Resolution::Single(existing) if *existing == candidate => {}
Resolution::Single(existing) => {
let prior = *existing;
*self = Resolution::Ambiguous(vec![prior, candidate]);
}
Resolution::Ambiguous(seen) => {
if !seen.contains(&candidate) {
seen.push(candidate);
}
}
}
}
fn unambiguous(&self) -> Option<T> {
match self {
Resolution::Single(v) => Some(*v),
Resolution::Ambiguous(_) => None,
}
}
}
struct TypeChecker {
symbol_table: SymbolTable,
fn_sigs: HashMap<FnId, FnSig>,
extra_sigs: HashMap<String, FnSig>,
bare_fn_aliases: HashMap<String, Resolution<FnId>>,
bare_type_aliases: HashMap<String, Resolution<TypeId>>,
visible_fn_ids: HashSet<FnId>,
visible_type_ids: HashSet<TypeId>,
module_depends: HashMap<String, Vec<String>>,
value_members: HashMap<String, Type>,
record_field_types: HashMap<RecordFieldKey, Type>,
type_variants: HashMap<String, Vec<String>>,
current_module_prefix: Option<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>,
self_host_mode: bool,
used_names: HashSet<String>,
fn_bindings: Vec<(String, usize)>,
unused_warnings: Vec<(String, String, usize)>,
in_verify_trace_context: bool,
}
impl TypeChecker {
fn new_with_symbols(symbol_table: SymbolTable) -> 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 {
symbol_table,
fn_sigs: HashMap::new(),
extra_sigs: HashMap::new(),
bare_fn_aliases: HashMap::new(),
bare_type_aliases: HashMap::new(),
visible_fn_ids: HashSet::new(),
visible_type_ids: HashSet::new(),
module_depends: HashMap::new(),
value_members: HashMap::new(),
record_field_types: HashMap::new(),
type_variants,
current_module_prefix: None,
globals: HashMap::new(),
locals: HashMap::new(),
errors: Vec::new(),
current_fn_ret: None,
current_fn_line: None,
opaque_types: HashSet::new(),
self_host_mode: false,
used_names: HashSet::new(),
fn_bindings: Vec::new(),
unused_warnings: Vec::new(),
in_verify_trace_context: false,
};
tc.register_builtins();
tc
}
pub(crate) fn resolve_fn_id(&self, name: &str) -> Option<FnId> {
if let Some((prefix, n)) = name.rsplit_once('.') {
if let Some(id) = self.symbol_table.fn_id_of(&FnKey::in_module(prefix, n))
&& self.visible_fn_ids.contains(&id)
{
return Some(id);
}
if self.current_module_prefix.as_deref() == Some(prefix)
&& let Some(id) = self.symbol_table.fn_id_of(&FnKey::entry(n))
&& self.visible_fn_ids.contains(&id)
{
return Some(id);
}
}
if let Some(prefix) = self.current_module_prefix.as_deref()
&& let Some(id) = self.symbol_table.fn_id_of(&FnKey::in_module(prefix, name))
&& self.visible_fn_ids.contains(&id)
{
return Some(id);
}
if let Some(id) = self.symbol_table.fn_id_of(&FnKey::entry(name))
&& self.visible_fn_ids.contains(&id)
{
return Some(id);
}
self.bare_fn_aliases
.get(name)
.and_then(Resolution::unambiguous)
}
pub(crate) fn type_name_is_ambiguous(&self, name: &str) -> bool {
matches!(
self.bare_type_aliases.get(name),
Some(Resolution::Ambiguous(_))
)
}
pub(crate) fn ambiguous_type_candidates(&self, bare: &str) -> Vec<String> {
let Some(Resolution::Ambiguous(ids)) = self.bare_type_aliases.get(bare) else {
return Vec::new();
};
let mut out: Vec<String> = ids
.iter()
.map(|id| self.symbol_table.type_entry(*id).key.canonical())
.collect();
out.sort();
out
}
pub(super) fn report_ambiguous_named(&mut self, ty: &Type, line: usize, source_ctx: &str) {
let mut seen_ambig: HashSet<String> = HashSet::new();
let mut seen_private: HashSet<String> = HashSet::new();
self.collect_unresolved_into(ty, &mut seen_ambig, &mut seen_private);
for name in seen_ambig {
let candidates = self.ambiguous_type_candidates(&name);
if candidates.is_empty() {
continue;
}
let suggestion = match candidates.as_slice() {
[a] => a.clone(),
[a, b] => format!("`{}` or `{}`", a, b),
more => {
let last = more.last().expect("non-empty");
let head = &more[..more.len() - 1];
let joined = head
.iter()
.map(|c| format!("`{}`", c))
.collect::<Vec<_>>()
.join(", ");
format!("{}, or `{}`", joined, last)
}
};
self.error_at_line(
line,
format!(
"{source_ctx}: Ambiguous type name '{name}'; use {suggestion} to disambiguate"
),
);
}
for qualified in seen_private {
let (module, type_name) = qualified
.rsplit_once('.')
.map(|(m, t)| (m.to_string(), t.to_string()))
.expect("private qualified name always has a `.`");
self.error_at_line(
line,
format!(
"{source_ctx}: Type '{qualified}' is not exposed by module '{module}' — add '{type_name}' to its `exposes` list to import it",
),
);
}
}
fn collect_unresolved_into(
&self,
ty: &Type,
ambig: &mut HashSet<String>,
private: &mut HashSet<String>,
) {
match ty {
Type::Named { id: None, name } => {
if self.type_name_is_ambiguous(name) {
ambig.insert(name.clone());
} else if self.type_name_is_private_import(name) {
private.insert(name.clone());
}
}
Type::Named { .. }
| Type::Int
| Type::Float
| Type::Str
| Type::Bool
| Type::Unit
| Type::Var(_)
| Type::Invalid => {}
Type::Option(inner) | Type::List(inner) | Type::Vector(inner) => {
self.collect_unresolved_into(inner, ambig, private);
}
Type::Result(ok, err) => {
self.collect_unresolved_into(ok, ambig, private);
self.collect_unresolved_into(err, ambig, private);
}
Type::Map(k, v) => {
self.collect_unresolved_into(k, ambig, private);
self.collect_unresolved_into(v, ambig, private);
}
Type::Tuple(items) => {
for item in items {
self.collect_unresolved_into(item, ambig, private);
}
}
Type::Fn(params, ret, _) => {
for p in params {
self.collect_unresolved_into(p, ambig, private);
}
self.collect_unresolved_into(ret, ambig, private);
}
}
}
pub(super) fn reject_fn_in_type(
&mut self,
ty: &Type,
allow_top_level_param: bool,
line: usize,
source_ctx: &str,
) {
match ty {
Type::Fn(params, ret, _) => {
if !allow_top_level_param {
self.error_at_line(
line,
format!(
"{source_ctx}: a function type `{}` is not allowed here. Aver permits `Fn(...)` only as a direct function parameter type \
(e.g. `fn run(step: Fn(Int) -> Int) -> Int`); functions are first-class values only in call-argument position \
(`HttpServer.listen(port, handler)`). Return a concrete value and call the function at its use site.",
ty.display()
),
);
return;
}
for p in params {
self.reject_fn_in_type(p, false, line, source_ctx);
}
self.reject_fn_in_type(ret, false, line, source_ctx);
}
Type::Option(inner) | Type::List(inner) | Type::Vector(inner) => {
self.reject_fn_in_type(inner, false, line, source_ctx);
}
Type::Result(ok, err) => {
self.reject_fn_in_type(ok, false, line, source_ctx);
self.reject_fn_in_type(err, false, line, source_ctx);
}
Type::Map(k, v) => {
self.reject_fn_in_type(k, false, line, source_ctx);
self.reject_fn_in_type(v, false, line, source_ctx);
}
Type::Tuple(items) => {
for item in items {
self.reject_fn_in_type(item, false, line, source_ctx);
}
}
Type::Named { .. }
| Type::Int
| Type::Float
| Type::Str
| Type::Bool
| Type::Unit
| Type::Var(_)
| Type::Invalid => {}
}
}
pub(super) fn type_contains_fn(&self, ty: &Type) -> bool {
match ty {
Type::Fn(..) => true,
Type::Option(inner) | Type::List(inner) | Type::Vector(inner) => {
self.type_contains_fn(inner)
}
Type::Result(ok, err) => self.type_contains_fn(ok) || self.type_contains_fn(err),
Type::Map(k, v) => self.type_contains_fn(k) || self.type_contains_fn(v),
Type::Tuple(items) => items.iter().any(|i| self.type_contains_fn(i)),
Type::Named { .. }
| Type::Int
| Type::Float
| Type::Str
| Type::Bool
| Type::Unit
| Type::Var(_)
| Type::Invalid => false,
}
}
pub(super) fn merge_bare_fn_alias(&mut self, alias: String, id: FnId) {
self.bare_fn_aliases
.entry(alias)
.and_modify(|r| r.merge(id))
.or_insert(Resolution::Single(id));
}
pub(super) fn merge_bare_type_alias(&mut self, alias: String, id: TypeId) {
self.bare_type_aliases
.entry(alias)
.and_modify(|r| r.merge(id))
.or_insert(Resolution::Single(id));
}
pub(crate) fn resolve_type_id(&self, name: &str) -> Option<TypeId> {
if let Some((prefix, n)) = name.rsplit_once('.') {
if let Some(id) = self.symbol_table.type_id_of(&TypeKey::in_module(prefix, n))
&& self.visible_type_ids.contains(&id)
{
return Some(id);
}
if self.current_module_prefix.as_deref() == Some(prefix)
&& let Some(id) = self.symbol_table.type_id_of(&TypeKey::entry(n))
&& self.visible_type_ids.contains(&id)
{
return Some(id);
}
}
if let Some(prefix) = self.current_module_prefix.as_deref()
&& let Some(id) = self
.symbol_table
.type_id_of(&TypeKey::in_module(prefix, name))
&& self.visible_type_ids.contains(&id)
{
return Some(id);
}
if let Some(id) = self.symbol_table.type_id_of(&TypeKey::entry(name))
&& self.visible_type_ids.contains(&id)
{
return Some(id);
}
self.bare_type_aliases
.get(name)
.and_then(Resolution::unambiguous)
}
pub(crate) fn type_name_is_private_import(&self, name: &str) -> bool {
let Some((prefix, n)) = name.rsplit_once('.') else {
return false;
};
if self.current_module_prefix.as_deref() == Some(prefix) {
return false;
}
let Some(id) = self.symbol_table.type_id_of(&TypeKey::in_module(prefix, n)) else {
return false;
};
!self.visible_type_ids.contains(&id)
}
pub(crate) fn canonical_type_name(&self, name: &str) -> String {
match self.resolve_type_id(name) {
Some(id) => {
let entry = self.symbol_table.type_entry(id);
if entry.module.is_entry()
&& let Some(prefix) = self.current_module_prefix.as_deref()
{
crate::visibility::qualified_name(prefix, &entry.key.name)
} else {
entry.key.canonical()
}
}
None => name.to_string(),
}
}
fn find_fn_sig(&self, key: &str) -> Option<&FnSig> {
if let Some(id) = self.resolve_fn_id(key)
&& let Some(sig) = self.fn_sigs.get(&id)
{
return Some(sig);
}
if let Some(sig) = self.extra_sigs.get(key) {
return Some(sig);
}
let canonical = self.canonical_extra_key(key);
if canonical != key {
return self.extra_sigs.get(&canonical);
}
None
}
fn canonical_extra_key(&self, key: &str) -> String {
if let Some((head, tail)) = key.split_once('.') {
let canonical_type = self.canonical_type_name(head);
if canonical_type != head {
return format!("{}.{}", canonical_type, tail);
}
}
key.to_string()
}
fn find_value_member(&self, key: &str) -> Option<&Type> {
if let Some(v) = self.value_members.get(key) {
return Some(v);
}
let canonical = self.canonical_extra_key(key);
if canonical != key {
return self.value_members.get(&canonical);
}
None
}
fn find_record_field_type(&self, type_name: &str, field_name: &str) -> Option<&Type> {
let direct = RecordFieldKey::new(type_name, field_name);
if let Some(ty) = self.record_field_types.get(&direct) {
return Some(ty);
}
let canonical_type = self.canonical_type_name(type_name);
if canonical_type != type_name {
let canonical = RecordFieldKey::new(canonical_type, field_name);
return self.record_field_types.get(&canonical);
}
None
}
fn fields_for_type(&self, type_name: &str) -> Vec<(String, Type)> {
let canonical = self.canonical_type_name(type_name);
let canonical_ref: &str = canonical.as_str();
self.record_field_types
.iter()
.filter(|(k, _)| k.type_name == canonical_ref || k.type_name == type_name)
.map(|(k, v)| (k.field_name.clone(), v.clone()))
.collect()
}
fn has_record_schema(&self, type_name: &str) -> bool {
let canonical = self.canonical_type_name(type_name);
let canonical_ref: &str = canonical.as_str();
self.record_field_types
.keys()
.any(|k| k.type_name == canonical_ref || k.type_name == type_name)
}
pub(crate) fn variants_for(&self, name: &str) -> Option<&Vec<String>> {
if let Some(v) = self.type_variants.get(name) {
return Some(v);
}
let canonical = self.canonical_type_name(name);
if canonical != name {
return self.type_variants.get(&canonical);
}
None
}
pub(crate) fn has_variants_for(&self, name: &str) -> bool {
self.variants_for(name).is_some()
}
fn all_fn_sigs(&self) -> impl Iterator<Item = (String, &FnSig)> + '_ {
let from_user = self.fn_sigs.iter().map(|(id, sig)| {
let name = self.symbol_table.fn_entry(*id).key.canonical();
(name, sig)
});
let from_extra = self.extra_sigs.iter().map(|(k, sig)| (k.clone(), sig));
from_user.chain(from_extra)
}
fn fn_sig_contains_canonical(&self, canonical: &str) -> bool {
if let Some(id) = self.resolve_fn_id(canonical)
&& self.fn_sigs.contains_key(&id)
{
return true;
}
if self.extra_sigs.contains_key(canonical) {
return true;
}
let canonical_form = self.canonical_extra_key(canonical);
canonical_form != canonical && self.extra_sigs.contains_key(&canonical_form)
}
fn insert_fn_sig(&mut self, canonical: &str, sig: FnSig) {
match self.fn_id_for_canonical(canonical) {
Some(id) => {
self.fn_sigs.insert(id, sig);
self.visible_fn_ids.insert(id);
}
None => {
self.extra_sigs.insert(canonical.to_string(), sig);
}
}
}
fn fn_id_for_canonical(&self, name: &str) -> Option<FnId> {
if let Some((prefix, n)) = name.rsplit_once('.') {
if let Some(id) = self.symbol_table.fn_id_of(&FnKey::in_module(prefix, n)) {
return Some(id);
}
if self.current_module_prefix.as_deref() == Some(prefix)
&& let Some(id) = self.symbol_table.fn_id_of(&FnKey::entry(n))
{
return Some(id);
}
}
if let Some(prefix) = self.current_module_prefix.as_deref()
&& let Some(id) = self.symbol_table.fn_id_of(&FnKey::in_module(prefix, name))
{
return Some(id);
}
self.symbol_table.fn_id_of(&FnKey::entry(name))
}
fn type_id_for_canonical(&self, name: &str) -> Option<TypeId> {
if let Some((prefix, n)) = name.rsplit_once('.') {
if let Some(id) = self.symbol_table.type_id_of(&TypeKey::in_module(prefix, n)) {
return Some(id);
}
if self.current_module_prefix.as_deref() == Some(prefix)
&& let Some(id) = self.symbol_table.type_id_of(&TypeKey::entry(n))
{
return Some(id);
}
}
if let Some(prefix) = self.current_module_prefix.as_deref()
&& let Some(id) = self
.symbol_table
.type_id_of(&TypeKey::in_module(prefix, name))
{
return Some(id);
}
self.symbol_table.type_id_of(&TypeKey::entry(name))
}
fn mark_type_visible(&mut self, id: TypeId) {
self.visible_type_ids.insert(id);
}
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.extra_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 compatible(&self, actual: &Type, expected: &Type) -> bool {
let mut subst = HashMap::new();
Self::match_expected_type_inner(actual, expected, &mut subst, Some(self))
}
pub(super) fn match_expected_type(
actual: &Type,
expected: &Type,
subst: &mut HashMap<String, Type>,
) -> bool {
Self::match_expected_type_inner(actual, expected, subst, None)
}
pub(super) fn match_with(
&self,
actual: &Type,
expected: &Type,
subst: &mut HashMap<String, Type>,
) -> bool {
Self::match_expected_type_inner(actual, expected, subst, Some(self))
}
fn match_expected_type_inner(
actual: &Type,
expected: &Type,
subst: &mut HashMap<String, Type>,
checker: Option<&TypeChecker>,
) -> bool {
if matches!(actual, Type::Invalid) || matches!(expected, Type::Invalid) {
return true;
}
match expected {
Type::Var(name) => Self::bind_expected_var(name, actual, subst),
Type::Invalid => unreachable!("Type::Invalid handled by the early guard above"),
Type::Int => matches!(actual, Type::Int),
Type::Float => matches!(actual, Type::Float),
Type::Str => matches!(actual, Type::Str),
Type::Bool => matches!(actual, Type::Bool),
Type::Unit => matches!(actual, Type::Unit),
Type::Named {
id: expected_id,
name: expected_name,
} => match actual {
Type::Named {
id: actual_id,
name: actual_name,
} => {
let exp_id = *expected_id;
let act_id = *actual_id;
match (exp_id, act_id) {
(Some(e), Some(a)) => e == a,
(Some(_), None) | (None, Some(_)) => false,
(None, None) => {
let exp = checker
.map(|c| c.canonical_type_name(expected_name))
.unwrap_or_else(|| expected_name.clone());
let act = checker
.map(|c| c.canonical_type_name(actual_name))
.unwrap_or_else(|| actual_name.clone());
exp == act
}
}
}
_ => false,
},
Type::Option(expected_inner) => match actual {
Type::Option(actual_inner) => {
Self::match_expected_type_inner(actual_inner, expected_inner, subst, checker)
}
_ => false,
},
Type::List(expected_inner) => match actual {
Type::List(actual_inner) => {
Self::match_expected_type_inner(actual_inner, expected_inner, subst, checker)
}
_ => false,
},
Type::Vector(expected_inner) => match actual {
Type::Vector(actual_inner) => {
Self::match_expected_type_inner(actual_inner, expected_inner, subst, checker)
}
_ => false,
},
Type::Result(expected_ok, expected_err) => match actual {
Type::Result(actual_ok, actual_err) => {
Self::match_expected_type_inner(actual_ok, expected_ok, subst, checker)
&& Self::match_expected_type_inner(actual_err, expected_err, subst, checker)
}
_ => false,
},
Type::Map(expected_k, expected_v) => match actual {
Type::Map(actual_k, actual_v) => {
Self::match_expected_type_inner(actual_k, expected_k, subst, checker)
&& Self::match_expected_type_inner(actual_v, expected_v, subst, checker)
}
_ => false,
},
Type::Tuple(expected_items) => match actual {
Type::Tuple(actual_items) if actual_items.len() == expected_items.len() => {
actual_items.iter().zip(expected_items.iter()).all(
|(actual_item, expected_item)| {
Self::match_expected_type_inner(
actual_item,
expected_item,
subst,
checker,
)
},
)
}
_ => false,
},
Type::Fn(expected_params, expected_ret, expected_effects) => match actual {
Type::Fn(actual_params, actual_ret, actual_effects)
if actual_params.len() == expected_params.len() =>
{
actual_params.iter().zip(expected_params.iter()).all(
|(actual_param, expected_param)| {
Self::match_expected_type_inner(
actual_param,
expected_param,
subst,
checker,
)
},
) && Self::match_expected_type_inner(actual_ret, expected_ret, subst, checker)
&& actual_effects.iter().all(|actual| {
expected_effects
.iter()
.any(|expected| crate::effects::effect_satisfies(expected, actual))
})
}
_ => false,
},
}
}
fn bind_expected_var(name: &str, actual: &Type, subst: &mut HashMap<String, Type>) -> bool {
match actual {
Type::Var(actual_name) => return actual_name == name,
Type::Invalid => return true,
_ => {}
}
if let Some(bound) = subst.get(name).cloned() {
return Self::match_expected_type(actual, &bound, subst)
&& Self::match_expected_type(&bound, actual, subst);
}
if Self::type_contains_var(actual, name) {
return false;
}
subst.insert(name.to_string(), actual.clone());
true
}
fn type_contains_var(ty: &Type, name: &str) -> bool {
match ty {
Type::Var(other) => other == name,
Type::Int
| Type::Float
| Type::Str
| Type::Bool
| Type::Unit
| Type::Invalid
| Type::Named { .. } => false,
Type::Option(inner) | Type::List(inner) | Type::Vector(inner) => {
Self::type_contains_var(inner, name)
}
Type::Result(ok, err) => {
Self::type_contains_var(ok, name) || Self::type_contains_var(err, name)
}
Type::Map(k, v) => Self::type_contains_var(k, name) || Self::type_contains_var(v, name),
Type::Tuple(items) => items.iter().any(|t| Self::type_contains_var(t, name)),
Type::Fn(params, ret, _effects) => {
params.iter().any(|p| Self::type_contains_var(p, name))
|| Self::type_contains_var(ret, name)
}
}
}
pub(super) fn instantiate_type(ty: &Type, subst: &HashMap<String, Type>) -> Type {
match ty {
Type::Var(name) => subst.get(name).cloned().unwrap_or_else(|| ty.clone()),
Type::Result(ok, err) => Type::Result(
Box::new(Self::instantiate_type(ok, subst)),
Box::new(Self::instantiate_type(err, subst)),
),
Type::Option(inner) => Type::Option(Box::new(Self::instantiate_type(inner, subst))),
Type::List(inner) => Type::List(Box::new(Self::instantiate_type(inner, subst))),
Type::Vector(inner) => Type::Vector(Box::new(Self::instantiate_type(inner, subst))),
Type::Map(k, v) => Type::Map(
Box::new(Self::instantiate_type(k, subst)),
Box::new(Self::instantiate_type(v, subst)),
),
Type::Tuple(items) => Type::Tuple(
items
.iter()
.map(|item| Self::instantiate_type(item, subst))
.collect(),
),
Type::Fn(params, ret, effects) => Type::Fn(
params
.iter()
.map(|param| Self::instantiate_type(param, subst))
.collect(),
Box::new(Self::instantiate_type(ret, subst)),
effects.clone(),
),
Type::Int
| Type::Float
| Type::Str
| Type::Bool
| Type::Unit
| Type::Invalid
| Type::Named { .. } => ty.clone(),
}
}
}