use std::collections::BTreeMap;
use super::expr::MethodCallSite;
use super::{Inferer, narrowing};
use crate::compiler_error::CompilerFailure;
use crate::{ChainPart, ExprId, ExprKind, Ident, Type, TypedExprKind};
impl Inferer<'_> {
pub(super) fn report_evolving_read(&mut self, ident: &Ident, span: crate::Span) {
let declaration = self
.visible_local(&ident.name)
.map(|entry| entry.decl_span)
.or_else(|| {
self.top_symbols
.get(&ident.name)
.map(|entry| entry.declaration_span)
});
let message = format!("cannot infer the type of `{}` at this read", ident.name);
let help = vec![format!(
"annotate its type, as in `{}: T[] = []` or `let {}: T | undefined;`",
ident.name, ident.name
)];
if let Some(declaration) = declaration
&& !self
.diagnostics
.iter()
.any(|diagnostic| diagnostic.span == declaration && diagnostic.message == message)
{
self.error_with_help(declaration, message.clone(), help.clone());
}
self.error_with_help(span, message, help);
}
pub(super) fn evolving_storage_type(&self, name: &Ident, inferred: Type) -> Type {
if !self.evolving_bindings.contains(&name.span) {
return inferred;
}
if matches!(inferred, Type::Array(_)) {
Type::Array(Box::new(Type::Unknown))
} else {
Type::Unknown
}
}
pub(super) fn evolving_binding_path(&self, name: &Ident) -> Option<narrowing::ReferencePath> {
let (span, binding) = if let Some(entry) = self.visible_local(&name.name) {
(
entry.decl_span,
narrowing::BindingId::Local {
name: name.name.clone(),
decl_scope: entry.decl_scope,
},
)
} else {
let entry = self.top_symbols.get(&name.name)?;
(
entry.declaration_span,
narrowing::BindingId::Global(entry.mangled_name.clone()),
)
};
self.evolving_bindings
.contains(&span)
.then(|| narrowing::ReferencePath::root(binding))
}
pub(super) fn is_evolving_path(&self, path: &narrowing::ReferencePath) -> bool {
if !path.chain.is_empty() {
return false;
}
match &path.root {
narrowing::BindingId::Local { name, decl_scope } => {
self.visible_local(name).is_some_and(|entry| {
entry.decl_scope == *decl_scope
&& self.evolving_bindings.contains(&entry.decl_span)
})
}
narrowing::BindingId::Global(mangled) => self.top_symbols.values().any(|entry| {
&entry.mangled_name == mangled
&& self.evolving_bindings.contains(&entry.declaration_span)
}),
narrowing::BindingId::This => false,
}
}
pub(super) fn narrow_evolving_binding(
&mut self,
name: &Ident,
ty: Type,
) -> Result<(), CompilerFailure> {
let Some(path) = self.evolving_binding_path(name) else {
return Ok(());
};
let evolving_array =
matches!(&ty, Type::Array(element) if matches!(element.as_ref(), Type::Never));
self.install_assignment_narrowing(path.clone(), name.clone(), ty, name.span)?;
self.record_evolving_type(
&path,
if evolving_array {
narrowing::EvolvingType::Array
} else {
narrowing::EvolvingType::Value
},
);
Ok(())
}
pub(super) fn evolving_assignment_type(
&self,
name: &Ident,
source: Option<ExprId>,
) -> Result<Option<narrowing::EvolvingType>, CompilerFailure> {
if self.evolving_binding_path(name).is_none() {
return Ok(None);
}
let Some(source) = source else {
return Ok(Some(narrowing::EvolvingType::Value));
};
let expression = self.ast.try_expr(source).map_err(super::arena_failure)?;
let is_empty_literal = matches!(&expression.kind,
ExprKind::ArrayLiteral { elements } if elements.is_empty());
Ok(Some(if is_empty_literal {
narrowing::EvolvingType::Array
} else {
narrowing::EvolvingType::Value
}))
}
pub(super) fn record_evolving_type(
&mut self,
path: &narrowing::ReferencePath,
evolving: narrowing::EvolvingType,
) {
let Some(view) = self
.narrow_scopes
.last_mut()
.and_then(|env| env.get_mut(path))
else {
return;
};
view.evolving = Some(evolving);
let view = view.clone();
for writes in &mut self.clause_write_scopes {
writes.record_evolving(path.clone(), view.clone());
}
}
pub(super) fn record_evolving_array_growth(
&mut self,
name: &Ident,
ty: Type,
) -> Result<(), CompilerFailure> {
let Some(path) = self.evolving_binding_path(name) else {
return Ok(());
};
let evolving = self
.lookup_narrowed_view(&path)
.and_then(|view| view.evolving)
.unwrap_or(narrowing::EvolvingType::Array);
self.install_assignment_narrowing(path.clone(), name.clone(), ty, name.span)?;
self.record_evolving_type(&path, evolving);
Ok(())
}
pub(super) fn evolving_array_receiver(
&self,
receiver: ExprId,
) -> Result<Option<Ident>, CompilerFailure> {
let ExprKind::Identifier(name) = &self
.ast
.try_expr(receiver)
.map_err(super::arena_failure)?
.kind
else {
return Ok(None);
};
Ok(self
.evolving_binding_path(name)
.and_then(|path| self.lookup_narrowed_view(&path))
.filter(|view| {
matches!(
view.evolving,
Some(narrowing::EvolvingType::Array | narrowing::EvolvingType::UnresolvedArray)
)
})
.filter(|view| array_element(&view.narrowed_ty).is_some())
.map(|_| name.clone()))
}
pub(super) fn evolving_array_element(&self, binding: &Ident) -> Option<Type> {
self.evolving_binding_path(binding)
.and_then(|path| self.lookup_narrowed_view(&path))
.filter(|view| {
matches!(
view.evolving,
Some(narrowing::EvolvingType::Array | narrowing::EvolvingType::UnresolvedArray)
)
})
.and_then(|view| array_element(&view.narrowed_ty))
}
pub(super) fn infer_array_operation_receiver(
&mut self,
receiver: ExprId,
) -> Result<(ExprId, Type), CompilerFailure> {
if let Some(name) = self.evolving_array_receiver(receiver)? {
let span = name.span;
let (kind, ty) = self.resolve_ident(name, span)?;
let typed = self.push_synthetic_expr(kind, ty.clone(), span)?;
return Ok((typed, ty));
}
self.infer_expr(receiver, None)
}
pub(super) fn infer_evolving_chain(
&mut self,
base: ExprId,
parts: &[crate::ChainPart],
asserts_tail: bool,
) -> Result<Option<(TypedExprKind, Type)>, CompilerFailure> {
let [
ChainPart::Field { name, .. },
ChainPart::Call {
args,
type_args,
span,
..
},
tail @ ..,
] = parts
else {
return Ok(None);
};
if !is_growth_method(name) || self.held_arguments.contains_key(&base) {
return Ok(None);
}
let Some(binding) = self.evolving_array_receiver(base)? else {
return Ok(None);
};
let (kind, ty) = self.infer_evolving_array_call(
base,
binding,
MethodCallSite {
name: name.clone(),
type_args: type_args.clone(),
args: args.clone(),
expected: None,
span: *span,
},
)?;
if tail.is_empty() {
return Ok(Some((kind, ty)));
}
let value = self.push_synthetic_expr(kind, ty, *span)?;
self.with_held_arguments(BTreeMap::from([(base, value)]), |this| {
this.infer_chain_steps(base, tail.to_vec(), asserts_tail)
})
.map(Some)
}
pub(super) fn infer_evolving_array_call(
&mut self,
receiver: ExprId,
binding: Ident,
call: MethodCallSite<'_>,
) -> Result<(TypedExprKind, Type), CompilerFailure> {
let (receiver, before) = self.infer_array_operation_receiver(receiver)?;
let mut statements = Vec::new();
let receiver = self.hold_in_temp(receiver, "evolving_receiver", &mut statements)?;
let mut held = BTreeMap::new();
let mut added = Vec::new();
for &arg in &call.args {
let source = arg;
let (typed, ty) = self.infer_expr(source, None)?;
let ty = if self.ast.call_spread_args.contains(&source) {
super::expr::spread_element_type(ty.peel())
.or_else(|| self.spread_iterable_element(&ty))
.unwrap_or(Type::Error)
} else {
ty
};
let ty = super::stmt::widen_literal_members(ty);
added.push(ty);
held.insert(
source,
self.hold_in_temp(typed, "evolving_argument", &mut statements)?,
);
}
let element = Type::union(added);
let current_element = self.evolving_array_element(&binding);
let after = Type::Array(Box::new(Type::union(vec![
array_element(&before).unwrap_or(Type::Never),
current_element.clone().unwrap_or(Type::Never),
element.clone(),
])));
let receiver = self.push_synthetic_expr(
TypedExprKind::Cast {
value: receiver,
target_ty: after.clone(),
check: None,
},
after.clone(),
call.span,
)?;
let method = self.find_method(&after, &call.name.name).ok_or_else(|| {
super::inference_failure("array growth method is declared in the prelude")
})?;
let span = call.span;
let (kind, ty) = self.with_held_arguments(held, |this| {
this.infer_found_method_call(receiver, &after, method, call)
})?;
let result = self.push_synthetic_expr(kind, ty.clone(), span)?;
if current_element.is_some() {
let element = Type::union(vec![current_element.unwrap_or(Type::Never), element]);
self.record_evolving_array_growth(&binding, Type::Array(Box::new(element)))?;
}
Ok((
TypedExprKind::Sequence {
stmts: statements,
result,
},
ty,
))
}
}
pub(super) fn is_growth_method(name: &Ident) -> bool {
matches!(name.name.as_str(), "push" | "unshift")
}
pub(super) fn calls_growth_method(ast: &crate::Ast, expr: ExprId) -> Result<bool, CompilerFailure> {
let calls_method = |callee: ExprId| -> Result<bool, CompilerFailure> {
Ok(matches!(
super::expr::string_key_callee_as_field(ast, callee)?,
ExprKind::FieldAccess { name, .. } if is_growth_method(&name)
))
};
match &ast.try_expr(expr).map_err(super::arena_failure)?.kind {
ExprKind::Call { callee, .. } => calls_method(*callee),
ExprKind::OptionalChain { base, parts } => match parts.as_slice() {
[ChainPart::Field { name, .. }, ChainPart::Call { .. }, ..] => {
Ok(is_growth_method(name))
}
[ChainPart::Call { optional: true, .. }, ..] => calls_method(*base),
_ => Ok(false),
},
_ => Ok(false),
}
}
pub(super) fn array_element(ty: &Type) -> Option<Type> {
match ty.peel() {
Type::Array(element) => Some((**element).clone()),
Type::Union(members) => members
.iter()
.map(array_element)
.collect::<Option<Vec<_>>>()
.map(Type::union),
_ => None,
}
}