use std::collections::BTreeMap;
use crate::ast::*;
use crate::builtin_signatures;
use super::union::apply_refinements;
pub(super) type InferredType = Option<TypeExpr>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Polarity {
Covariant,
Contravariant,
Invariant,
}
impl Polarity {
pub(super) fn compose(self, child: Variance) -> Polarity {
match (self, child) {
(_, Variance::Invariant) | (Polarity::Invariant, _) => Polarity::Invariant,
(p, Variance::Covariant) => p,
(Polarity::Covariant, Variance::Contravariant) => Polarity::Contravariant,
(Polarity::Contravariant, Variance::Contravariant) => Polarity::Covariant,
}
}
}
#[derive(Debug, Clone)]
pub(super) struct EnumDeclInfo {
pub(super) type_params: Vec<TypeParam>,
pub(super) variants: Vec<EnumVariant>,
}
#[derive(Debug, Clone)]
pub(super) struct TypeAliasInfo {
pub(super) type_params: Vec<TypeParam>,
pub(super) body: TypeExpr,
}
#[derive(Debug, Clone)]
pub(super) struct StructDeclInfo {
pub(super) type_params: Vec<TypeParam>,
pub(super) fields: Vec<StructField>,
}
#[derive(Debug, Clone)]
pub(super) struct InterfaceDeclInfo {
pub(super) type_params: Vec<TypeParam>,
pub(super) associated_types: Vec<(String, Option<TypeExpr>)>,
pub(super) methods: Vec<InterfaceMethod>,
}
#[derive(Debug, Clone)]
pub(super) struct TypeScope {
pub(super) vars: BTreeMap<String, InferredType>,
pub(super) functions: BTreeMap<String, FnSignature>,
pub(super) type_aliases: BTreeMap<String, TypeAliasInfo>,
pub(super) enums: BTreeMap<String, EnumDeclInfo>,
pub(super) interfaces: BTreeMap<String, InterfaceDeclInfo>,
pub(super) structs: BTreeMap<String, StructDeclInfo>,
pub(super) impl_methods: BTreeMap<String, Vec<ImplMethodSig>>,
pub(super) generic_type_params: std::collections::BTreeSet<String>,
pub(super) where_constraints: BTreeMap<String, String>,
pub(super) mutable_vars: std::collections::BTreeSet<String>,
pub(super) narrowed_vars: BTreeMap<String, InferredType>,
pub(super) nil_widenable_vars: BTreeMap<String, bool>,
pub(super) schema_bindings: BTreeMap<String, InferredType>,
pub(super) untyped_sources: BTreeMap<String, String>,
pub(super) unknown_ruled_out: BTreeMap<String, Vec<String>>,
pub(super) parent: Option<Box<TypeScope>>,
}
#[derive(Debug, Clone)]
pub(super) struct ImplMethodSig {
pub(super) name: String,
pub(super) param_count: usize,
pub(super) param_types: Vec<Option<TypeExpr>>,
pub(super) return_type: Option<TypeExpr>,
}
#[derive(Debug, Clone)]
pub(super) struct FnSignature {
pub(super) params: Vec<(String, InferredType)>,
pub(super) return_type: InferredType,
pub(super) type_param_names: Vec<String>,
pub(super) required_params: usize,
pub(super) where_clauses: Vec<(String, String)>,
pub(super) has_rest: bool,
}
impl TypeScope {
pub(super) fn new() -> Self {
let mut scope = Self {
vars: BTreeMap::new(),
functions: BTreeMap::new(),
type_aliases: BTreeMap::new(),
enums: BTreeMap::new(),
interfaces: BTreeMap::new(),
structs: BTreeMap::new(),
impl_methods: BTreeMap::new(),
generic_type_params: std::collections::BTreeSet::new(),
where_constraints: BTreeMap::new(),
mutable_vars: std::collections::BTreeSet::new(),
narrowed_vars: BTreeMap::new(),
nil_widenable_vars: BTreeMap::new(),
schema_bindings: BTreeMap::new(),
untyped_sources: BTreeMap::new(),
unknown_ruled_out: BTreeMap::new(),
parent: None,
};
scope.enums.insert(
"Result".into(),
EnumDeclInfo {
type_params: vec![
TypeParam {
name: "T".into(),
variance: Variance::Covariant,
},
TypeParam {
name: "E".into(),
variance: Variance::Covariant,
},
],
variants: vec![
EnumVariant {
name: "Ok".into(),
fields: vec![TypedParam {
name: "value".into(),
type_expr: Some(TypeExpr::Named("T".into())),
default_value: None,
rest: false,
}],
},
EnumVariant {
name: "Err".into(),
fields: vec![TypedParam {
name: "error".into(),
type_expr: Some(TypeExpr::Named("E".into())),
default_value: None,
rest: false,
}],
},
],
},
);
scope
}
pub(super) fn child(&self) -> Self {
Self {
vars: BTreeMap::new(),
functions: BTreeMap::new(),
type_aliases: BTreeMap::new(),
enums: BTreeMap::new(),
interfaces: BTreeMap::new(),
structs: BTreeMap::new(),
impl_methods: BTreeMap::new(),
generic_type_params: std::collections::BTreeSet::new(),
where_constraints: BTreeMap::new(),
mutable_vars: std::collections::BTreeSet::new(),
narrowed_vars: BTreeMap::new(),
nil_widenable_vars: BTreeMap::new(),
schema_bindings: BTreeMap::new(),
untyped_sources: BTreeMap::new(),
unknown_ruled_out: BTreeMap::new(),
parent: Some(Box::new(self.clone())),
}
}
pub(super) fn get_var(&self, name: &str) -> Option<&InferredType> {
self.vars
.get(name)
.or_else(|| self.parent.as_ref()?.get_var(name))
}
pub(super) fn add_unknown_ruled_out(&mut self, var_name: &str, type_name: &str) {
if !self.unknown_ruled_out.contains_key(var_name) {
let inherited = self.lookup_unknown_ruled_out(var_name);
self.unknown_ruled_out
.insert(var_name.to_string(), inherited);
}
let entry = self
.unknown_ruled_out
.get_mut(var_name)
.expect("just inserted");
if !entry.iter().any(|t| t == type_name) {
entry.push(type_name.to_string());
}
}
pub(super) fn lookup_unknown_ruled_out(&self, var_name: &str) -> Vec<String> {
if let Some(list) = self.unknown_ruled_out.get(var_name) {
list.clone()
} else if let Some(parent) = &self.parent {
parent.lookup_unknown_ruled_out(var_name)
} else {
Vec::new()
}
}
pub(super) fn collect_unknown_ruled_out(&self) -> BTreeMap<String, Vec<String>> {
let mut out: BTreeMap<String, Vec<String>> = BTreeMap::new();
self.collect_unknown_ruled_out_inner(&mut out);
out
}
fn collect_unknown_ruled_out_inner(&self, acc: &mut BTreeMap<String, Vec<String>>) {
if let Some(parent) = &self.parent {
parent.collect_unknown_ruled_out_inner(acc);
}
for (name, list) in &self.unknown_ruled_out {
acc.insert(name.clone(), list.clone());
}
}
pub(super) fn clear_unknown_ruled_out(&mut self, var_name: &str) {
self.unknown_ruled_out
.insert(var_name.to_string(), Vec::new());
}
pub(super) fn all_fn_names(&self) -> Vec<String> {
let mut names: Vec<String> = self.functions.keys().cloned().collect();
if let Some(parent) = &self.parent {
names.extend(parent.all_fn_names());
}
names
}
pub(super) fn all_struct_names(&self) -> Vec<String> {
let mut names: Vec<String> = self.structs.keys().cloned().collect();
if let Some(parent) = &self.parent {
names.extend(parent.all_struct_names());
}
names
}
pub(super) fn get_fn(&self, name: &str) -> Option<&FnSignature> {
self.functions
.get(name)
.or_else(|| self.parent.as_ref()?.get_fn(name))
}
pub(super) fn get_schema_binding(&self, name: &str) -> Option<&InferredType> {
self.schema_bindings
.get(name)
.or_else(|| self.parent.as_ref()?.get_schema_binding(name))
}
pub(super) fn resolve_type(&self, name: &str) -> Option<&TypeExpr> {
self.type_aliases
.get(name)
.map(|info| &info.body)
.or_else(|| self.parent.as_ref()?.resolve_type(name))
}
pub(super) fn resolve_type_alias(&self, name: &str) -> Option<&TypeAliasInfo> {
self.type_aliases
.get(name)
.or_else(|| self.parent.as_ref()?.resolve_type_alias(name))
}
pub(super) fn is_generic_type_param(&self, name: &str) -> bool {
self.generic_type_params.contains(name)
|| self
.parent
.as_ref()
.is_some_and(|p| p.is_generic_type_param(name))
}
pub(super) fn get_where_constraint(&self, type_param: &str) -> Option<&str> {
self.where_constraints
.get(type_param)
.map(|s| s.as_str())
.or_else(|| {
self.parent
.as_ref()
.and_then(|p| p.get_where_constraint(type_param))
})
}
pub(super) fn get_enum(&self, name: &str) -> Option<&EnumDeclInfo> {
self.enums
.get(name)
.or_else(|| self.parent.as_ref()?.get_enum(name))
}
pub(super) fn get_interface(&self, name: &str) -> Option<&InterfaceDeclInfo> {
self.interfaces
.get(name)
.or_else(|| self.parent.as_ref()?.get_interface(name))
}
pub(super) fn get_struct(&self, name: &str) -> Option<&StructDeclInfo> {
self.structs
.get(name)
.or_else(|| self.parent.as_ref()?.get_struct(name))
}
pub(super) fn get_impl_methods(&self, name: &str) -> Option<&Vec<ImplMethodSig>> {
self.impl_methods
.get(name)
.or_else(|| self.parent.as_ref()?.get_impl_methods(name))
}
pub(super) fn variance_of(&self, name: &str) -> Option<Vec<Variance>> {
if let Some(info) = self.get_enum(name) {
return Some(info.type_params.iter().map(|tp| tp.variance).collect());
}
if let Some(info) = self.get_struct(name) {
return Some(info.type_params.iter().map(|tp| tp.variance).collect());
}
if let Some(info) = self.get_interface(name) {
return Some(info.type_params.iter().map(|tp| tp.variance).collect());
}
None
}
pub(super) fn define_var(&mut self, name: &str, ty: InferredType) {
if is_discard_name(name) {
return;
}
self.vars.insert(name.to_string(), ty);
}
pub(super) fn define_var_mutable(&mut self, name: &str, ty: InferredType) {
if is_discard_name(name) {
return;
}
self.vars.insert(name.to_string(), ty);
self.mutable_vars.insert(name.to_string());
}
pub(super) fn mark_nil_widenable(&mut self, name: &str) {
if is_discard_name(name) {
return;
}
self.nil_widenable_vars.insert(name.to_string(), true);
}
pub(super) fn clear_nil_widenable(&mut self, name: &str) {
if is_discard_name(name) {
return;
}
self.nil_widenable_vars.insert(name.to_string(), false);
}
pub(super) fn is_nil_widenable(&self, name: &str) -> bool {
if let Some(enabled) = self.nil_widenable_vars.get(name) {
return *enabled;
}
self.parent
.as_ref()
.is_some_and(|p| p.is_nil_widenable(name))
}
pub(super) fn define_schema_binding(&mut self, name: &str, ty: InferredType) {
if is_discard_name(name) {
return;
}
self.schema_bindings.insert(name.to_string(), ty);
}
pub(super) fn is_untyped_source(&self, name: &str) -> Option<&str> {
if let Some(source) = self.untyped_sources.get(name) {
if source.is_empty() {
return None; }
return Some(source.as_str());
}
self.parent.as_ref()?.is_untyped_source(name)
}
pub(super) fn mark_untyped_source(&mut self, name: &str, source: &str) {
if is_discard_name(name) {
return;
}
self.untyped_sources
.insert(name.to_string(), source.to_string());
}
pub(super) fn clear_untyped_source(&mut self, name: &str) {
self.untyped_sources.insert(name.to_string(), String::new());
}
pub(super) fn is_mutable(&self, name: &str) -> bool {
self.mutable_vars.contains(name) || self.parent.as_ref().is_some_and(|p| p.is_mutable(name))
}
pub(super) fn define_fn(&mut self, name: &str, sig: FnSignature) {
self.functions.insert(name.to_string(), sig);
}
}
#[derive(Debug, Clone, Default)]
pub(super) struct Refinements {
pub(super) truthy: Vec<(String, InferredType)>,
pub(super) falsy: Vec<(String, InferredType)>,
pub(super) truthy_ruled_out: Vec<(String, String)>,
pub(super) falsy_ruled_out: Vec<(String, String)>,
}
impl Refinements {
pub(super) fn empty() -> Self {
Self::default()
}
pub(super) fn inverted(self) -> Self {
Self {
truthy: self.falsy,
falsy: self.truthy,
truthy_ruled_out: self.falsy_ruled_out,
falsy_ruled_out: self.truthy_ruled_out,
}
}
pub(super) fn apply_truthy(&self, scope: &mut TypeScope) {
apply_refinements(scope, &self.truthy);
for (var, ty) in &self.truthy_ruled_out {
scope.add_unknown_ruled_out(var, ty);
}
}
pub(super) fn apply_falsy(&self, scope: &mut TypeScope) {
apply_refinements(scope, &self.falsy);
for (var, ty) in &self.falsy_ruled_out {
scope.add_unknown_ruled_out(var, ty);
}
}
}
pub(super) fn builtin_return_type(name: &str) -> InferredType {
builtin_signatures::builtin_return_type(name)
}
pub(super) fn is_builtin(name: &str) -> bool {
builtin_signatures::is_builtin(name)
}