use super::Checker;
use super::context::Context;
use super::model::Type;
use crate::ast::{BinOp, Expr, ExprKind, Method, Pattern, Stmt, UnOp};
use crate::macros::builtin::{Gives, named};
impl Checker<'_, '_, '_> {
pub(super) fn narrow(&mut self, names: Vec<String>) {
let depth = self.scopes.len();
self.non_null.extend(names.into_iter().map(|n| (depth, n)));
}
pub(super) fn is_narrowed(&self, name: &str, slot: usize) -> bool {
self.non_null
.iter()
.any(|(depth, n)| n == name && slot < *depth)
}
pub(super) fn assigned_to(&mut self, name: &str, t: &Type) {
let members = format!("{name}{PATH_SEP}");
self.non_null
.retain(|(_, n)| n != name && !n.starts_with(&members));
if t.known() && !t.is_nullable() && *t != Type::Null {
self.narrow(vec![name.to_string()]);
}
}
pub(super) fn scopes_left(&mut self, depth: usize) {
self.non_null.retain(|(d, _)| *d <= depth);
}
pub(super) fn branch_with(&mut self, names: Vec<String>, s: &Stmt) {
self.scoped(|c| {
c.narrow(names);
c.stmt(s);
});
}
pub(super) fn expr_with(&mut self, names: Vec<String>, e: &Expr) -> Type {
self.scoped(|c| {
c.narrow(names);
c.expr(e)
})
}
}
pub(super) fn proven(cond: &Expr, when: bool) -> Vec<String> {
match &cond.kind {
ExprKind::Binary { op, left, right } => match (op, when) {
(BinOp::NotEq, true) | (BinOp::Eq, false) => compared_to_null(left, right),
(BinOp::And, true) | (BinOp::Or, false) => {
[proven(left, when), proven(right, when)].concat()
}
_ => Vec::new(),
},
ExprKind::Unary {
op: UnOp::Not,
expr,
} => proven(expr, !when),
ExprKind::Is { expr, pattern } => match (path_of(expr), matched_not_null(pattern)) {
(Some(path), Some(m)) if m == when => vec![path],
_ => Vec::new(),
},
_ => Vec::new(),
}
}
fn compared_to_null(left: &Expr, right: &Expr) -> Vec<String> {
let x = match (&left.kind, &right.kind) {
(_, ExprKind::Null) => left,
(ExprKind::Null, _) => right,
_ => return Vec::new(),
};
path_of(x).into_iter().collect()
}
pub(super) fn path_of(e: &Expr) -> Option<String> {
match &e.kind {
ExprKind::Ident(n) => Some(n.clone()),
ExprKind::Member {
expr,
name,
safe: false,
} => Some(member_path(&path_of(expr)?, &name.text)),
_ => None,
}
}
pub(super) fn member_path(of: &str, name: &str) -> String {
format!("{of}{PATH_SEP}{name}")
}
const PATH_SEP: char = '.';
fn matched_not_null(p: &Pattern) -> Option<bool> {
match p {
Pattern::Type { .. } | Pattern::Property { .. } | Pattern::List(..) => Some(true),
Pattern::Value(Expr {
kind: ExprKind::Null,
..
}) => Some(false),
Pattern::Not(inner) => matched_not_null(inner).map(|m| !m),
_ => None,
}
}
pub fn leaves(s: &Stmt) -> bool {
match s {
Stmt::Return(..) | Stmt::Break(_) | Stmt::Continue(_) | Stmt::Skip(..) | Stmt::Goto(..) => true,
Stmt::Yield(None, _) => true,
Stmt::Block(b) | Stmt::Unsafe(b, _) | Stmt::Checked(b, _) => b.stmts.last().is_some_and(leaves),
Stmt::If {
then,
otherwise: Some(o),
..
} => leaves(then) && leaves(o),
Stmt::Expr(Expr {
kind: ExprKind::Macro { name, .. },
..
}) => named(&name.text).is_some_and(|b| matches!(b.gives, Gives::Never)),
_ => false,
}
}
impl Checker<'_, '_, '_> {
fn as_nullable(&self, t: &Type) -> Option<Type> {
match t {
Type::Nullable(inner) if !self.is_reference(inner) => Some(Type::Own(
yog_script_ids::system::NULLABLE.to_string(),
vec![(**inner).clone()],
)),
_ => None,
}
}
pub(super) fn nullable_member(&mut self, on: &Type, name: &str) -> Option<Type> {
let t = self.own_member(&self.as_nullable(on)?, name);
t.known().then_some(t)
}
pub(super) fn nullable_methods(
&mut self,
on: &Type,
name: &str,
) -> Option<Vec<(Method, Context)>> {
self.own_methods(&self.as_nullable(on)?, name)
}
}