use crate::diagnostics::{Diagnostic, Diagnostics, Error};
use crate::grammar::*;
pub fn validate_dictionary(dictionary: &Dictionary, diagnostics: &mut Diagnostics) {
has_allowed_key_type(dictionary, diagnostics);
}
fn has_allowed_key_type(dictionary: &Dictionary, diagnostics: &mut Diagnostics) {
if let Some(e) = check_dictionary_key_type(&dictionary.key_type) {
e.push_into(diagnostics)
}
}
fn check_dictionary_key_type(type_ref: &TypeRef) -> Option<Diagnostic> {
if type_ref.is_optional {
return Some(Diagnostic::new(Error::KeyMustBeNonOptional).set_span(type_ref.span()));
}
let definition = type_ref.definition();
let is_valid = match definition.concrete_type() {
Types::Struct(struct_def) => {
if !struct_def.is_compact {
return Some(Diagnostic::new(Error::StructKeyMustBeCompact).set_span(type_ref.span()));
}
let errors = struct_def
.fields()
.into_iter()
.filter_map(|field| check_dictionary_key_type(field.data_type()))
.collect::<Vec<_>>();
if !errors.is_empty() {
let mut error = Diagnostic::new(Error::StructKeyContainsDisallowedType {
struct_identifier: struct_def.identifier().to_owned(),
})
.set_span(type_ref.span());
for e in errors {
error = error.add_note(e.message(), e.span());
}
return Some(error);
}
true
}
Types::Enum(enum_def) => {
if enum_def.underlying.is_none() {
let error = Diagnostic::new(Error::KeyTypeNotSupported {
kind: formatted_kind(definition),
})
.set_span(type_ref.span())
.add_note("only enums with underlying types can be used as dictionary keys", None);
return Some(error);
}
true
}
Types::Class(_) => false,
Types::CustomType(_) => true,
Types::ResultType(_) => false,
Types::Sequence(_) => false,
Types::Dictionary(_) => false,
Types::Primitive(primitive) => {
primitive.is_integral() || matches!(primitive, Primitive::Bool | Primitive::String)
}
};
if !is_valid {
return Some(
Diagnostic::new(Error::KeyTypeNotSupported {
kind: formatted_kind(definition),
})
.set_span(type_ref.span()),
);
}
None
}
fn formatted_kind(definition: &dyn Type) -> String {
match definition.concrete_type() {
Types::Class(class_def) => format!("class '{}'", class_def.identifier()),
Types::Enum(enum_def) => format!("enum '{}'", enum_def.identifier()),
_ => definition.kind().to_owned(),
}
}