mod index;
use yog_ids::script_problem;
use crate::ast::Decl;
use index::{Index, Info};
use yog_analysis::Report;
pub(super) fn check<'d>(
(library, others): (&'d [Decl], &[&'d [Decl]]),
decls: &'d [Decl],
report: &mut Report<'_>,
) {
let mut index = Index::collect(library);
for other in others {
index.add(other);
}
index.mark_library();
index.add(decls);
for info in index.types.values().filter(|i| !i.library) {
bases(&index, info, report);
overrides(&index, info, report);
explicit(&index, info, report);
if info.class && !info.is_abstract {
implemented(&index, info, report);
}
}
}
fn bases(index: &Index, info: &Info, report: &mut Report<'_>) {
let mut classes = 0;
for (i, b) in info.bases.iter().enumerate() {
let name = &b.0;
let Some(base) = index.base(b) else {
continue;
};
if base.interface {
if base.readonly && !info.readonly {
report.error(
script_problem::INHERIT,
name.span,
format!(
"`{}` is shared, so what implements it is `readonly` too",
name.text
),
);
}
continue;
}
classes += 1;
let problem = if !info.class {
Some(format!(
"`{}` inherits interfaces only: `{}` is a class",
info.name, name.text
))
} else if classes > 1 {
Some(format!(
"`{}` inherits one class; `{}` is a second",
info.name, name.text
))
} else if i > 0 {
Some(format!(
"the base class `{}` comes first, before the interfaces",
name.text
))
} else if base.sealed {
Some(format!("`{}` is sealed: nothing inherits it", name.text))
} else if base.readonly && !info.readonly {
Some(format!(
"`{}` is never changed, so shared: what inherits it is `readonly` too",
name.text
))
} else {
None
};
if let Some(message) = problem {
report.error(script_problem::INHERIT, name.span, message);
}
}
}
fn overrides(index: &Index, info: &Info, report: &mut Report<'_>) {
for m in info.members.iter().filter(|m| m.overrides) {
let replaced = index
.ancestors(info)
.iter()
.flat_map(|a| &a.members)
.any(|b| b.name == m.name && (b.is_abstract || b.is_virtual || b.overrides));
if !replaced {
report.error(
script_problem::INHERIT,
m.span,
format!(
"`{}` overrides nothing: no base has a `virtual` or `abstract` `{}`",
m.name, m.name
),
);
}
}
}
fn explicit(index: &Index, info: &Info, report: &mut Report<'_>) {
for m in &info.members {
let Some(of) = &m.explicit else {
continue;
};
let ancestors = index.ancestors(info);
let problem = match ancestors.iter().find(|a| a.name == of) {
None => Some(format!("`{}` doesn't implement `{of}`", info.name)),
Some(a) if !a.interface => Some(format!("`{of}` isn't an interface")),
Some(a) if !a.members.iter().any(|b| b.name == m.name) => {
Some(format!("`{of}` has no `{}` to implement", m.name))
}
Some(_) if m.access => Some(format!(
"`{of}.{}` is reached through `{of}` only: it says no access of its own",
m.name
)),
Some(_) => None,
};
if let Some(message) = problem {
report.error(script_problem::INHERIT, m.span, message);
}
}
}
fn implemented(index: &Index, info: &Info, report: &mut Report<'_>) {
let lineage: Vec<&Info> = std::iter::once(info).chain(index.ancestors(info)).collect();
let has = |name: &str, of: &str| {
lineage
.iter()
.filter(|i| i.class)
.flat_map(|i| i.members.iter().map(move |m| (i, m)))
.filter(|(_, m)| m.explicit.as_deref().is_none_or(|e| e == of))
.any(|(i, m)| m.name == name && (m.body || i.intrinsic))
};
for owner in &lineage {
for m in owner
.members
.iter()
.filter(|m| m.is_abstract || owner.interface && !m.body)
{
if !has(&m.name, owner.name) {
report.error(
script_problem::INHERIT,
info.span,
format!(
"`{}` doesn't implement `{}` of `{}`",
info.name, m.name, owner.name
),
);
}
}
}
}
impl super::Checker<'_, '_, '_> {
pub(super) fn made(&mut self, t: &super::Type, at: crate::source::token::Span) {
let super::Type::Own(name, args) = t else {
return;
};
if self
.own
.get(name, args.len())
.is_some_and(|o| o.is_abstract || o.interface)
{
self.report.error(
script_problem::INHERIT,
at,
format!("`{name}` is abstract: make a class that inherits it"),
);
}
}
}