use std::collections::HashMap;
use crate::diagnostic::Diagnostic;
use crate::parser::phenotyper_actions as ast;
use super::{
AliasId, AliasInfo, EnumId, EnumInfo, FieldId, FieldInfo, ImportResolution, Requiredness,
Symbol, SymbolTable, TypeId, TypeInfo,
};
pub fn collect_symbols(
file_ast: &ast::File,
table: &mut SymbolTable,
file: &str,
diags: &mut Vec<Diagnostic>,
) {
if let Some(ref decls) = file_ast.ns.decls {
for decl in decls {
match decl {
ast::TopLevelDecl::TypeDecl(td) => {
collect_type_decl(td, file, diags, table);
}
ast::TopLevelDecl::TypeDef(td) => {
collect_type_def(td, file, diags, table);
}
}
}
}
}
fn warn_if_shadows(table: &SymbolTable, name: &str, file: &str, diags: &mut Vec<Diagnostic>) {
match table.imported(name) {
Some(ImportResolution::One(is)) => diags.push(super::warning(
file,
format!(
"local declaration `{name}` shadows the import from `{}`",
is.namespace
),
)),
Some(ImportResolution::Ambiguous(nss)) => diags.push(super::warning(
file,
format!(
"local declaration `{name}` shadows imports from {}",
nss.join(", ")
),
)),
None => {}
}
}
fn collect_type_decl(
decl: &ast::TypeDecl,
file: &str,
diags: &mut Vec<Diagnostic>,
table: &mut SymbolTable,
) {
let name = &decl.name;
match &decl.body {
ast::TypeDeclBody::Alias(_alias) => {
let alias_id = AliasId(table.aliases.len());
if table.names.contains_key(name) {
diags.push(super::error(file, format!("duplicate type name `{name}`")));
return;
}
warn_if_shadows(table, name, file, diags);
table.aliases.push(AliasInfo {
id: alias_id,
name: name.clone(),
});
table.names.insert(name.clone(), Symbol::Alias(alias_id));
}
ast::TypeDeclBody::Enum(enum_decl) => {
let enum_id = EnumId(table.enums.len());
if table.names.contains_key(name) {
diags.push(super::error(file, format!("duplicate type name `{name}`")));
return;
}
let members = flatten_enum_members(&enum_decl.members);
let mut seen_members = HashMap::new();
for member in &members {
if let Some(_prev) = seen_members.insert(member.clone(), ()) {
diags.push(super::error(
file,
format!("duplicate enum member `{member}` in enum `{name}`"),
));
}
}
warn_if_shadows(table, name, file, diags);
table.enums.push(EnumInfo {
id: enum_id,
name: name.clone(),
members,
});
table.names.insert(name.clone(), Symbol::Enum(enum_id));
}
}
}
fn collect_type_def(
def: &ast::TypeDef,
file: &str,
diags: &mut Vec<Diagnostic>,
table: &mut SymbolTable,
) {
let singular = &def.name;
let type_id = TypeId(table.types.len());
if table.names.contains_key(singular) {
diags.push(super::error(
file,
format!("duplicate type name `{singular}`"),
));
return;
}
let plural = def.plural_clause.as_ref().map(|pc| pc.plural_name.clone());
if let Some(ref plural_name) = plural {
if table.names.contains_key(plural_name) {
diags.push(super::error(
file,
format!("duplicate type name `{plural_name}` (plural companion of `{singular}`)"),
));
return;
}
if plural_name == singular {
diags.push(super::error(
file,
format!(
"singular name `{singular}` and plural name `{plural_name}` must be different"
),
));
return;
}
}
let mut fields = Vec::new();
let mut field_names: HashMap<String, ()> = HashMap::new();
let mut field_id_counter = 0usize;
for item in &def.items {
if let ast::BodyItem::Field(f) = item {
let field_name = &f.field.name;
if field_names.contains_key(field_name) {
diags.push(super::error(
file,
format!("duplicate field `{field_name}` in type `{singular}`"),
));
continue;
}
let req = match f.field.req {
ast::Requiredness::Req => Requiredness::Required,
ast::Requiredness::Opt => Requiredness::Optional,
};
fields.push(FieldInfo {
id: FieldId(field_id_counter),
name: field_name.clone(),
requiredness: req,
});
field_names.insert(field_name.clone(), ());
field_id_counter += 1;
}
}
warn_if_shadows(table, singular, file, diags);
table.types.push(TypeInfo {
id: type_id,
singular_name: singular.clone(),
plural_name: plural.clone(),
fields,
});
table
.names
.insert(singular.clone(), Symbol::Phenotype(type_id));
if let Some(plural_name) = plural {
warn_if_shadows(table, &plural_name, file, diags);
table
.names
.insert(plural_name, Symbol::PluralCompanion { type_id });
}
for item in &def.items {
if let ast::BodyItem::NestedType(nt) | ast::BodyItem::NestedTypePlural(nt) = item {
collect_type_def(&nt.nested, file, diags, table);
}
}
}
fn flatten_enum_members(members: &ast::EnumMembers) -> Vec<String> {
let mut result = Vec::new();
flatten_enum_members_inner(members, &mut result);
result
}
fn flatten_enum_members_inner(members: &ast::EnumMembers, out: &mut Vec<String>) {
match members {
ast::EnumMembers::Cons(cons) => {
out.push(cons.first.clone());
flatten_enum_members_inner(&cons.rest, out);
}
ast::EnumMembers::Single(single) => {
out.push(single.last.clone());
}
}
}