use std::collections::HashMap;
use yog_ids::script_problem;
use super::Checker;
use super::delegates::Sig;
use super::model::Type;
use super::own::TypeKey;
use super::types::OperatorUse;
use crate::ast::{BinOp, Decl, DeclKind, Expr, Modifier, OperatorKind, UnOp};
use crate::parser::expr::sign_of;
use crate::source::token::Span;
pub(super) type Operators = HashMap<TypeKey, Vec<(OperatorKind, String, Sig)>>;
const PAIRS: [(BinOp, BinOp); 3] = [
(BinOp::Eq, BinOp::NotEq),
(BinOp::Lt, BinOp::Gt),
(BinOp::LtEq, BinOp::GtEq),
];
impl Checker<'_, '_, '_> {
pub(super) fn type_operators(&mut self) {
let declared: Vec<(TypeKey, Vec<String>, Vec<crate::ast::Operator>)> = self
.own
.all()
.filter(|(_, t)| !t.operators.is_empty())
.map(|(k, t)| (k.clone(), t.generics.clone(), t.operators.clone()))
.collect();
let mut operators = Operators::new();
self.quietly(|c| {
for (key, generics, ops) in declared {
let typed = c.as_declared(&key.name, &generics, |c| {
ops.iter()
.map(|op| {
(
op.kind,
op.method.name.text.clone(),
c.signature_types(&op.method),
)
})
.collect()
});
operators.insert(key, typed);
}
});
self.operators = operators;
}
fn declared(&self, t: &Type, kind: OperatorKind) -> Vec<(OperatorUse, Sig)> {
let Type::Own(name, args) = t.non_null() else {
return Vec::new();
};
let mut out = Vec::new();
for (key, args) in self.lineage_of(name, args) {
let bound = self.bound_of(&key.name, &args);
for (k, method, (params, ret)) in self.operators.get(&key).into_iter().flatten() {
if *k == kind {
let owner = Type::Own(key.name.clone(), args.clone());
let sig = (
params.iter().map(|p| p.substitute(&bound)).collect(),
ret.substitute(&bound),
);
out.push((
OperatorUse {
owner,
method: method.clone(),
},
sig,
));
}
}
}
out
}
fn chosen(&self, kind: OperatorKind, given: &[Type]) -> Option<(OperatorUse, Type)> {
given
.iter()
.flat_map(|t| self.declared(t, kind))
.find(|(_, (params, _))| {
params.len() == given.len()
&& given
.iter()
.zip(params)
.all(|(g, p)| self.converts_plainly(g, p))
})
.map(|(used, (_, ret))| (used, ret))
}
pub(super) fn declared_binary(
&self,
op: BinOp,
a: &Type,
b: &Type,
) -> Option<(OperatorUse, Type)> {
self.chosen(OperatorKind::Binary(op), &[a.clone(), b.clone()])
}
pub(super) fn declared_unary(&mut self, e: &Expr, op: UnOp, t: &Type) -> Option<Type> {
let declared = OperatorKind::Unary(op.declared());
let (used, ret) = self.chosen(declared, std::slice::from_ref(t))?;
self.types.set_operator(e, used);
Some(ret)
}
fn conversion(&self, kind: OperatorKind, got: &Type, wanted: &Type) -> Option<OperatorUse> {
[got, wanted]
.into_iter()
.flat_map(|t| self.declared(t, kind))
.find(|(_, (params, ret))| {
params.len() == 1
&& self.converts_plainly(got, ¶ms[0])
&& self.converts_plainly(ret, wanted)
})
.map(|(used, _)| used)
}
pub(super) fn implicit(&self, got: &Type, wanted: &Type) -> Option<OperatorUse> {
self.conversion(OperatorKind::Implicit, got, wanted)
}
pub(super) fn note_conversion(&mut self, e: &Expr, got: &Type, wanted: &Type) {
self.note_array_view(e, got, wanted);
if *wanted.non_null() == Type::Decimal
&& got.non_null().is_numeric()
&& *got.non_null() != Type::Decimal
{
let used = OperatorUse {
owner: Type::own(yog_script_ids::clr::DECIMAL.to_string()),
method: yog_script_ids::clr::OP_IMPLICIT.to_string(),
};
self.types.set_conversion(e, used);
return;
}
self.note_variance(e, got, wanted);
if !self.converts_plainly(got, wanted)
&& let Some(used) = self.implicit(got, wanted)
{
self.types.set_conversion(e, used);
}
}
pub(super) fn note_cast(&mut self, cast: &Expr, got: &Type, to: &Type) {
if self.converts_plainly(got, to) {
return;
}
let used = self
.conversion(OperatorKind::Explicit, got, to)
.or_else(|| self.implicit(got, to));
if let Some(used) = used {
self.types.set_operator(cast, used);
}
}
pub(super) fn check_operators(&mut self, this: &Type, body: &[Decl]) {
let mut binaries = Vec::new();
for d in body {
let DeclKind::Operator(op) = &d.kind else {
continue;
};
let at = op.method.name.span;
if !(d.has(Modifier::Public) && d.has(Modifier::Static)) {
self.bad_operator(at, "an operator is `public static`");
}
let (params, ret) = self.signature_types(&op.method);
let own = |t: &Type| {
t.non_null() == this.non_null()
|| self.hosted(t.non_null()).as_ref() == Some(this.non_null())
};
let of_this = match op.kind {
OperatorKind::Implicit | OperatorKind::Explicit => {
params.iter().any(own) || own(&ret)
}
_ => params.iter().any(own),
};
if !of_this {
self.bad_operator(at, &format!("an operator of `{this}` takes a `{this}`"));
}
if let OperatorKind::Binary(b) = op.kind {
binaries.push((b, at));
}
}
for (one, other) in PAIRS {
for (a, b) in [(one, other), (other, one)] {
if let Some((_, at)) = binaries.iter().find(|(o, _)| *o == a)
&& !binaries.iter().any(|(o, _)| *o == b)
{
let (a, b) = (sign_of(a), sign_of(b));
self.bad_operator(*at, &format!("`operator {a}` comes with `operator {b}`"));
}
}
}
}
fn bad_operator(&mut self, at: Span, why: &str) {
self.report
.error(script_problem::OPERATOR, at, why.to_string());
}
}