use crate::diagnostics::{Diagnostic, Diagnostics, Error};
use crate::grammar::*;
pub fn validate_members(members: Vec<&impl Member>, diagnostics: &mut Diagnostics) {
tags_have_optional_types(members.clone(), diagnostics);
tagged_members_cannot_use_classes(members.clone(), diagnostics);
tags_are_unique(members.clone(), diagnostics);
}
fn tags_are_unique(members: Vec<&impl Member>, diagnostics: &mut Diagnostics) {
let mut sorted_tagged_members = members.into_iter().filter(|m| m.is_tagged()).collect::<Vec<_>>();
sorted_tagged_members.sort_by_key(|member| member.tag().expect("tagged member has no tag!"));
sorted_tagged_members.windows(2).for_each(|window| {
if window[0].tag() == window[1].tag() {
Diagnostic::new(Error::CannotHaveDuplicateTag {
identifier: window[1].identifier().to_owned(),
})
.set_span(window[1].span())
.add_note(
format!(
"The tag '{}' is already being used by member '{}'",
window[0].tag().unwrap(),
&window[0].identifier(),
),
Some(window[0].span()),
)
.push_into(diagnostics);
};
});
}
fn tags_have_optional_types(members: Vec<&impl Member>, diagnostics: &mut Diagnostics) {
let tagged_members = members.into_iter().filter(|member| member.is_tagged());
for member in tagged_members {
if !member.data_type().is_optional {
Diagnostic::new(Error::TaggedMemberMustBeOptional {
identifier: member.identifier().to_owned(),
})
.set_span(member.span())
.push_into(diagnostics);
}
}
}
fn tagged_members_cannot_use_classes(members: Vec<&impl Member>, diagnostics: &mut Diagnostics) {
fn uses_classes(typeref: &TypeRef) -> bool {
match typeref.definition().concrete_type() {
Types::Struct(struct_def) => struct_def.fields().iter().any(|m| uses_classes(&m.data_type)),
Types::Class(_) => true,
Types::Enum(_) => false,
Types::CustomType(_) => false,
Types::ResultType(_) => false, Types::Sequence(sequence) => uses_classes(&sequence.element_type),
Types::Dictionary(dictionary) => uses_classes(&dictionary.value_type),
Types::Primitive(primitive) => matches!(primitive, Primitive::AnyClass),
}
}
for member in members {
if member.is_tagged() && uses_classes(member.data_type()) {
let identifier = member.identifier().to_owned();
let error = if member.data_type().is_class_type() {
Error::CannotTagClass { identifier }
} else {
Error::CannotTagContainingClass { identifier }
};
Diagnostic::new(error).set_span(member.span()).push_into(diagnostics);
}
}
}