use crate::ast::node::Node;
use crate::ast::Ast;
use crate::diagnostics::{Diagnostic, Diagnostics, Error, Note};
use crate::grammar::*;
use std::collections::{BTreeSet, HashSet};
pub(super) fn detect_cycles(ast: &Ast, diagnostics: &mut Diagnostics) {
let mut cycle_detector = CycleDetector {
type_being_checked: None,
dependency_stack: Vec::new(),
reported_cycles: HashSet::new(),
diagnostics,
};
for node in ast.as_slice() {
let candidate: &dyn CycleCandidate = match node {
Node::Struct(struct_def) => struct_def.borrow(),
Node::Enum(enum_def) => enum_def.borrow(),
_ => continue,
};
debug_assert!(cycle_detector.dependency_stack.is_empty());
cycle_detector.type_being_checked = Some((candidate.module_scoped_identifier(), candidate));
candidate.check_for_cycles(&mut cycle_detector)
}
}
trait CycleCandidate<'a>: Type + NamedSymbol {
fn check_for_cycles(&'a self, cycle_detector: &mut CycleDetector<'a>);
}
impl<'a> CycleCandidate<'a> for Struct {
fn check_for_cycles(&'a self, cycle_detector: &mut CycleDetector<'a>) {
cycle_detector.check_fields_for_cycles(self);
}
}
impl<'a> CycleCandidate<'a> for Enum {
fn check_for_cycles(&'a self, cycle_detector: &mut CycleDetector<'a>) {
for enumerator in self.enumerators() {
cycle_detector.check_fields_for_cycles(enumerator);
}
}
}
struct CycleDetector<'a> {
type_being_checked: Option<(String, &'a dyn CycleCandidate<'a>)>,
dependency_stack: Vec<(String, &'a Field)>,
reported_cycles: HashSet<BTreeSet<String>>,
diagnostics: &'a mut Diagnostics,
}
impl<'a> CycleDetector<'a> {
fn check_fields_for_cycles(&mut self, container: &'a dyn Container<Field>) {
for field in container.contents() {
self.check_field_type_for_cycles(field.data_type(), field);
}
}
fn check_field_type_for_cycles(&mut self, type_ref: &'a TypeRef, origin: &'a Field) {
match type_ref.concrete_type() {
Types::Struct(struct_ref) => self.push_to_stack_and_check(struct_ref, origin),
Types::Enum(enum_ref) => self.push_to_stack_and_check(enum_ref, origin),
Types::ResultType(result_type) => {
self.check_field_type_for_cycles(&result_type.success_type, origin);
self.check_field_type_for_cycles(&result_type.failure_type, origin);
}
Types::Sequence(sequence) => self.check_field_type_for_cycles(&sequence.element_type, origin),
Types::Dictionary(dictionary) => {
self.check_field_type_for_cycles(&dictionary.key_type, origin);
self.check_field_type_for_cycles(&dictionary.value_type, origin);
}
Types::Primitive(_) | Types::CustomType(_) => {}
}
}
fn push_to_stack_and_check(&mut self, candidate: &'a dyn CycleCandidate<'a>, origin: &'a Field) {
let candidate_type_string = candidate.module_scoped_identifier();
if self.type_being_checked.as_ref().unwrap().0 == candidate_type_string {
self.dependency_stack.push((candidate_type_string, origin));
self.report_cycle_error();
self.dependency_stack.pop();
return;
}
for (seen_type_id, _) in &self.dependency_stack {
if seen_type_id == &candidate_type_string {
return;
}
}
self.dependency_stack.push((candidate_type_string, origin));
candidate.check_for_cycles(self);
self.dependency_stack.pop();
}
fn report_cycle_error(&mut self) {
let cycle_set: BTreeSet<String> = self.dependency_stack.iter().map(|(id, _)| id.clone()).collect();
if !self.reported_cycles.insert(cycle_set) {
return;
}
let type_being_checked = self.type_being_checked.as_ref().unwrap();
let type_id = type_being_checked.0.clone();
let mut cycle = type_id.clone();
for (link_type_id, _) in &self.dependency_stack {
cycle = cycle + " -> " + link_type_id;
}
let cycle_notes = self.dependency_stack.iter().map(|(_, field)| Self::get_note_for(field));
let mut error = Diagnostic::from_error(Error::InfiniteSizeCycle { type_id, cycle });
error = error.set_span(type_being_checked.1.span());
error.notes = cycle_notes.collect();
self.diagnostics.push(error);
}
fn get_note_for(field: &Field) -> Note {
let parent_type: &dyn Entity = match field.parent().concrete_entity() {
Entities::Struct(struct_def) => struct_def,
Entities::Enumerator(enumerator) => enumerator.parent(), _ => unreachable!("Attempted to get cycle note for a container that wasn't a struct or enumerator!"),
};
let message = format!(
"{container_kind} '{container}' contains a field named '{field}' that is of type '{field_type}'",
container_kind = parent_type.kind(),
container = parent_type.identifier(),
field = field.identifier(),
field_type = field.data_type().type_string(),
);
let span = Some(field.span().clone());
Note { message, span }
}
}