mod index;
use yog_ids::script_problem;
use crate::ast::Decl;
use index::{Index, Info};
use yog_analysis::Report;
pub(super) fn check(decls: &[Decl], report: &mut Report<'_>) {
let index = Index::collect(decls);
for info in index.types.values() {
bases(&index, info, report);
overrides(&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 Some(base) = index.types.get(b.text.as_str()) else {
continue;
};
if base.interface {
continue;
}
classes += 1;
let problem = if !info.class {
Some(format!(
"`{}` inherits interfaces only: `{}` is a class",
info.name, b.text
))
} else if classes > 1 {
Some(format!(
"`{}` inherits one class; `{}` is a second",
info.name, b.text
))
} else if i > 0 {
Some(format!(
"the base class `{}` comes first, before the interfaces",
b.text
))
} else if base.sealed {
Some(format!("`{}` is sealed: nothing inherits it", b.text))
} else {
None
};
if let Some(message) = problem {
report.error(script_problem::INHERIT, b.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.name)
.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 implemented(index: &Index, info: &Info, report: &mut Report<'_>) {
let lineage: Vec<&Info> = std::iter::once(info)
.chain(index.ancestors(info.name))
.collect();
let has = |name: &str| {
lineage
.iter()
.filter(|i| i.class)
.flat_map(|i| &i.members)
.any(|m| m.name == name && m.body)
};
for owner in &lineage {
for m in owner
.members
.iter()
.filter(|m| m.is_abstract || owner.interface && !m.body)
{
if !has(&m.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, _) = t else { return };
if self
.own
.types
.get(name)
.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"),
);
}
}
}