use std::collections::HashMap;
use yog_ids::script_problem;
use super::Checker;
use super::context::Context;
use super::generics::Bound;
use super::model::Type;
use crate::ast::{self, Constraint, Method, Where};
use crate::source::token::Span;
#[derive(Debug, Clone, Default)]
pub(super) struct Bounds {
pub types: Vec<ast::Type>,
pub class: bool,
pub value: bool,
pub new: bool,
}
pub(super) type Constraints = HashMap<String, Bounds>;
pub(super) fn constraints_of(wheres: &[Where]) -> Constraints {
let mut out = Constraints::new();
for w in wheres {
let b: &mut Bounds = out.entry(w.name.text.clone()).or_default();
for c in &w.bounds {
match c {
Constraint::Class => b.class = true,
Constraint::Struct => b.value = true,
Constraint::New => b.new = true,
Constraint::Type(t) => b.types.push(t.clone()),
}
}
}
out
}
impl Checker<'_, '_, '_> {
pub(super) fn with_constraints<R>(
&mut self,
wheres: &[Where],
f: impl FnOnce(&mut Self) -> R,
) -> R {
self.constraints.push(constraints_of(wheres));
let out = f(self);
self.constraints.pop();
out
}
fn bounds_of(&self, p: &str) -> Option<Bounds> {
self.constraints
.iter()
.rev()
.find_map(|c| c.get(p))
.cloned()
}
pub(super) fn bound_types(&mut self, p: &str) -> Vec<Type> {
let written = self.bounds_of(p).map(|b| b.types).unwrap_or_default();
written.iter().map(|t| self.type_of(t)).collect()
}
pub(super) fn param_member(&mut self, p: &str, name: &str) -> Option<(Type, Type)> {
self.bound_types(p).into_iter().find_map(|b| {
let t = self.own_member(&b, name);
t.known().then_some((b, t))
})
}
pub(super) fn param_methods(
&mut self,
p: &str,
name: &str,
) -> Option<(Type, Vec<(Method, Context)>)> {
self.bound_types(p)
.into_iter()
.find_map(|b| self.own_methods(&b, name).map(|m| (b, m)))
}
pub(super) fn param_made(&mut self, p: &str, args: usize, at: Span) {
if args > 0 || !self.bounds_of(p).is_some_and(|b| b.new || b.value) {
self.report.error(
script_problem::CONSTRAINT,
at,
format!("`new {p}()` needs `where {p} : new()`, and takes no arguments"),
);
}
}
pub(super) fn type_meets(&mut self, t: &Type, at: Span) {
let Type::Own(name, args) = t else {
return;
};
let Some(own) = self
.own
.get(name, args.len())
.filter(|o| !o.wheres.is_empty())
else {
return;
};
let (generics, wheres) = (own.generics.clone(), own.wheres.clone());
self.meets((&generics, &wheres), args, at);
}
pub(super) fn call_meets(&mut self, used: &super::types::MethodUse, at: Span) {
if used.type_args.is_empty() {
return;
}
let methods = match &used.owner {
Some(o) => self
.own
.of_key(&super::own::TypeKey::new(o, used.owner_arity))
.and_then(|t| t.methods.get(&used.name)),
None => self.own.methods.get(&used.name),
};
let Some(m) = methods
.and_then(|m| m.get(used.overload))
.filter(|m| !m.constraints.is_empty())
else {
return;
};
let generics: Vec<String> = m
.generics
.iter()
.filter(|g| !g.lifetime)
.map(|g| g.name.text.clone())
.collect();
let wheres = m.constraints.clone();
self.meets((&generics, &wheres), &used.type_args.clone(), at);
}
fn meets(&mut self, (generics, wheres): (&[String], &[Where]), args: &[Type], at: Span) {
if wheres.is_empty() || self.meeting {
return;
}
self.meeting = true;
self.meets_each((generics, wheres), args, at);
self.meeting = false;
}
fn meets_each(&mut self, (generics, wheres): (&[String], &[Where]), args: &[Type], at: Span) {
let bound: Bound = generics.iter().cloned().zip(args.iter().cloned()).collect();
for (p, b) in constraints_of(wheres) {
let Some(arg) = generics
.iter()
.position(|g| *g == p)
.and_then(|i| args.get(i))
else {
continue;
};
if !arg.known() || matches!(arg, Type::Param(_)) {
continue;
}
let wanted: Vec<Type> = self.with_types(bound.clone(), |c| {
b.types.iter().map(|t| c.type_of(t)).collect()
});
if let Some(why) = self.unmet(arg, &b, &wanted) {
self.report.error(
script_problem::CONSTRAINT,
at,
format!("`{arg}` can't be `{p}`: {why}"),
);
}
}
}
fn unmet(&self, arg: &Type, b: &Bounds, wanted: &[Type]) -> Option<String> {
let reference = self.is_reference(arg.non_null()) || matches!(arg.non_null(), Type::Fn(..));
if b.class && !reference {
return Some("it must be a class (`where … : class`)".into());
}
if b.value && reference {
return Some("it must be a value (`where … : struct`)".into());
}
if b.new && !self.makes_empty(arg) {
return Some("it must be made with no arguments (`where … : new()`)".into());
}
let host = self.hosted(arg.non_null());
let missing = wanted.iter().find(|w| {
w.known()
&& !self.converts(arg, w)
&& !host.as_ref().is_some_and(|h| self.converts(h, w))
})?;
Some(format!("it isn't a `{missing}`"))
}
fn makes_empty(&self, t: &Type) -> bool {
let Type::Own(name, args) = t else {
return !self.is_reference(t);
};
let Some(own) = self.own.get(name, args.len()) else {
return true;
};
if !own.heap {
return true;
}
!own.is_abstract
&& !own.interface
&& (own.ctors.iter().any(Vec::is_empty) || own.ctors.is_empty() && own.ctor.is_empty())
}
}
impl Checker<'_, '_, '_> {
pub(super) fn object_member(&mut self, t: &Type, name: &str, at: Span) -> Type {
let object = Type::own(yog_script_ids::clr::OBJECT);
let found = self.own_member(&object, name);
if !found.known() && !self.own_has_method(&object, name) {
self.unknown_member(t, name, at);
}
found
}
pub(super) fn object_methods(&mut self, t: &Type, name: &ast::Name) -> super::members::Callee {
match self.own_methods(&Type::own(yog_script_ids::clr::OBJECT), &name.text) {
Some(methods) => super::members::Callee::Own(methods),
None => self.extension_or_unknown(t, name),
}
}
}