use yog_ids::script_problem;
use super::Checker;
use super::game::{self, Found};
use super::model::Type;
use crate::ast::{Arg, Expr, ExprKind, Method, Pass};
use crate::source::token::Span;
pub(super) enum Callee {
Own(Vec<Method>, super::generics::Bound),
Typed(Vec<Signature>),
Unknown,
}
pub(super) struct Signature {
pub params: Vec<Type>,
pub passes: Vec<Option<Pass>>,
pub required: usize,
pub ret: Type,
pub generics: Vec<String>,
}
impl Checker<'_, '_, '_> {
pub(super) fn member(&mut self, on: &Type, name: &str, safe: bool, at: Span) -> Type {
if is_side_call(name) {
return Type::Unknown;
}
if on.is_nullable() && !safe {
self.report.error(
script_problem::NULLABLE,
at,
format!("this may be `null`: `?.{name}` reads `{name}` only when it isn't"),
);
}
let t = match on.non_null() {
Type::Static(of) => self.own_member(&Type::own(of.clone()), name),
Type::Own(..) => {
let t = self.own_member(on.non_null(), name);
if !t.known() && !self.own_has_method(on.non_null(), name) {
self.unknown_member(on.non_null(), name, at);
}
t
}
Type::Game(class) => match self.extension_value(class, name) {
Some(t) => t,
None => self.game_value(class, name, at),
},
Type::Namespace(ns) => self.in_namespace(&super::names::split(ns), name, at),
Type::Yog(type_name) => match self.extension_value(type_name, name) {
Some(t) => t,
None => self.yog_value(type_name, name, at),
},
_ => Type::Unknown,
};
if safe { t.nullable() } else { t }
}
pub(super) fn own_member(&mut self, t: &Type, name: &str) -> Type {
let (type_name, args) = match t {
Type::Own(n, args) => (n, args),
Type::Game(class) | Type::Yog(class) => {
return self.extension_value(class, name).unwrap_or(Type::Unknown);
}
_ => return Type::Unknown,
};
let bound = self.bound_of(type_name, args);
let lineage = self.own.lineage(type_name);
self.with_types(bound, |c| {
lineage
.iter()
.map(|of| c.own_member_of(of, name))
.find(Type::known)
.unwrap_or(Type::Unknown)
})
}
fn own_member_of(&mut self, of: &str, name: &str) -> Type {
let Some(own) = self.own.types.get(of) else {
return Type::Unknown;
};
if let Some(written) = own.values.get(name).cloned() {
return self.type_of(&written);
}
if own.cases.iter().any(|c| c == name) {
return Type::own(of.to_string());
}
let components = own.components.clone();
if components.iter().any(|c| c == name) {
return Type::own(name.to_string());
}
components
.iter()
.map(|c| self.own_member(&Type::own(c.clone()), name))
.find(Type::known)
.unwrap_or(Type::Unknown)
}
fn extension_value(&mut self, class: &str, name: &str) -> Option<Type> {
let ext = self.own.extended.get(crate::index::simple_name(class))?;
if let Some(written) = ext.values.get(name).cloned() {
return Some(self.type_of(&written));
}
let components = ext.components.clone();
if components.iter().any(|c| c == name) {
return Some(Type::own(name.to_string()));
}
components
.iter()
.map(|c| self.own_member(&Type::own(c.clone()), name))
.find(Type::known)
}
fn extension_methods(&self, class: &str, name: &str) -> Option<Vec<Method>> {
let ext = self.own.extended.get(crate::index::simple_name(class))?;
ext.methods.get(name).cloned().or_else(|| {
ext.components
.iter()
.find_map(|c| self.own_methods(&Type::own(c.clone()), name))
})
}
fn own_has_method(&self, t: &Type, name: &str) -> bool {
let Type::Own(type_name, _) = t else {
return false;
};
self.own.lineage(type_name).iter().any(|of| {
self.own.types.get(of).is_some_and(|o| {
o.methods.contains_key(name)
|| o.components
.iter()
.any(|c| self.own_has_method(&Type::own(c.clone()), name))
})
})
}
fn game_value(&mut self, class: &str, name: &str, at: Span) -> Type {
let Some(lookup) = &self.lookup else {
return Type::Unknown;
};
let Some(c) = lookup.class(class) else {
return Type::Unknown;
};
match game::member(lookup, c, name, false) {
Found::Value(t) => t,
Found::Nothing if matches!(game::member(lookup, c, name, true), Found::Nothing) => {
self.unknown_member(&Type::Game(class.to_string()), name, at);
Type::Unknown
}
_ => Type::Unknown,
}
}
fn yog_value(&mut self, type_name: &str, name: &str, at: Span) -> Type {
let surface = &*super::surface::SURFACE;
if let Some(s) = surface.structs.get(type_name) {
if let Some(f) = s
.fields
.iter()
.find(|f| yog_codegen::pascal(f.name) == name)
{
return Type::of_shape(&(f.shape)());
}
} else if let Some(e) = surface.enums.get(type_name) {
if e.variants
.iter()
.any(|v| yog_codegen::pascal(v.name) == name)
{
return Type::Yog(type_name.to_string());
}
} else {
let sub = super::surface::subgroup(type_name, name);
if surface.groups.contains_key(&sub) {
return Type::Yog(sub);
}
if let Some(args) = surface
.groups
.get(type_name)
.and_then(|g| g.events.get(name))
{
return Type::Fn(vec![Type::Yog(args.clone())], Box::new(Type::Void));
}
if surface
.groups
.get(type_name)
.is_some_and(|g| g.methods.contains_key(name))
{
return Type::Unknown;
}
}
self.unknown_member(&Type::Yog(type_name.to_string()), name, at);
Type::Unknown
}
fn unknown_member(&mut self, on: &Type, name: &str, at: Span) {
self.report.error(
script_problem::UNKNOWN_MEMBER,
at,
format!("`{on}` has no member `{name}`"),
);
}
pub(super) fn call(&mut self, callee: &Expr, args: &[Arg]) -> Type {
let target = self.callee(callee);
let given: Vec<Type> = args.iter().map(|a| self.expr(&a.value)).collect();
match target {
Callee::Own(methods, bound) => {
let sigs: Vec<Signature> =
self.with_types(bound, |c| methods.iter().map(|m| c.signature(m)).collect());
self.pick(&sigs, &given, args, callee.span)
}
Callee::Typed(sigs) => self.pick(&sigs, &given, args, callee.span),
Callee::Unknown => Type::Unknown,
}
}
fn callee(&mut self, callee: &Expr) -> Callee {
match &callee.kind {
ExprKind::Ident(name) if self.delegate(name).is_some() => {
let t = self.expr(callee);
self.types.set(callee, t);
self.delegate(name)
.map_or(Callee::Unknown, |sig| Callee::Typed(vec![sig]))
}
ExprKind::Member { expr, name, .. } if name.text == INVOKE => {
match self.expr(expr).non_null() {
Type::Fn(params, ret) => Callee::Typed(vec![signature_of(params, ret)]),
_ => Callee::Unknown,
}
}
ExprKind::Ident(name) if self.local(name).is_none() => {
let methods = self.callable(name);
if methods.is_empty() {
match self.variant_ctor(crate::system::RESULT, name) {
Callee::Unknown => self.static_import(name, callee.span),
made => made,
}
} else {
if let Some(Type::Own(of, _)) = self.this.clone()
&& self.own_methods(&Type::own(of.clone()), name).is_some()
{
self.called_on(None, &of, name, callee.span);
}
Callee::Own(methods, super::generics::Bound::new())
}
}
ExprKind::Member { expr, name, .. } => {
let on = self.expr(expr);
match on.non_null() {
Type::Own(of, _) | Type::Static(of) => {
match self.own_methods(&Type::own(of.clone()), &name.text) {
Some(methods) => {
if matches!(on.non_null(), Type::Own(..)) {
self.called_on(Some(expr), of, &name.text, name.span);
}
Callee::Own(methods, self.bound_of(of, own_args(on.non_null())))
}
None => self.variant_ctor(&of.clone(), &name.text),
}
}
Type::Yog(group) => match self.extension_methods(group, &name.text) {
Some(m) => Callee::Own(m, super::generics::Bound::new()),
None => self.yog_methods(group, &name.text, name.span),
},
Type::Game(class) => match self.extension_methods(class, &name.text) {
Some(m) => Callee::Own(m, super::generics::Bound::new()),
None if is_side_call(&name.text) => Callee::Unknown,
None => self.game_methods(class, &name.text, name.span),
},
_ => Callee::Unknown,
}
}
_ => {
self.expr(callee);
Callee::Unknown
}
}
}
pub(super) fn callable(&self, name: &str) -> Vec<Method> {
let in_this = self.this.clone().and_then(|t| match &t {
Type::Game(class) | Type::Yog(class) => self.extension_methods(class, name),
_ => self.own_methods(&t, name),
});
in_this
.or_else(|| self.own.methods.get(name).cloned())
.unwrap_or_default()
}
pub(super) fn signature_types(&mut self, m: &Method) -> (Vec<Type>, Type) {
let s = self.signature(m);
(s.params, s.ret)
}
fn own_methods(&self, t: &Type, name: &str) -> Option<Vec<Method>> {
let Type::Own(type_name, _) = t else {
return None;
};
self.own.lineage(type_name).iter().find_map(|of| {
let own = self.own.types.get(of)?;
own.methods.get(name).cloned().or_else(|| {
own.components
.iter()
.find_map(|c| self.own_methods(&Type::own(c.clone()), name))
})
})
}
fn delegate(&mut self, name: &str) -> Option<Signature> {
let t = match self.local(name) {
Some(t) => t.clone(),
None => self.this.clone().map(|this| self.own_member(&this, name))?,
};
match t.non_null() {
Type::Fn(params, ret) => Some(signature_of(params, ret)),
_ => None,
}
}
fn static_import(&mut self, name: &str, at: Span) -> Callee {
let statics = self.directives.statics.clone();
for path in statics {
let names: Vec<crate::ast::Name> = path
.iter()
.map(|text| crate::ast::Name {
text: text.clone(),
span: at,
})
.collect();
if let Type::Yog(group) = self.qualified(&names, at)
&& super::surface::SURFACE
.groups
.get(&group)
.is_some_and(|g| g.methods.contains_key(name))
{
return self.yog_methods(&group, name, at);
}
}
Callee::Unknown
}
fn yog_methods(&mut self, group: &str, name: &str, at: Span) -> Callee {
let surface = &*super::surface::SURFACE;
let Some(g) = surface.groups.get(group) else {
return Callee::Unknown;
};
let Some(methods) = g.methods.get(name) else {
self.unknown_member(&Type::Yog(group.to_string()), name, at);
return Callee::Unknown;
};
if !self.to_server
&& methods
.iter()
.any(|m| m.address == yog_ids::service_net::SEND_TO_SERVER)
{
self.report.error(
script_problem::UNDECLARED,
at,
"this mod sends to the server without saying so: `[network]` `to_server = true` in yog.toml (it then needs Yog on the server too)",
);
}
Callee::Typed(
methods
.iter()
.map(|m| Signature {
params: m
.params
.iter()
.map(|p| Type::of_shape(&(p.shape)()))
.collect(),
passes: vec![None; m.params.len()],
required: m.params.iter().take_while(|p| p.required).count(),
ret: Type::of_shape(&m.reply),
generics: Vec::new(),
})
.collect(),
)
}
fn game_methods(&mut self, class: &str, name: &str, at: Span) -> Callee {
let Some(lookup) = &self.lookup else {
return Callee::Unknown;
};
let Some(c) = lookup.class(class) else {
return Callee::Unknown;
};
match game::member(lookup, c, name, true) {
Found::Methods(sigs) => Callee::Typed(
sigs.into_iter()
.map(|(params, ret)| Signature {
passes: vec![None; params.len()],
required: params.len(),
params,
ret,
generics: Vec::new(),
})
.collect(),
),
Found::Nothing => {
self.unknown_member(&Type::Game(class.to_string()), name, at);
Callee::Unknown
}
_ => Callee::Unknown,
}
}
fn signature(&mut self, m: &Method) -> Signature {
let bound = super::generics::params_of(&m.generics);
let generics = bound.keys().cloned().collect();
self.with_types(bound, |c| Signature {
params: m.params.iter().map(|p| c.type_of(&p.ty)).collect(),
passes: m.params.iter().map(|p| p.pass).collect(),
required: m.params.iter().take_while(|p| p.default.is_none()).count(),
ret: m.ret.as_ref().map_or(Type::Void, |t| c.type_of(t)),
generics,
})
}
fn pick(&mut self, sigs: &[Signature], given: &[Type], args: &[Arg], at: Span) -> Type {
if sigs.is_empty() || args.iter().any(|a| a.name.is_some()) {
return Type::Unknown;
}
let takes = |s: &Signature| (s.required..=s.params.len()).contains(&given.len());
let fits = |s: &Signature| {
let mut found = super::generics::Bound::new();
let all = takes(s)
&& given
.iter()
.zip(&s.params)
.all(|(g, p)| self.unify(p, g, &s.generics, &mut found));
all.then_some(found)
};
if let Some((s, found)) = sigs.iter().find_map(|s| fits(s).map(|f| (s, f))) {
self.check_passing(&s.passes, args, given);
return s.ret.substitute(&found);
}
let taking: Vec<&Signature> = sigs.iter().filter(|s| takes(s)).collect();
match taking.as_slice() {
[] => {
self.report.error(
script_problem::WRONG_ARGS,
at,
format!("no overload takes {} arguments", given.len()),
);
Type::Unknown
}
[one] => {
for ((g, p), a) in given.iter().zip(&one.params).zip(args) {
self.want(g, p, a.value.span);
}
one.ret.clone()
}
_ => Type::Unknown,
}
}
}
fn is_side_call(name: &str) -> bool {
[&crate::words::client::WORD, &crate::words::server::WORD]
.iter()
.any(|w| yog_codegen::pascal(w.text) == name)
}
fn signature_of(params: &[Type], ret: &Type) -> Signature {
Signature {
params: params.to_vec(),
passes: vec![None; params.len()],
required: params.len(),
ret: ret.clone(),
generics: Vec::new(),
}
}
fn own_args(t: &Type) -> &[Type] {
match t {
Type::Own(_, args) => args,
_ => &[],
}
}
const INVOKE: &str = "Invoke";