use crate::diagnostic::Diagnostic;
use crate::parser::phenotyper_actions as ast;
use super::{PrimitiveType, ResolvedSymbol, Symbol, SymbolTable, TypeId};
pub fn resolve_references(
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 {
if let ast::TopLevelDecl::TypeDef(td) = decl {
resolve_type_def(td, table, file, diags);
}
if let ast::TopLevelDecl::TypeDecl(td) = decl {
resolve_type_decl(td, table, file, diags);
}
}
}
}
fn resolve_type_decl(
decl: &ast::TypeDecl,
table: &SymbolTable,
file: &str,
diags: &mut Vec<Diagnostic>,
) {
if let ast::TypeDeclBody::Alias(alias) = &decl.body {
resolve_type_expr(&alias.alias, &decl.name, table, file, diags);
}
}
fn resolve_type_def(
def: &ast::TypeDef,
table: &SymbolTable,
file: &str,
diags: &mut Vec<Diagnostic>,
) {
let type_name = &def.name;
let type_id = match table.resolve(type_name) {
Some(Symbol::Phenotype(id)) => Some(*id),
_ => None,
};
for item in &def.items {
match item {
ast::BodyItem::Field(f) => {
resolve_type_expr(&f.field.type_expr, type_name, table, file, diags);
}
ast::BodyItem::Render(r) => {
resolve_render_expr(&r.render, type_name, type_id, table, file, diags);
}
ast::BodyItem::NestedType(nt) | ast::BodyItem::NestedTypePlural(nt) => {
resolve_type_def(&nt.nested, table, file, diags);
}
}
}
}
fn resolve_type_expr(
expr: &ast::TypeExpr,
context_type: &str,
table: &SymbolTable,
file: &str,
diags: &mut Vec<Diagnostic>,
) {
match expr {
ast::TypeExpr::Simple(simple) => {
resolve_type_name(&simple.name, context_type, table, file, diags);
}
ast::TypeExpr::Cardinalized(card) => {
resolve_type_name(&card.base, context_type, table, file, diags);
}
ast::TypeExpr::Union(union) => {
for member in &union.members {
resolve_type_name(member, context_type, table, file, diags);
}
}
}
}
fn resolve_type_name(
name: &ast::TypeName,
context_type: &str,
table: &SymbolTable,
file: &str,
diags: &mut Vec<Diagnostic>,
) {
match name {
ast::TypeName::String
| ast::TypeName::Int64
| ast::TypeName::Real64
| ast::TypeName::Bool
| ast::TypeName::Date
| ast::TypeName::Time
| ast::TypeName::DateTime => {}
ast::TypeName::UserDefined(ud) => match table.resolve_any(&ud.name) {
Some(ResolvedSymbol::Local(_) | ResolvedSymbol::Imported(_)) => {}
Some(ResolvedSymbol::Ambiguous(namespaces)) => {
diags.push(super::error(
file,
format!(
"ambiguous type `{}` referenced in `{context_type}`: imported from {} \
(REQ-LANG-003) — declare it in one namespace, or drop the other `uses`",
ud.name,
namespaces.join(" and ")
),
));
}
None => {
diags.push(super::error(
file,
format!("unknown type `{}` referenced in `{context_type}`", ud.name),
));
}
},
}
}
fn resolve_render_expr(
expr: &ast::RenderExpr,
type_name: &str,
type_id: Option<TypeId>,
table: &SymbolTable,
file: &str,
diags: &mut Vec<Diagnostic>,
) {
match expr {
ast::RenderExpr::FieldRef(fr) => {
if fr.ref_path.segments.len() == 1 {
if let Some(field_name) = fr.ref_path.segments.last() {
check_field_ref(field_name, type_name, type_id, table, file, diags);
}
}
}
ast::RenderExpr::Directive(d) => {
resolve_directive_suffix(&d.suffix, type_name, type_id, table, file, diags);
}
ast::RenderExpr::BareDirective(_) => {
}
ast::RenderExpr::StringLit(_) => {
}
ast::RenderExpr::ConditionalRef(cr) => {
if cr.ref_path.segments.len() == 1 {
if let Some(field_name) = cr.ref_path.segments.last() {
check_field_ref(field_name, type_name, type_id, table, file, diags);
}
}
if let Some(ref block) = cr.block {
for item in &block.items {
resolve_render_expr(item, type_name, type_id, table, file, diags);
}
}
}
ast::RenderExpr::ConditionalDirective(cd) => {
resolve_directive_suffix(&cd.suffix, type_name, type_id, table, file, diags);
if let Some(ref block) = cd.block {
for item in &block.items {
resolve_render_expr(item, type_name, type_id, table, file, diags);
}
}
}
}
}
fn resolve_directive_suffix(
suffix: &ast::DirectiveSuffix,
type_name: &str,
type_id: Option<TypeId>,
table: &SymbolTable,
file: &str,
diags: &mut Vec<Diagnostic>,
) {
match suffix {
ast::DirectiveSuffix::WithArgs(wa) => {
for arg in &wa.args {
if let ast::Argument::IdentArg(id_arg) = arg {
check_field_ref(&id_arg.val, type_name, type_id, table, file, diags);
}
}
if let Some(ref block) = wa.block {
for item in &block.items {
resolve_render_expr(item, type_name, type_id, table, file, diags);
}
}
}
ast::DirectiveSuffix::EmptyParen(ep) => {
if let Some(ref block) = ep.block {
for item in &block.items {
resolve_render_expr(item, type_name, type_id, table, file, diags);
}
}
}
}
}
fn check_field_ref(
field_name: &str,
type_name: &str,
type_id: Option<TypeId>,
table: &SymbolTable,
file: &str,
diags: &mut Vec<Diagnostic>,
) {
if let Some(tid) = type_id {
if table.resolve_field(tid, field_name).is_none() {
diags.push(super::error(
file,
format!("unknown field `{field_name}` in type `{type_name}`"),
));
}
}
}
#[allow(dead_code)]
pub fn as_primitive(name: &ast::TypeName) -> Option<PrimitiveType> {
match name {
ast::TypeName::String => Some(PrimitiveType::String),
ast::TypeName::Int64 => Some(PrimitiveType::Int64),
ast::TypeName::Real64 => Some(PrimitiveType::Real64),
ast::TypeName::Bool => Some(PrimitiveType::Bool),
ast::TypeName::Date => Some(PrimitiveType::Date),
ast::TypeName::Time => Some(PrimitiveType::Time),
ast::TypeName::DateTime => Some(PrimitiveType::DateTime),
ast::TypeName::UserDefined(_) => None,
}
}