use super::Checker;
use super::model::Type;
use crate::ast::{self, BinOp, Binding, Block, Expr, ExprKind, Pattern, Stmt};
use yog_script_ids::system::ENUMERABLE;
impl Checker<'_, '_, '_> {
pub(super) fn block(&mut self, b: &Block) {
self.scoped(|c| c.stmts(&b.stmts));
}
pub(super) fn stmts(&mut self, stmts: &[Stmt]) {
let depth = self.scopes.len();
for f in crate::ast::local_functions(stmts) {
self.local_fns.push((depth, f.clone()));
}
for s in stmts {
self.stmt(s);
}
}
pub(super) fn stmt(&mut self, s: &Stmt) {
match s {
Stmt::Switch { expr, sections } => self.switch_stmt(expr, sections),
Stmt::Goto(crate::ast::Goto::Case(value), _) => self.goto_case(value),
Stmt::Goto(..) | Stmt::Label(_) => {}
Stmt::Function(d) => {
if let crate::ast::DeclKind::Method(m) = &d.kind {
let side = self.side;
self.method_decl(d, m, side);
}
}
Stmt::Local {
ty,
binding,
value,
mutable,
} => self.local_decl(ty.as_ref(), binding, value.as_ref(), *mutable),
Stmt::Assign { place, op, value } => self.assign_stmt(place, op.as_ref(), value),
Stmt::If {
cond,
then,
otherwise,
} => {
self.if_stmt(cond, then, otherwise.as_deref());
}
Stmt::Foreach {
ty,
binding,
iter,
body,
mutable,
awaits,
} => self.foreach((ty.as_ref(), binding, *mutable), (iter, *awaits), body),
Stmt::For {
init,
cond,
step,
body,
} => self.for_loop((init.as_deref(), cond.as_ref(), step.as_deref()), body),
Stmt::While { cond, body } => self.scoped(|c| {
c.condition(cond);
c.branch_with(super::nulls::proven(cond, true), body);
}),
Stmt::DoWhile { body, cond } => self.scoped(|c| {
c.condition(cond);
c.branch(body);
}),
Stmt::Block(b) | Stmt::Checked(b, _) => self.block(b),
Stmt::Unsafe(b, at) => self.unsafe_block(b, *at),
Stmt::Yield(v, at) => self.yielded(v.as_ref(), *at),
Stmt::Lock {
target,
value,
body,
..
} => self.locked(target, value.as_ref(), body),
Stmt::Return(Some(v), at) => self.returned(v, *at),
Stmt::Skip(Some(v), _) | Stmt::Expr(v) | Stmt::Value(v) => {
self.expr(v);
}
Stmt::Return(None, _) | Stmt::Skip(None, _) | Stmt::Break(_) | Stmt::Continue(_) => {}
}
}
fn local_decl(
&mut self,
ty: Option<&ast::Type>,
binding: &Binding,
value: Option<&Expr>,
mutable: bool,
) {
let declared = ty.map(|t| self.type_of(t));
let t = match (declared, value) {
(Some(d), Some(v)) => {
self.fits(v, &d);
d
}
(Some(d), None) => d,
(None, Some(v)) => self.expr(v),
(None, None) => Type::Unknown,
};
if let Some(v) = value {
self.handed(v, &t);
}
self.bind_binding(binding, &t, !mutable);
}
fn foreach(
&mut self,
(ty, binding, mutable): (Option<&ast::Type>, &Binding, bool),
(iter, awaits): (&Expr, bool),
body: &Stmt,
) {
let items = self.expr(iter);
let item = if awaits {
self.sequence_item(&items, yog_script_ids::system::ASYNC_ENUMERABLE)
} else {
self.item_of(&items)
};
let t = ty.map_or(item, |t| self.type_of(t));
self.types.set_item(iter, t.clone());
self.scoped(|c| {
c.bind_binding(binding, &t, !mutable);
c.branch(body);
});
}
fn returned(&mut self, v: &Expr, at: crate::source::token::Span) {
if self.yields.is_some() {
self.expr(v);
return self.returned_from_iterator(at);
}
match self.returns.clone() {
Some(r) if r != Type::Void => self.fits(v, &r),
_ => {
let t = self.expr(v);
if let Some(inferred) = &mut self.inferred {
inferred.push(t);
}
}
}
}
fn locked(&mut self, target: &Expr, value: Option<&crate::ast::Name>, body: &Stmt) {
let t = self.expr(target);
let held = match t.non_null() {
Type::Own(name, args) if name == yog_script_ids::system::LOCK && args.len() == 1 => {
args[0].clone()
}
_ => {
self.report.error(
yog_ids::script_problem::TYPE_MISMATCH,
target.span,
format!("`lock` holds a `Lock<T>`, not a `{t}`"),
);
Type::Unknown
}
};
self.scoped(|c| {
if let Some(n) = value {
c.bind_as(&n.text, held, false);
}
c.branch(body);
});
}
fn assign_stmt(&mut self, place: &Expr, op: Option<&BinOp>, value: &Expr) {
let t = self.expr(place);
self.written(place);
match op {
Some(&op) if self.delegate_sig(&t).is_none() => self.compound(place, op, &t, value),
_ => {
self.fits(value, &t);
self.handed(value, &t);
}
}
if matches!(op, None | Some(BinOp::Coalesce))
&& let Some(path) = super::nulls::path_of(place)
&& let Some(v) = self.types.of(value).cloned()
{
self.assigned_to(&path, &v);
}
}
fn if_stmt(&mut self, cond: &Expr, then: &Stmt, otherwise: Option<&Stmt>) {
use super::nulls::{leaves, proven};
self.scoped(|c| {
c.patterns_in(cond);
c.condition(cond);
c.branch_with(proven(cond, true), then);
});
if let Some(o) = otherwise {
self.branch_with(proven(cond, false), o);
}
let (then_leaves, else_leaves) = (leaves(then), otherwise.is_some_and(leaves));
if then_leaves && !else_leaves {
self.narrow(proven(cond, false));
} else if else_leaves && !then_leaves {
self.narrow(proven(cond, true));
}
}
fn for_loop(
&mut self,
(init, cond, step): (Option<&Stmt>, Option<&Expr>, Option<&Stmt>),
body: &Stmt,
) {
self.scoped(|c| {
if let Some(i) = init {
c.stmt(i);
}
if let Some(cond) = cond {
c.condition(cond);
}
if let Some(s) = step {
c.stmt(s);
}
c.branch(body);
});
}
fn branch(&mut self, s: &Stmt) {
self.scoped(|c| c.stmt(s));
}
fn record_params(&mut self, name: &str, args: &[Type]) -> Vec<Type> {
let Some(params) = self
.own
.get(name, args.len())
.filter(|o| o.record)
.map(|o| o.ctor.clone())
else {
return Vec::new();
};
let context = self.context_of(name, args);
self.in_context(&context, |c| {
params.iter().map(|p| c.type_of(&p.ty)).collect()
})
}
fn bind_binding(&mut self, b: &Binding, t: &Type, fixed: bool) {
match (b, t) {
(Binding::One(n), _) => self.bind_as(&n.text, t.clone(), fixed),
(Binding::Tuple(names), Type::Tuple(items, _)) if names.len() == items.len() => {
for (n, item) in names.iter().zip(items) {
self.types.set_binding(n, item.clone());
self.bind_as(&n.text, item.clone(), fixed);
}
}
(Binding::Tuple(names), Type::Own(record, args))
if self.record_params(record, args).len() == names.len() =>
{
for (n, item) in names.iter().zip(self.record_params(record, args)) {
self.types.set_binding(n, item.clone());
self.bind_as(&n.text, item, fixed);
}
}
(Binding::Tuple(names), _) => names
.iter()
.for_each(|n| self.bind_as(&n.text, Type::Unknown, fixed)),
}
}
pub(super) fn patterns_in(&mut self, e: &Expr) {
match &e.kind {
ExprKind::Is { expr, pattern } => {
let on = self.quietly(|c| c.expr(expr));
self.bind_arm(pattern, &on);
}
ExprKind::Binary { left, right, .. } => {
self.patterns_in(left);
self.patterns_in(right);
}
_ => {}
}
}
pub(super) fn bind_pattern(&mut self, p: &Pattern) {
match p {
Pattern::Type { ty, bind: Some(n) } => {
let t = self.type_of(ty);
self.types.set_binding(n, t.clone());
self.bind_as(&n.text, t, true);
}
Pattern::Let(n) => self.bind_as(&n.text, Type::Unknown, true),
Pattern::And(a, b) | Pattern::Or(a, b) => {
self.bind_pattern(a);
self.bind_pattern(b);
}
_ => {}
}
}
fn compound(&mut self, place: &Expr, op: BinOp, t: &Type, value: &Expr) {
let v = self.expr(value);
let r = match self.declared_binary(op, t, &v) {
Some((used, ret)) => {
self.types.set_assigned(place, used);
ret
}
None => self.combined(op, (t.clone(), v.clone()), (place.span, value.span)),
};
let narrowed = r.is_numeric()
&& t.is_numeric()
&& (super::exprs::is_number_literal(value) || self.converts(&v, t));
if !narrowed {
self.want(&r, t, value.span);
}
}
pub(super) fn item_of(&self, t: &Type) -> Type {
match t.non_null() {
Type::Array(item, _) => (**item).clone(),
Type::String => Type::Char,
_ => self.sequence_item(t, ENUMERABLE),
}
}
pub(super) fn sequence_item(&self, t: &Type, of: &str) -> Type {
match t.non_null() {
Type::Own(name, args) => self
.lineage_of(name, args)
.into_iter()
.find(|(k, _)| k.name == of && k.arity == 1)
.and_then(|(_, args)| args.into_iter().next())
.unwrap_or(Type::Unknown),
_ => Type::Unknown,
}
}
fn switch_stmt(&mut self, expr: &Expr, sections: &[crate::ast::Section]) {
let t = self.expr(expr);
self.switched.push(t.clone());
for s in sections {
self.scoped(|c| {
for (pattern, guard) in s.labels.iter().flatten() {
c.bind_arm(pattern, &t);
if let Some(g) = guard {
c.condition(g);
}
}
c.stmts(&s.body);
});
}
self.switched.pop();
}
fn goto_case(&mut self, value: &Expr) {
match self.switched.last().cloned() {
Some(t) => self.fits(value, &t),
None => {
self.expr(value);
}
}
}
}