use yog_ids::script_problem;
use super::Checker;
use super::model::{BLOCKS, SECONDS, TICKS, Type};
use crate::ast::{BinOp, Element, Expr, ExprKind, StrSeg, UnOp};
use crate::source::token::Span;
const TICK_SUFFIX: &str = "t";
const SECOND_SUFFIXES: [&str; 4] = ["s", "ms", "m", "h"];
const BLOCK_SUFFIX: &str = "b";
impl Checker<'_, '_, '_> {
pub(super) fn expr(&mut self, e: &Expr) -> Type {
let t = self.expr_kind(e);
self.types.set(e, t.clone());
t
}
fn expr_kind(&mut self, e: &Expr) -> Type {
if let Some(t) = literal(&e.kind) {
return t;
}
match &e.kind {
ExprKind::Str(segs) => {
for s in segs {
if let StrSeg::Expr(e) = s {
self.expr(e);
}
}
Type::String
}
ExprKind::Bool(_) | ExprKind::Mc(_) | ExprKind::Is { .. } | ExprKind::Has { .. } => {
self.walk_inner(e);
Type::Bool
}
ExprKind::Ident(name) => self.name(name, e.span),
ExprKind::This => self.this.clone().unwrap_or(Type::Unknown),
ExprKind::Base => self.base(),
ExprKind::Declare { ty, name, mutable } => {
self.declare(ty.as_ref(), &name.text, *mutable)
}
ExprKind::NameOf(expr) => self.name_of(expr),
ExprKind::Move(expr) => self.expr(expr),
ExprKind::Default(Some(ty)) => self.type_of(ty),
ExprKind::Member { expr, name, safe } => {
let on = self.expr(expr);
self.member(&on, &name.text, *safe, name.span)
}
ExprKind::Call { callee, args } => self.call(callee, args),
ExprKind::Switch { expr, arms } => self.switch(expr, arms, e.span),
ExprKind::Lambda { params, body } => self.lambda_alone(params, body),
ExprKind::Macro { name, args } => self.builtin_macro(name, args),
ExprKind::Unary { op, expr } => self.unary(*op, expr),
ExprKind::Binary { op, left, right } => self.binary(*op, left, right, e.span),
ExprKind::Conditional {
cond,
then,
otherwise,
} => {
self.condition(cond);
let t = self.expr(then);
self.expr(otherwise);
t
}
ExprKind::Cast { ty, expr } => {
self.expr(expr);
self.type_of(ty)
}
ExprKind::As { expr, ty } => {
self.expr(expr);
self.type_of(ty).nullable()
}
ExprKind::New { ty, args, init } => {
args.iter().flatten().for_each(|a| {
self.expr(&a.value);
});
init.iter().for_each(|(_, v)| {
self.expr(v);
});
let t = ty.as_ref().map_or(Type::Unknown, |t| self.type_of(t));
self.made(&t, e.span);
t
}
ExprKind::NotNull(inner) => self.expr(inner).non_null().clone(),
ExprKind::Index { expr, index } => {
let on = self.expr(expr);
self.indexed(on, index, e.span)
}
_ => {
self.walk_inner(e);
Type::Unknown
}
}
}
fn walk_inner(&mut self, e: &Expr) {
match &e.kind {
ExprKind::Is { expr, .. }
| ExprKind::Has { expr, .. }
| ExprKind::TypeArgs { expr, .. } => {
self.expr(expr);
}
ExprKind::Tuple(items) => items.iter().for_each(|i| {
self.expr(i);
}),
ExprKind::Collection(items) => items.iter().for_each(|i| match i {
Element::Item(e) | Element::Spread(e) => {
self.expr(e);
}
Element::Entry(k, v) => {
self.expr(k);
self.expr(v);
}
}),
ExprKind::Range { from, to, .. } => {
self.expr(from);
self.expr(to);
}
ExprKind::Lambda { params, body } => {
self.lambda_alone(params, body);
}
_ => {}
}
}
fn name(&mut self, name: &str, at: Span) -> Type {
if let Some(t) = self.local(name) {
return t.clone();
}
if let Some(this) = self.this.clone() {
let t = self.own_member(&this, name);
if t.known() {
return t;
}
}
if let Some(t) = self.own.values.get(name).cloned() {
return self.type_of(&t);
}
if self.own.types.contains_key(name) {
return Type::Static(name.to_string());
}
self.imported(name, at)
}
fn unary(&mut self, op: UnOp, expr: &Expr) -> Type {
let t = self.expr(expr);
match op {
UnOp::Not => {
self.want(&t, &Type::Bool, expr.span);
Type::Bool
}
UnOp::Neg => t,
UnOp::Try => match t.non_null() {
Type::Own(name, args) if name == crate::system::RESULT => {
args.first().cloned().unwrap_or(Type::Unknown)
}
_ => Type::Unknown,
},
UnOp::Await => Type::Unknown,
}
}
fn binary(&mut self, op: BinOp, left: &Expr, right: &Expr, at: Span) -> Type {
let (a, b) = (self.expr(left), self.expr(right));
match op {
BinOp::And | BinOp::Or => {
self.want(&a, &Type::Bool, left.span);
self.want(&b, &Type::Bool, right.span);
Type::Bool
}
BinOp::Eq
| BinOp::NotEq
| BinOp::Lt
| BinOp::LtEq
| BinOp::Gt
| BinOp::GtEq
| BinOp::In => Type::Bool,
BinOp::Coalesce => a.non_null().clone(),
BinOp::Add if a == Type::String || b == Type::String => Type::String,
BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div | BinOp::Rem => {
if a.known() && b.known() && !(a.is_numeric() && b.is_numeric()) {
self.report.error(
script_problem::TYPE_MISMATCH,
at,
format!("`{a}` and `{b}` don't make a number together"),
);
return Type::Unknown;
}
Type::wider(&a, &b)
}
}
}
pub(super) fn condition(&mut self, e: &Expr) {
let t = self.expr(e);
self.want(&t, &Type::Bool, e.span);
}
pub(super) fn fits(&mut self, e: &Expr, wanted: &Type) {
if let Type::Fn(params, ret) = wanted.non_null()
&& self.as_delegate(e, params, ret)
{
return;
}
let t = self.expr(e);
if is_number_literal(e) && (wanted.non_null().is_numeric()) {
return;
}
self.want(&t, wanted, e.span);
}
pub(super) fn want(&mut self, got: &Type, wanted: &Type, at: Span) {
if self.converts(got, wanted) {
return;
}
let (code, message) = if *got == Type::Null
|| got.is_nullable() && got.non_null().converts_to(wanted)
{
(
script_problem::NULLABLE,
format!(
"this may be `null`, but a `{wanted}` can't be: `??` gives a value for null, `!` says it isn't"
),
)
} else {
(
script_problem::TYPE_MISMATCH,
format!("a `{got}` where a `{wanted}` is wanted"),
)
};
self.report.error(code, at, message);
}
}
impl Checker<'_, '_, '_> {
fn declare(&mut self, ty: Option<&crate::ast::Type>, name: &str, mutable: bool) -> Type {
let t = ty.map_or(Type::Unknown, |t| self.type_of(t));
self.bind_as(name, t.clone(), !mutable);
t
}
fn name_of(&mut self, expr: &Expr) -> Type {
self.expr(expr);
Type::String
}
}
fn literal(kind: &ExprKind) -> Option<Type> {
let t = match kind {
ExprKind::Int(_) => Type::I32,
ExprKind::Float(_) => Type::F64,
ExprKind::Unit { unit, .. } => super::builtin::by_word(unit)
.filter(super::builtin::is_number)
.unwrap_or_else(|| Type::Unit(unit_of(unit))),
ExprKind::RawStr(_) => Type::String,
ExprKind::Char(_) => Type::Char,
ExprKind::Text(_) => Type::Text,
ExprKind::Content { kind, .. } => Type::Content(kind.clone()),
ExprKind::Tag(_) => Type::Tag,
ExprKind::Null => Type::Null,
_ => return None,
};
Some(t)
}
fn unit_of(suffix: &str) -> String {
match suffix {
TICK_SUFFIX => TICKS.to_string(),
BLOCK_SUFFIX => BLOCKS.to_string(),
s if SECOND_SUFFIXES.contains(&s) => SECONDS.to_string(),
own => own.to_string(),
}
}
fn is_number_literal(e: &Expr) -> bool {
match &e.kind {
ExprKind::Int(_) | ExprKind::Float(_) => true,
ExprKind::Unary {
op: UnOp::Neg,
expr,
} => is_number_literal(expr),
_ => false,
}
}