use yog_ids::script_problem;
use super::Checker;
use super::generics::Bound;
use super::members::Signature;
use super::model::Type;
use crate::ast::{Arg, Expr, ExprKind};
use crate::source::token::Span;
pub(super) struct Chosen {
pub index: usize,
pub params: Vec<Type>,
pub type_args: Vec<Type>,
pub rest_from: Option<usize>,
}
fn expanded(s: &Signature, count: usize) -> Option<Signature> {
let fixed = s.params.len().checked_sub(1)?;
if !s.rest || count < fixed {
return None;
}
let Type::Array(item, 1) = &s.params[fixed] else {
return None;
};
let mut params = s.params[..fixed].to_vec();
params.extend(std::iter::repeat_n((**item).clone(), count - fixed));
let mut passes = s.passes[..fixed.min(s.passes.len())].to_vec();
passes.resize(count, None);
Some(Signature {
params,
passes,
required: count,
ret: s.ret.clone(),
generics: s.generics.clone(),
bound: s.bound.clone(),
rest: false,
})
}
impl Checker<'_, '_, '_> {
pub(super) fn pick(
&mut self,
sigs: &[Signature],
given: &[Type],
args: &[&Arg],
at: Span,
) -> (Type, Option<Chosen>) {
if sigs.is_empty() || args.iter().any(|a| a.name.is_some()) {
self.lambdas_alone(args);
return (Type::Unknown, None);
}
let as_written = |s: &Signature| !s.rest || given.len() == s.params.len();
let written: Vec<_> = (0..sigs.len())
.filter(|&i| as_written(&sigs[i]))
.filter_map(|i| self.fit(&sigs[i], given, args).map(|f| (i, f, None)))
.collect();
let fits = if written.is_empty() {
(0..sigs.len())
.filter_map(|i| {
let x = expanded(&sigs[i], given.len())?;
self.fit(&x, given, args).map(|f| (i, f, Some(x)))
})
.collect()
} else {
written
};
let fitting = self.best(sigs, fits, given);
if let Some((i, found, expanded)) = fitting {
let rest_from = expanded.as_ref().map(|_| sigs[i].params.len() - 1);
let s = expanded.as_ref().unwrap_or(&sigs[i]);
self.check_passing(&s.passes, args, given);
let params: Vec<Type> = s.params.iter().map(|p| p.substitute(&found)).collect();
for ((arg, g), p) in args.iter().zip(given).zip(¶ms) {
let group = self.is_group(&arg.value);
if let ExprKind::Declare {
ty: None,
name,
mutable,
} = &arg.value.kind
{
self.bind_as(&name.text, p.clone(), !mutable);
self.types.set(&arg.value, p.clone());
} else if is_lambda(&arg.value)
|| is_collection(&arg.value)
|| group
|| (super::exprs::is_number_literal(&arg.value) && p.non_null().is_numeric())
{
self.fits(&arg.value, p);
} else {
self.note_conversion(&arg.value, g, p);
}
}
let ret = s.ret.substitute(&found);
let type_args = s
.generics
.iter()
.map(|g| found.get(g).cloned().unwrap_or(Type::Unknown))
.collect();
return (
ret,
Some(Chosen {
index: i,
params,
type_args,
rest_from,
}),
);
}
self.none_fits(sigs, given, args, at)
}
fn fit(&mut self, s: &Signature, given: &[Type], args: &[&Arg]) -> Option<Bound> {
if !(s.required..=s.params.len()).contains(&given.len()) {
return None;
}
let mut found = s.bound.clone();
let groups: Vec<bool> = args.iter().map(|a| self.is_group(&a.value)).collect();
for ((g, p), a) in given.iter().zip(&s.params).zip(args) {
if is_collection(&a.value) && collection_target(p) {
continue;
}
if !is_lambda(&a.value) && !self.unify(p, g, &s.generics, &mut found) {
return None;
}
}
for ((g, p), a) in given.iter().zip(&s.params).zip(args) {
if !self.takes(g, &p.substitute(&found), a) {
return None;
}
}
for ((p, a), _) in s
.params
.iter()
.zip(args)
.zip(&groups)
.filter(|((_, a), group)| is_lambda(&a.value) || **group)
{
let Some((params, ret)) = self.lambda_sig(&p.substitute(&found)) else {
continue;
};
if !takes_as_many(&a.value, params.len()) {
return None;
}
if params.iter().any(open) {
continue;
}
let got = match self.group_result(&a.value, ¶ms) {
Some(given) => given,
None => self.quietly(|c| c.lambda_result(&a.value, ¶ms)),
};
if got.known() && !self.unify(&ret, &got, &s.generics, &mut found) {
return None;
}
}
Some(found)
}
pub(super) fn fits_any(&mut self, sigs: &[Signature], given: &[Type], args: &[&Arg]) -> bool {
sigs.iter().any(|s| {
let as_written = !s.rest || given.len() == s.params.len();
(as_written && self.fit(s, given, args).is_some())
|| expanded(s, given.len()).is_some_and(|x| self.fit(&x, given, args).is_some())
})
}
fn takes(&mut self, g: &Type, p: &Type, a: &Arg) -> bool {
let typed_by_it = is_lambda(&a.value)
|| is_collection(&a.value)
|| self.is_group(&a.value)
|| matches!(a.value.kind, ExprKind::Declare { .. })
|| (super::exprs::is_number_literal(&a.value) && p.non_null().is_numeric());
typed_by_it || !g.known() || !p.known() || self.converts(g, p)
}
fn none_fits(
&mut self,
sigs: &[Signature],
given: &[Type],
args: &[&Arg],
at: Span,
) -> (Type, Option<Chosen>) {
let takes = |s: &Signature| {
(s.required..=s.params.len()).contains(&given.len())
|| (s.rest && given.len() + 1 >= s.params.len())
};
let taking: Vec<usize> = (0..sigs.len()).filter(|&i| takes(&sigs[i])).collect();
match taking.as_slice() {
[] => {
self.lambdas_alone(args);
self.report.error(
script_problem::WRONG_ARGS,
at,
format!("no overload takes {} arguments", given.len()),
);
(Type::Unknown, None)
}
&[i] => {
let one = &sigs[i];
for ((g, p), a) in given.iter().zip(&one.params).zip(args) {
if is_lambda(&a.value) || is_collection(&a.value) || self.is_group(&a.value) {
self.fits(&a.value, p);
} else {
self.want(g, p, a.value.span);
}
}
let params = one.params.clone();
let type_args = one
.generics
.iter()
.map(|g| one.bound.get(g).cloned().unwrap_or(Type::Unknown))
.collect();
(
one.ret.clone(),
Some(Chosen {
index: i,
params,
type_args,
rest_from: None,
}),
)
}
_ => {
self.lambdas_alone(args);
let types: Vec<String> = given.iter().map(ToString::to_string).collect();
self.report.error(
script_problem::TYPE_MISMATCH,
at,
format!("no overload takes these: ({})", types.join(", ")),
);
(Type::Unknown, None)
}
}
}
pub(super) fn lambdas_alone(&mut self, args: &[&Arg]) {
for a in args.iter().filter(|a| is_lambda(&a.value)) {
self.expr(&a.value);
}
}
}
pub(super) fn is_lambda(e: &Expr) -> bool {
matches!(e.kind, ExprKind::Lambda { .. })
}
fn open(t: &Type) -> bool {
match t {
Type::Param(_) => true,
Type::Own(_, args) | Type::Tuple(args, _) => args.iter().any(open),
Type::Array(item, _) | Type::Nullable(item) => open(item),
Type::Fn(params, ret) => params.iter().any(open) || open(ret),
_ => false,
}
}
fn takes_as_many(e: &Expr, count: usize) -> bool {
match &e.kind {
ExprKind::Lambda { params, .. } if params.is_empty() => count <= 1,
ExprKind::Lambda { params, .. } => params.len() == count,
_ => true,
}
}
fn is_collection(e: &Expr) -> bool {
matches!(e.kind, ExprKind::Collection(_))
}
fn collection_target(p: &Type) -> bool {
matches!(p.non_null(), Type::Array(..) | Type::Own(..))
}