knave-expand 0.14.21

The reading, scoping and expansion stages of knave
Documentation
use super::problem_mark::ProblemMark;
use super::problem_report::ProblemReport;
use crate::diagnostic::{Diagnostic, DistinctDiagnostics};
use crate::scope::Phase;

#[derive(Default)]
pub(super) struct RecoveredErrors {
    errors: Vec<Diagnostic>,
}

impl RecoveredErrors {
    pub(super) fn keep(&mut self, error: Diagnostic, phase: Phase) -> Result<(), Diagnostic> {
        if phase == Phase::CompileTime {
            return Err(error);
        }
        self.errors.push(error);
        Ok(())
    }

    pub(super) fn mark(&self) -> ProblemMark {
        ProblemMark::end_of(&self.errors)
    }

    pub(super) fn settle_since<T>(
        &mut self,
        mark: ProblemMark,
        outcome: Result<T, Diagnostic>,
    ) -> Result<T, Diagnostic> {
        let recovered = mark.since(&mut self.errors);
        match outcome {
            Ok(value) => {
                self.keep_distinct(recovered);
                Ok(value)
            }
            Err(error) => Err(self.keep_distinct_beside(error, recovered)),
        }
    }

    pub(super) fn keep_distinct_since(&mut self, mark: ProblemMark, failures: Vec<Diagnostic>) {
        let mut recovered = mark.since(&mut self.errors);
        recovered.extend(failures);
        self.keep_distinct(recovered);
    }

    pub(super) fn take_first_since(&mut self, mark: ProblemMark) -> Option<Diagnostic> {
        let mut since = mark.since(&mut self.errors);
        let first = (!since.is_empty()).then(|| since.remove(0));
        self.errors.extend(since);
        first
    }

    pub(super) fn roll_back(&mut self, mark: ProblemMark) {
        drop(mark.since(&mut self.errors));
    }

    pub(super) fn hand_over(&mut self, report: &mut ProblemReport) {
        for error in std::mem::take(&mut self.errors) {
            report.report(error);
        }
    }

    fn keep_distinct(&mut self, recovered: Vec<Diagnostic>) {
        let distinct = DistinctDiagnostics::of(recovered);
        self.errors.extend(distinct.into_diagnostics());
    }

    fn keep_distinct_beside(
        &mut self,
        error: Diagnostic,
        recovered: Vec<Diagnostic>,
    ) -> Diagnostic {
        let with_error = std::iter::once(error).chain(recovered);
        let mut distinct = DistinctDiagnostics::of(with_error).into_diagnostics();
        let most_helpful_error = distinct.remove(0);
        self.errors.extend(distinct);
        most_helpful_error
    }
}

#[cfg(test)]
mod tests {
    use super::RecoveredErrors;
    use crate::diagnostic::Diagnostic;
    use crate::expand::problem_report::ProblemReport;
    use crate::expand::problems::ExpandProblems;
    use crate::scope::Phase;
    use crate::source::{SourceId, Span};

    struct RecoveryFixture {
        recovered: RecoveredErrors,
    }

    impl RecoveryFixture {
        fn new() -> Self {
            Self {
                recovered: RecoveredErrors::default(),
            }
        }

        fn error_at(start: usize) -> Diagnostic {
            ExpandProblems::empty_call(Span::new(SourceId::new(0), start..start + 2))
        }

        fn keep_at(&mut self, start: usize) {
            let kept = self.recovered.keep(Self::error_at(start), Phase::Runtime);
            assert!(kept.is_ok());
        }

        fn starts(&mut self) -> Vec<usize> {
            let mut report = ProblemReport::default();
            self.recovered.hand_over(&mut report);
            report
                .take_errors()
                .in_source_order()
                .iter()
                .map(|error| error.primary().span().range().start)
                .collect()
        }
    }

    #[test]
    fn an_error_at_runtime_is_kept() {
        let mut fixture = RecoveryFixture::new();
        fixture.keep_at(4);
        assert_eq!(fixture.starts(), [4]);
    }

    #[test]
    fn handing_over_leaves_no_error_behind() {
        let mut fixture = RecoveryFixture::new();
        fixture.keep_at(4);
        fixture.starts();
        assert!(fixture.starts().is_empty());
    }

    #[test]
    fn an_error_at_compile_time_is_refused_and_not_kept() {
        let mut fixture = RecoveryFixture::new();
        let refused = fixture
            .recovered
            .keep(RecoveryFixture::error_at(4), Phase::CompileTime);
        assert!(refused.is_err());
        assert!(fixture.starts().is_empty());
    }

    #[test]
    fn settling_a_success_keeps_one_of_each_repeated_error() {
        let mut fixture = RecoveryFixture::new();
        let mark = fixture.recovered.mark();
        fixture.keep_at(4);
        fixture.keep_at(4);
        fixture.keep_at(8);
        let settled = fixture.recovered.settle_since(mark, Ok(()));
        assert!(settled.is_ok());
        assert_eq!(fixture.starts(), [4, 8]);
    }

    #[test]
    fn settling_a_failure_returns_it_and_keeps_the_other_errors_beside_it() {
        let mut fixture = RecoveryFixture::new();
        let mark = fixture.recovered.mark();
        fixture.keep_at(4);
        fixture.keep_at(8);
        let settled: Result<(), Diagnostic> = fixture
            .recovered
            .settle_since(mark, Err(RecoveryFixture::error_at(4)));
        let failure = settled.expect_err("the failure stays a failure");
        assert_eq!(failure.primary().span().range().start, 4);
        assert_eq!(fixture.starts(), [8]);
    }

    #[test]
    fn settling_leaves_errors_before_the_mark_alone() {
        let mut fixture = RecoveryFixture::new();
        fixture.keep_at(4);
        let mark = fixture.recovered.mark();
        fixture.keep_at(4);
        let _ = fixture.recovered.settle_since(mark, Ok(()));
        assert_eq!(fixture.starts(), [4, 4]);
    }

    #[test]
    fn a_run_keeps_its_failures_beside_its_recovered_errors_once_each() {
        let mut fixture = RecoveryFixture::new();
        let mark = fixture.recovered.mark();
        fixture.keep_at(4);
        let failures = vec![RecoveryFixture::error_at(4), RecoveryFixture::error_at(8)];
        fixture.recovered.keep_distinct_since(mark, failures);
        assert_eq!(fixture.starts(), [4, 8]);
    }

    #[test]
    fn a_run_leaves_errors_before_its_mark_alone() {
        let mut fixture = RecoveryFixture::new();
        fixture.keep_at(4);
        let mark = fixture.recovered.mark();
        let failures = vec![RecoveryFixture::error_at(4)];
        fixture.recovered.keep_distinct_since(mark, failures);
        assert_eq!(fixture.starts(), [4, 4]);
    }

    #[test]
    fn the_first_error_since_a_mark_is_taken_and_the_rest_stay() {
        let mut fixture = RecoveryFixture::new();
        fixture.keep_at(2);
        let mark = fixture.recovered.mark();
        fixture.keep_at(4);
        fixture.keep_at(8);
        let first = fixture.recovered.take_first_since(mark);
        assert_eq!(
            first.map(|error| error.primary().span().range().start),
            Some(4)
        );
        assert_eq!(fixture.starts(), [2, 8]);
    }

    #[test]
    fn no_error_is_taken_when_none_came_since_the_mark() {
        let mut fixture = RecoveryFixture::new();
        fixture.keep_at(2);
        let mark = fixture.recovered.mark();
        assert!(fixture.recovered.take_first_since(mark).is_none());
    }

    #[test]
    fn rolling_back_drops_the_errors_since_the_mark() {
        let mut fixture = RecoveryFixture::new();
        fixture.keep_at(2);
        let mark = fixture.recovered.mark();
        fixture.keep_at(4);
        fixture.recovered.roll_back(mark);
        assert_eq!(fixture.starts(), [2]);
    }
}