knave-expand 0.14.21

The reading, scoping and expansion stages of knave
Documentation
use super::Diagnostic;
use super::distinct::DistinctDiagnostics;

#[derive(Clone, Debug, Default)]
pub struct Diagnostics {
    items: Vec<Diagnostic>,
}

impl Diagnostics {
    pub fn push(&mut self, diagnostic: Diagnostic) {
        self.items.push(diagnostic);
    }

    pub fn extend(&mut self, other: Diagnostics) {
        self.items.extend(other.items);
    }

    pub fn has_errors(&self) -> bool {
        self.items.iter().any(Diagnostic::is_error)
    }

    pub fn is_empty(&self) -> bool {
        self.items.is_empty()
    }

    pub fn len(&self) -> usize {
        self.items.len()
    }

    pub fn retain(&mut self, keep: impl FnMut(&Diagnostic) -> bool) {
        self.items.retain(keep);
    }

    pub fn split_off(&mut self, at: usize) -> Self {
        Self {
            items: self.items.split_off(at),
        }
    }

    pub fn map(self, relabel: impl FnMut(Diagnostic) -> Diagnostic) -> Self {
        Self {
            items: self.items.into_iter().map(relabel).collect(),
        }
    }

    pub fn distinct(self) -> Self {
        Self {
            items: DistinctDiagnostics::of(self.items).into_diagnostics(),
        }
    }

    pub fn in_source_order(&self) -> Vec<&Diagnostic> {
        let mut ordered: Vec<&Diagnostic> = self.items.iter().collect();
        ordered.sort_by_key(|diagnostic| {
            let span = diagnostic.primary().span();
            (span.source().index(), span.range().start)
        });
        ordered
    }

    pub fn into_result<T>(self, value: T) -> Result<T, Diagnostics> {
        if self.has_errors() {
            Err(self)
        } else {
            Ok(value)
        }
    }
}

impl FromIterator<Diagnostic> for Diagnostics {
    fn from_iter<I: IntoIterator<Item = Diagnostic>>(diagnostics: I) -> Self {
        Self {
            items: diagnostics.into_iter().collect(),
        }
    }
}

impl From<Diagnostic> for Diagnostics {
    fn from(diagnostic: Diagnostic) -> Self {
        Self {
            items: vec![diagnostic],
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::diagnostic::{DiagnosticCode, Label};
    use crate::source::{SourceId, Span};

    struct CollectionFixture;

    impl CollectionFixture {
        fn unbound(name: &str) -> Diagnostic {
            let span = Span::new(SourceId::new(0), 0..4);
            Diagnostic::error(
                DiagnosticCode::UNBOUND_NAME,
                format!("cannot find `{name}` in this scope"),
                Label::new(span, "not found in this scope"),
            )
        }

        fn titles(diagnostics: &Diagnostics) -> Vec<&str> {
            diagnostics.items.iter().map(Diagnostic::title).collect()
        }
    }

    #[test]
    fn collecting_keeps_the_order_of_the_diagnostics() {
        let collected: Diagnostics = ["first", "second"]
            .into_iter()
            .map(CollectionFixture::unbound)
            .collect();
        assert_eq!(
            CollectionFixture::titles(&collected),
            vec![
                "cannot find `first` in this scope",
                "cannot find `second` in this scope"
            ]
        );
    }
}