use super::Checker;
use super::state::Var;
use crate::ast::{Binding, Block, ExprKind, Stmt};
impl Checker<'_, '_> {
pub(super) fn block(&mut self, b: &Block) {
self.scoped(|c| b.stmts.iter().for_each(|s| c.stmt(s)));
}
fn scoped(&mut self, f: impl FnOnce(&mut Self)) {
self.state.push();
f(self);
self.state.pop();
}
pub(super) fn stmt(&mut self, s: &Stmt) {
match s {
Stmt::Local { binding, value, .. } => {
if let Some(v) = value {
self.expr(v);
}
self.bind(binding);
}
Stmt::Assign { place, op, value } => {
self.expr(value);
match (&place.kind, op) {
(ExprKind::Ident(name), None) => self.renew(name),
_ => self.expr(place),
}
}
Stmt::If {
cond,
then,
otherwise,
} => {
self.expr(cond);
let before = self.state.clone();
self.scoped(|c| c.stmt(then));
let after_then = std::mem::replace(&mut self.state, before);
if let Some(o) = otherwise {
self.scoped(|c| c.stmt(o));
}
self.state = after_then.join(self.state.clone());
}
Stmt::Foreach {
binding,
iter,
body,
..
} => {
self.expr(iter);
self.looping(|c| {
c.bind(binding);
c.stmt(body);
});
}
Stmt::For {
init,
cond,
step,
body,
} => self.scoped(|c| {
if let Some(i) = init {
c.stmt(i);
}
c.looping(|c| {
c.opt_expr(cond.as_ref());
c.stmt(body);
if let Some(s) = step {
c.stmt(s);
}
});
}),
Stmt::While { cond, body } | Stmt::DoWhile { body, cond } => self.looping(|c| {
c.expr(cond);
c.stmt(body);
}),
_ => self.leaf(s),
}
}
fn leaf(&mut self, s: &Stmt) {
match s {
Stmt::Block(b) => self.block(b),
Stmt::Return(value, _) | Stmt::Skip(value, _) => {
self.opt_expr(value.as_ref());
self.state.dead = true;
}
Stmt::Expr(e) | Stmt::Value(e) => self.expr(e),
_ => {}
}
}
fn looping(&mut self, mut body: impl FnMut(&mut Self)) {
let before = self.state.clone();
for _ in 0..LOOP_READS {
self.scoped(&mut body);
self.state = before.clone().join(self.state.clone());
}
}
pub(super) fn bind(&mut self, b: &Binding) {
match b {
Binding::One(n) => self.state.declare(&n.text, Var::default()),
Binding::Tuple(names) => names
.iter()
.for_each(|n| self.state.declare(&n.text, Var::default())),
}
}
pub(super) fn renew(&mut self, name: &str) {
if let Some(v) = self.state.get_mut(name) {
v.moved = None;
}
}
}
const LOOP_READS: usize = 2;