use std::collections::BTreeSet;
use crate::compiler_error::CompilerFailure;
use crate::{ArrowBody, Ast, ExprId, ExprKind, Ident, Span, StmtId, StmtKind, Type};
use super::{Inferer, narrowing};
pub(super) struct InvokedBodyExit {
pub(super) written: BTreeSet<narrowing::ReferencePath>,
pub(super) narrowed: Vec<(narrowing::ReferencePath, Type)>,
}
pub(super) fn immediately_invoked_arrow(
ast: &Ast,
callee: ExprId,
args: &[ExprId],
) -> Result<Option<ExprId>, CompilerFailure> {
if !args.is_empty() {
return Ok(None);
}
Ok(
match &ast.try_expr(callee).map_err(super::arena_failure)?.kind {
ExprKind::Paren(inner) => immediately_invoked_arrow(ast, *inner, args)?,
ExprKind::FunctionExpression {
name: None,
function,
..
} => Some(*function),
ExprKind::Arrow { .. } => Some(callee),
_ => None,
},
)
}
pub(super) fn returns_before_end(ast: &Ast, body: &ArrowBody) -> Result<bool, CompilerFailure> {
let ArrowBody::Block(block) = body else {
return Ok(false);
};
let StmtKind::Block(stmts) = &ast.try_stmt(*block).map_err(super::arena_failure)?.kind else {
return contains_return(ast, *block);
};
let Some((last, leading)) = stmts.split_last() else {
return Ok(false);
};
for &stmt in leading {
if contains_return(ast, stmt)? {
return Ok(true);
}
}
if matches!(
ast.try_stmt(*last).map_err(super::arena_failure)?.kind,
StmtKind::Return(_)
) {
return Ok(false);
}
contains_return(ast, *last)
}
fn contains_return(ast: &Ast, stmt: StmtId) -> Result<bool, CompilerFailure> {
let nested: Vec<StmtId> = match &ast.try_stmt(stmt).map_err(super::arena_failure)?.kind {
StmtKind::Return(_) => return Ok(true),
StmtKind::Block(stmts) => stmts.clone(),
StmtKind::If {
then_block,
else_block,
..
} => std::iter::once(*then_block).chain(*else_block).collect(),
StmtKind::While { body, .. }
| StmtKind::DoWhile { body, .. }
| StmtKind::For { body, .. }
| StmtKind::ForOf { body, .. }
| StmtKind::ForOfPattern { body, .. } => vec![*body],
StmtKind::Switch { cases, default, .. } => cases
.iter()
.map(|case| case.body)
.chain(default.iter().map(|default| default.body))
.collect(),
StmtKind::Try {
body,
catches,
finally,
} => std::iter::once(*body)
.chain(catches.iter().map(|catch| catch.body))
.chain(*finally)
.collect(),
_ => Vec::new(),
};
for stmt in nested {
if contains_return(ast, stmt)? {
return Ok(true);
}
}
Ok(false)
}
impl Inferer<'_> {
pub(super) fn infer_call_running_invoked_body(
&mut self,
callee: ExprId,
type_args: Option<Vec<crate::TypeAnnotation>>,
args: Vec<ExprId>,
expected: Option<&Type>,
span: Span,
) -> Result<(crate::TypedExprKind, Type), CompilerFailure> {
let Some(arrow) = immediately_invoked_arrow(self.ast, callee, &args)? else {
return self
.infer_call_typing_invoked_parameters(callee, type_args, args, expected, span);
};
let arrow_span = self.ast.try_expr(arrow).map_err(super::arena_failure)?.span;
self.immediately_invoked = Some(arrow_span);
self.invoked_return_hint = expected.cloned();
let inferred = self.infer_call(callee, type_args, args, expected, span);
self.immediately_invoked = None;
self.invoked_return_hint = None;
let exit = self.invoked_body_exit.take();
let inferred = inferred?;
if let Some(exit) = exit {
self.apply_invoked_body_exit(exit, span)?;
}
Ok(inferred)
}
fn infer_call_typing_invoked_parameters(
&mut self,
callee: ExprId,
type_args: Option<Vec<crate::TypeAnnotation>>,
args: Vec<ExprId>,
expected: Option<&Type>,
span: Span,
) -> Result<(crate::TypedExprKind, Type), CompilerFailure> {
let Some(arrow) = super::expr::function_literal(self.ast, callee)? else {
return self.infer_call(callee, type_args, args, expected, span);
};
let arrow_expr = self.ast.try_expr(arrow).map_err(super::arena_failure)?;
let arrow_span = arrow_expr.span;
let ExprKind::Arrow { params, .. } = &arrow_expr.kind else {
return self.infer_call(callee, type_args, args, expected, span);
};
let params = params.clone();
let outermost = self.invoked_argument_memo.is_none();
if outermost {
self.invoked_argument_memo = Some(Default::default());
}
let types = self.invoked_argument_types(¶ms, &args);
let inferred = types.and_then(|types| {
let enclosing = self.invoked_parameter_types.take();
if types.iter().any(Option::is_some) {
self.invoked_parameter_types = Some((arrow_span, types));
}
let inferred = self.infer_call(callee, type_args, args, expected, span);
self.invoked_parameter_types = enclosing;
inferred
});
if outermost {
self.invoked_argument_memo = None;
}
inferred
}
pub(super) fn without_argument_memo<T>(&mut self, infer: impl FnOnce(&mut Self) -> T) -> T {
let memo = self.invoked_argument_memo.take();
let inferred = infer(self);
self.invoked_argument_memo = memo;
inferred
}
fn invoked_argument_types(
&mut self,
params: &[crate::ParamDecl],
args: &[ExprId],
) -> Result<Vec<Option<Type>>, CompilerFailure> {
let mut types = vec![None; params.len()];
for (index, (param, &arg)) in params.iter().zip(args).enumerate() {
if param.rest {
break;
}
if param.ty.is_some() || self.is_context_sensitive_function(arg)? {
continue;
}
if let Some(known) = self
.invoked_argument_memo
.as_ref()
.and_then(|memo| memo.get(&arg))
{
types[index] = known.clone();
continue;
}
let ty = Some(
self.speculative_expr_type(arg, false, true)?
.unwrap_or(Type::Error),
);
if let Some(memo) = self.invoked_argument_memo.as_mut() {
memo.insert(arg, ty.clone());
}
types[index] = ty;
}
Ok(types)
}
pub(super) fn take_invoked_parameter_types(&mut self, span: Span) -> Vec<Option<Type>> {
match self.invoked_parameter_types.take() {
Some((invoked, types)) if invoked == span => types,
other => {
self.invoked_parameter_types = other;
Vec::new()
}
}
}
pub(super) fn capture_invoked_body_exit(&self, returns_before_end: bool) -> InvokedBodyExit {
let written = self.assigned_scopes.last().cloned().unwrap_or_default();
let ends_normally = !returns_before_end;
let narrowed = written
.iter()
.filter(|path| ends_normally && path.chain.is_empty())
.filter_map(|path| {
let view = self.narrow_scopes.last()?.get(path)?;
Some((path.clone(), view.narrowed_ty.clone()))
})
.collect();
InvokedBodyExit { written, narrowed }
}
pub(super) fn apply_invoked_body_exit(
&mut self,
exit: InvokedBodyExit,
call_span: Span,
) -> Result<(), CompilerFailure> {
for path in exit.written {
if self.path_root_in_scope(&path) {
self.invalidate_for_reassignment(path, call_span);
}
}
for (path, narrowed_ty) in exit.narrowed {
if !self.path_root_in_scope(&path) {
continue;
}
let Some(name) = self.binding_name(&path) else {
continue;
};
let ident = Ident {
name,
span: call_span,
};
self.install_assignment_narrowing(path, ident, narrowed_ty, call_span)?;
}
Ok(())
}
fn binding_name(&self, path: &narrowing::ReferencePath) -> Option<String> {
match &path.root {
narrowing::BindingId::Local { name, .. } => Some(name.clone()),
narrowing::BindingId::Global(mangled) => self
.top_symbols
.iter()
.find(|(_, entry)| &entry.mangled_name == mangled)
.map(|(name, _)| name.clone()),
narrowing::BindingId::This => None,
}
}
}