#![cfg_attr(rustfmt, rustfmt_skip)] use slicec::grammar::Attribute as GrammarAttribute;
use slicec::grammar::CustomType as GrammarCustomType;
use slicec::grammar::Definition as GrammarDefinition;
use slicec::grammar::Dictionary as GrammarDictionary;
use slicec::grammar::DocComment as GrammarDocComment;
use slicec::grammar::Enum as GrammarEnum;
use slicec::grammar::Enumerator as GrammarEnumerator;
use slicec::grammar::Field as GrammarField;
use slicec::grammar::Identifier as GrammarIdentifier;
use slicec::grammar::Interface as GrammarInterface;
use slicec::grammar::MessageComponent as GrammarMessageComponent;
use slicec::grammar::Operation as GrammarOperation;
use slicec::grammar::Parameter as GrammarParameter;
use slicec::grammar::ResultType as GrammarResultType;
use slicec::grammar::Sequence as GrammarSequence;
use slicec::grammar::Struct as GrammarStruct;
use slicec::grammar::Types as GrammarTypes;
use slicec::grammar::TypeAlias as GrammarTypeAlias;
use slicec::grammar::TypeRef as GrammarTypeRef;
use slicec::slice_file::SliceFile as GrammarSliceFile;
use slicec::grammar::{Attributable, Commentable, Contained, Entity, Member, NamedSymbol, Type};
use slicec::grammar::attributes::{Allow, Compress, Deprecated, Oneway, SlicedFormat, Unparsed};
use slicec::diagnostics::Diagnostic as SlicecDiagnostic;
use slicec::diagnostics::DiagnosticKind as SlicecDiagnosticKind;
use slicec::diagnostics::Error as SlicecError;
use slicec::diagnostics::Lint as SlicecLint;
use slicec::ast::Ast;
use slicec::diagnostics::Diagnostics;
use slicec::slice_file::Span;
use crate::definition_types::*;
use std::fmt::Write;
const CRITICAL_FLAW_STRING: &str = "this indicates a critical flaw in the plugin that generated this diagnostic";
pub fn convert_diagnostic(diagnostic: Diagnostic, ast: &Ast, files: &[GrammarSliceFile], output: &mut Diagnostics) {
let kind = convert_diagnostic_kind(diagnostic.kind);
let notes = diagnostic.notes.into_iter().map(|note| {
let DiagnosticNote {message, source} = note;
let (span, _) = convert_source(source.as_deref(), ast, files, output);
slicec::diagnostics::Note { message, span }
}).collect();
let (span, scope) = convert_source(diagnostic.source.as_deref(), ast, files, output);
let converted_diagnostic = slicec::diagnostics::Diagnostic { kind, span, scope, notes, plugin: None };
output.push(converted_diagnostic);
}
fn convert_diagnostic_kind(kind: DiagnosticKind) -> SlicecDiagnosticKind {
match kind {
DiagnosticKind::Info { message } => SlicecDiagnosticKind::Info(message),
DiagnosticKind::Warning { message } => SlicecDiagnosticKind::Lint(SlicecLint::Other { message }),
DiagnosticKind::Error { message } => SlicecDiagnosticKind::Error(SlicecError::Other { message }),
DiagnosticKind::InvalidAttribute { directive }
=> SlicecDiagnosticKind::Error(SlicecError::InvalidAttribute { directive }),
DiagnosticKind::UnknownAttribute { directive }
=> SlicecDiagnosticKind::Error(SlicecError::UnknownAttribute { directive }),
DiagnosticKind::MissingRequiredAttribute { expected_attribute }
=> SlicecDiagnosticKind::Error(SlicecError::MissingRequiredAttribute { expected_attribute }),
DiagnosticKind::AttributeIsNotRepeatable { directive }
=> SlicecDiagnosticKind::Error(SlicecError::AttributeIsNotRepeatable { directive }),
DiagnosticKind::InvalidAttributeArgument { directive, argument }
=> SlicecDiagnosticKind::Error(SlicecError::InvalidAttributeArgument { directive, argument }),
DiagnosticKind::IncorrectAttributeArgumentCount { directive, min_expected, max_expected, actual_count } => {
let min_expected = min_expected as usize;
let max_expected = if max_expected == u8::MAX { usize::MAX - 1 } else { max_expected as usize };
SlicecDiagnosticKind::Error(SlicecError::IncorrectAttributeArgumentCount {
directive,
expected_count: min_expected..(max_expected + 1),
actual_count: actual_count as usize,
})
}
DiagnosticKind::Unknown { discriminant, fields_payload } => {
let mut message = format!("received an unknown diagnostic with a code of '{discriminant}'");
if !fields_payload.is_empty() {
write!(message, " and field-payload of:\n{fields_payload:?}").unwrap();
}
SlicecDiagnosticKind::Error(SlicecError::Other { message})
}
}
}
fn convert_source(source: Option<&str>, ast: &Ast, files: &[GrammarSliceFile], output: &mut Diagnostics) -> (Option<Span>, Option<String>) {
let Some(source) = source else {
return (None, None);
};
let attributable: &dyn Attributable;
let scope: Option<String>;
let default_span: Option<Span>;
let (symbol_id, extension) = if let Some((symbol_id, extension)) = source.split_once("::$") {
(symbol_id, Some(extension))
} else {
(source, None)
};
if let Some(diagnostic_file_path) = symbol_id.strip_prefix('#') {
let slice_file = match files.iter().find(|file| file.relative_path == diagnostic_file_path) {
Some(file) => file,
None => {
let message = format!("no file with the relative path '{diagnostic_file_path}' was parsed by 'slicec'");
SlicecDiagnostic::from_error(SlicecError::Other { message })
.add_note(CRITICAL_FLAW_STRING, None)
.push_into(output);
return (None, None);
}
};
attributable = slice_file;
scope = None;
default_span = Some(Span::new((1, 1).into(), (1, 1).into(), &slice_file.relative_path));
} else {
let named_symbol = match ast.find_element::<dyn NamedSymbol>(symbol_id) {
Ok(named_symbol) => named_symbol,
Err(err) => {
SlicecDiagnostic::from_error(err.into())
.add_note(CRITICAL_FLAW_STRING, None)
.push_into(output);
return (None, None);
}
};
attributable = named_symbol;
scope = Some(named_symbol.parser_scoped_identifier());
default_span = Some(named_symbol.span().clone());
}
let span = match extension {
None => default_span,
Some(ext) if ext.starts_with("attributes::") => get_attribute_span(attributable, ext, output),
Some(unknown) => {
let message = format!("the diagnostic source '{source}' has an unrecognized extension '{unknown}'");
let error = SlicecDiagnostic::from_error(SlicecError::Other { message });
output.push(error);
default_span
}
};
(span, scope)
}
fn get_attribute_span<T: Attributable + ?Sized>(symbol: &T, extension: &str, output: &mut Diagnostics) -> Option<Span> {
let indices = extension.split("::").collect::<Vec<_>>();
assert!(indices.len() > 1 && indices[0] == "attributes");
let (attribute_index_str, argument_index_str) = match &indices[1..] {
[i] => (i.parse::<usize>(), None),
[i, j] => (i.parse::<usize>(), Some(j.parse::<usize>())),
[] => unreachable!("'get_attribute_span' had 0 indices despite asserting that there was at least 1!"),
_ => {
let message = format!("{} indices were supplied to the '$attributes' diagnostic source", indices.len() - 1);
let error = SlicecDiagnostic::from_error(SlicecError::Other { message })
.add_note(CRITICAL_FLAW_STRING, None);
output.push(error);
return None;
}
};
let Ok(attribute_index) = attribute_index_str else {
let message = format!("{} is not a valid integer", indices[1]);
let error = SlicecDiagnostic::from_error(SlicecError::Other { message })
.add_note(CRITICAL_FLAW_STRING, None);
output.push(error);
return None;
};
let Some(&attribute) = symbol.attributes().get(attribute_index) else {
let message = format!("attribute index '{attribute_index}' is out of bounds");
let error = SlicecDiagnostic::from_error(SlicecError::Other { message })
.add_note(CRITICAL_FLAW_STRING, None);
output.push(error);
return None;
};
if argument_index_str.is_none() {
return Some(attribute.span.clone());
}
Some(attribute.span.clone())
}
fn get_entity_info_for(element: &impl Commentable) -> EntityInfo {
EntityInfo {
identifier: element.identifier().to_owned(),
attributes: get_attributes_from(element.attributes()),
comment: element.comment().map(Into::into),
}
}
fn get_doc_comment_for_parameter(parameter: &GrammarParameter) -> Option<DocComment> {
let operation_comment = parameter.parent().comment()?;
operation_comment.params.iter()
.find(|param_tag| param_tag.identifier.value == parameter.identifier())
.map(|param_tag| param_tag.message.value.iter().map(Into::into).collect())
.map(|message| DocComment {
overview: message,
see_tags: Vec::new(),
})
}
fn convert_doc_comment_link(link_result: Result<&dyn Entity, &GrammarIdentifier>) -> EntityId {
match link_result {
Ok(entity) => entity.parser_scoped_identifier(),
Err(identifier) => identifier.value.clone(),
}
}
fn get_attributes_from(attributes: Vec<&GrammarAttribute>) -> Vec<Attribute> {
attributes.into_iter().map(|attribute| Attribute {
directive: attribute.kind.directive().to_owned(),
args: get_attribute_args(attribute),
})
.collect()
}
fn get_attribute_args(attribute: &GrammarAttribute) -> Vec<String> {
if let Some(unparsed) = attribute.downcast::<Unparsed>() {
return unparsed.args.clone();
}
if let Some(allow) = attribute.downcast::<Allow>() {
return allow.allowed_lints.clone();
}
if let Some(compress) = attribute.downcast::<Compress>() {
let mut args = Vec::new();
if compress.compress_args {
args.push("Args".to_owned());
}
if compress.compress_return {
args.push("Return".to_owned());
}
return args;
}
if let Some(deprecated) = attribute.downcast::<Deprecated>() {
return deprecated.reason.iter().cloned().collect();
}
if attribute.downcast::<Oneway>().is_some() {
return Vec::new();
}
if let Some(sliced_format) = attribute.downcast::<SlicedFormat>() {
let mut args = Vec::new();
if sliced_format.sliced_args {
args.push("Args".to_owned());
}
if sliced_format.sliced_return {
args.push("Return".to_owned());
}
return args;
}
panic!("Impossible attribute encountered")
}
impl From<&GrammarSliceFile> for SliceFile {
fn from(slice_file: &GrammarSliceFile) -> Self {
let module = slice_file.module.as_ref().unwrap().borrow();
let converted_module = Module {
identifier: module.nested_module_identifier().to_owned(),
attributes: get_attributes_from(module.attributes()),
};
SliceFile {
path: slice_file.relative_path.clone(),
module_declaration: converted_module,
attributes: get_attributes_from(slice_file.attributes()),
contents: SliceFileContentsConverter::convert(&slice_file.contents),
}
}
}
impl From<&GrammarDocComment> for DocComment {
fn from(doc_comment: &GrammarDocComment) -> Self {
let overview = doc_comment.overview.as_ref().map(|message| {
message.value.iter().map(Into::into)
});
let see_tags = doc_comment.see.iter().map(|tag| {
convert_doc_comment_link(tag.linked_entity())
});
DocComment {
overview: overview.map_or(Vec::new(), |v| v.collect()),
see_tags: see_tags.collect(),
}
}
}
impl From<&GrammarMessageComponent> for MessageComponent {
fn from(component: &GrammarMessageComponent) -> Self {
match component {
GrammarMessageComponent::Text(text) => MessageComponent::Text(text.clone()),
GrammarMessageComponent::Link(tag) => {
MessageComponent::Link(convert_doc_comment_link(tag.linked_entity()))
}
}
}
}
#[derive(Debug)]
struct SliceFileContentsConverter {
converted_contents: Vec<Symbol>,
}
impl SliceFileContentsConverter {
fn convert(contents: &[GrammarDefinition]) -> Vec<Symbol> {
let mut converter = SliceFileContentsConverter {
converted_contents: Vec::new()
};
for definition in contents {
let converted = match definition {
GrammarDefinition::Struct(v) => Symbol::Struct(converter.convert_struct(v.borrow())),
GrammarDefinition::Interface(v) => Symbol::Interface(converter.convert_interface(v.borrow())),
GrammarDefinition::Enum(v) => converter.convert_enum(v.borrow()),
GrammarDefinition::CustomType(v) => Symbol::CustomType(converter.convert_custom_type(v.borrow())),
GrammarDefinition::TypeAlias(v) => Symbol::TypeAlias(converter.convert_type_alias(v.borrow())),
};
converter.converted_contents.push(converted);
}
converter.converted_contents
}
fn convert_type_ref(&mut self, type_ref: &GrammarTypeRef) -> TypeRef {
TypeRef {
type_id: self.get_type_id_for(type_ref),
is_optional: type_ref.is_optional,
type_attributes: get_attributes_from(type_ref.attributes()),
}
}
fn convert_struct(&mut self, struct_def: &GrammarStruct) -> Struct {
Struct {
entity_info: get_entity_info_for(struct_def),
is_compact: struct_def.is_compact,
fields: struct_def.fields().into_iter().map(|e| self.convert_field(e)).collect(),
}
}
fn convert_field(&mut self, field: &GrammarField) -> Field {
Field {
entity_info: get_entity_info_for(field),
tag: field.tag.as_ref().map(|integer| integer.value as i32),
data_type: self.convert_type_ref(field.data_type()),
}
}
fn convert_interface(&mut self, interface_def: &GrammarInterface) -> Interface {
let bases = interface_def.base_interfaces();
Interface {
entity_info: get_entity_info_for(interface_def),
bases: bases.into_iter().map(|i| i.module_scoped_identifier()).collect(),
operations: interface_def.operations().into_iter().map(|e| self.convert_operation(e)).collect(),
}
}
fn convert_operation(&mut self, operation: &GrammarOperation) -> Operation {
Operation {
entity_info: get_entity_info_for(operation),
is_idempotent: operation.is_idempotent,
parameters: operation.parameters().into_iter().map(|e| self.convert_parameter(e)).collect(),
has_streamed_parameter: operation
.parameters
.last()
.is_some_and(|parameter| parameter.borrow().is_streamed),
return_type: operation.return_members().into_iter().map(|e| self.convert_parameter(e)).collect(),
has_streamed_return: operation
.return_type
.last()
.is_some_and(|parameter| parameter.borrow().is_streamed),
}
}
fn convert_parameter(&mut self, parameter: &GrammarParameter) -> Field {
let parameter_info = EntityInfo {
identifier: parameter.identifier().to_owned(),
attributes: get_attributes_from(parameter.attributes()),
comment: get_doc_comment_for_parameter(parameter),
};
Field {
entity_info: parameter_info,
tag: parameter.tag.as_ref().map(|integer| integer.value as i32),
data_type: self.convert_type_ref(parameter.data_type()),
}
}
fn convert_enum(&mut self, enum_def: &GrammarEnum) -> Symbol {
if let Some(underlying_type) = enum_def.underlying.as_ref() {
Symbol::BasicEnum(BasicEnum {
entity_info: get_entity_info_for(enum_def),
is_unchecked: enum_def.is_unchecked,
underlying: underlying_type.type_string(),
enumerators: enum_def.enumerators().into_iter().map(|e| self.convert_enumerator(e)).collect(),
})
} else {
Symbol::VariantEnum(VariantEnum {
entity_info: get_entity_info_for(enum_def),
is_compact: enum_def.is_compact,
is_unchecked: enum_def.is_unchecked,
variants: enum_def.enumerators().into_iter().map(|e| self.convert_variant(e)).collect(),
})
}
}
fn convert_enumerator(&mut self, enumerator: &GrammarEnumerator) -> Enumerator {
let entity_info = get_entity_info_for(enumerator);
let absolute_value = enumerator.value().unsigned_abs() as u64;
let has_negative_value = enumerator.value().is_negative();
Enumerator { entity_info, absolute_value, has_negative_value }
}
fn convert_variant(&mut self, enumerator: &GrammarEnumerator) -> Variant {
let entity_info = get_entity_info_for(enumerator);
let discriminant = enumerator.value().try_into().unwrap();
let fields = enumerator.fields().into_iter().map(|e| self.convert_field(e)).collect();
Variant { entity_info, discriminant, fields }
}
fn convert_custom_type(&mut self, custom_type: &GrammarCustomType) -> CustomType {
CustomType {
entity_info: get_entity_info_for(custom_type)
}
}
fn convert_type_alias(&mut self, type_alias: &GrammarTypeAlias) -> TypeAlias {
TypeAlias {
entity_info: get_entity_info_for(type_alias),
underlying_type: self.convert_type_ref(&type_alias.underlying),
}
}
fn convert_sequence(&mut self, sequence: &GrammarSequence) -> SequenceType {
SequenceType {
element_type: self.convert_type_ref(&sequence.element_type),
}
}
fn convert_dictionary(&mut self, dictionary: &GrammarDictionary) -> DictionaryType {
DictionaryType {
key_type: self.convert_type_ref(&dictionary.key_type),
value_type: self.convert_type_ref(&dictionary.value_type),
}
}
fn convert_result_type(&mut self, result_type: &GrammarResultType) -> ResultType {
ResultType {
success_type: self.convert_type_ref(&result_type.success_type),
failure_type: self.convert_type_ref(&result_type.failure_type),
}
}
fn get_type_id_for(&mut self, type_ref: &GrammarTypeRef) -> TypeId {
match type_ref.concrete_type() {
GrammarTypes::Struct(v) => v.module_scoped_identifier(),
GrammarTypes::Enum(v) => v.module_scoped_identifier(),
GrammarTypes::CustomType(v) => v.module_scoped_identifier(),
GrammarTypes::Primitive(v) => v.type_string(),
GrammarTypes::ResultType(v) => {
let converted_symbol = Symbol::ResultType(self.convert_result_type(v));
self.converted_contents.push(converted_symbol);
(self.converted_contents.len() - 1).to_string()
}
GrammarTypes::Sequence(v) => {
let converted_symbol = Symbol::SequenceType(self.convert_sequence(v));
self.converted_contents.push(converted_symbol);
(self.converted_contents.len() - 1).to_string()
}
GrammarTypes::Dictionary(v) => {
let converted_symbol = Symbol::DictionaryType(self.convert_dictionary(v));
self.converted_contents.push(converted_symbol);
(self.converted_contents.len() - 1).to_string()
}
}
}
}