use crate::{DataRecord, Diagnostic, InstanceId, Parameter, Span};
use std::collections::HashSet;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum Key {
Small(u64),
Large(Box<str>),
}
impl Key {
fn of(id: &InstanceId) -> Self {
let digits = id.as_str().trim_start_matches('0');
if digits.is_empty() {
return Self::Small(0);
}
digits
.parse()
.map_or_else(|_| Self::Large(digits.into()), Self::Small)
}
fn to_id(&self) -> InstanceId {
match self {
Self::Small(value) => InstanceId::from(*value),
Self::Large(digits) => {
InstanceId::new(digits).expect("canonical digits are a valid instance id")
}
}
}
}
#[derive(Debug, Default)]
struct Defined {
small: HashSet<u64>,
large: HashSet<Box<str>>,
}
impl Defined {
fn insert(&mut self, key: Key) -> bool {
match key {
Key::Small(value) => self.small.insert(value),
Key::Large(digits) => self.large.insert(digits),
}
}
fn contains(&self, key: &Key) -> bool {
match key {
Key::Small(value) => self.small.contains(value),
Key::Large(digits) => self.large.contains(digits),
}
}
}
#[derive(Debug, Default)]
pub(crate) struct ReferenceCheck {
defined: Defined,
unresolved: Vec<Key>,
pending: Vec<(Span, usize)>,
}
impl ReferenceCheck {
pub(crate) fn record<S>(
&mut self,
record: &DataRecord<S>,
span: Span,
diagnostics: &mut Vec<Diagnostic>,
) {
if !self.defined.insert(Key::of(&record.id)) {
diagnostics.push(Diagnostic::duplicate_id(span, record.id.clone()));
}
let start = self.unresolved.len();
for component in record.records() {
for parameter in &component.parameters {
self.collect(parameter);
}
}
if self.unresolved.len() > start {
self.pending.push((span, self.unresolved.len()));
}
}
fn collect<S>(&mut self, parameter: &Parameter<S>) {
match parameter {
Parameter::Ref(id) => {
let key = Key::of(id);
if !self.defined.contains(&key) {
self.unresolved.push(key);
}
}
Parameter::List(items) => {
for item in items {
self.collect(item);
}
}
Parameter::Typed { value, .. } => self.collect(value),
_ => {}
}
}
pub(crate) fn finish(self, diagnostics: &mut Vec<Diagnostic>) {
let mut reported: HashSet<&Key> = HashSet::new();
let mut start = 0;
for (span, end) in &self.pending {
reported.clear();
for key in &self.unresolved[start..*end] {
if !self.defined.contains(key) && reported.insert(key) {
diagnostics.push(Diagnostic::dangling_reference(*span, key.to_id()));
}
}
start = *end;
}
diagnostics.sort_by_key(|diagnostic| diagnostic.span().start);
}
}