Skip to main content

tensorlogic_compiler/error_recovery/
strategy.rs

1//! Recovery strategy used by the tolerant compiler.
2//!
3//! A [`RecoveryStrategy`] selects how the tolerant driver reacts to a
4//! blocking diagnostic — i.e. a [`crate::error_recovery::Severity::Error`]
5//! or [`crate::error_recovery::Severity::Fatal`].
6//!
7//! | Strategy         | Warning  | Error                 | Fatal                 |
8//! |------------------|----------|-----------------------|-----------------------|
9//! | `SkipOnError`    | continue | skip this expr only   | skip this expr only   |
10//! | `SkipOnFatal`    | continue | skip this expr only   | abort whole program   |
11//! | `AbortOnAny`     | continue | abort whole program   | abort whole program   |
12//!
13//! "Skip this expr only" means the offending expression's slot becomes
14//! `None` in the result while the remaining expressions are still compiled.
15//! "Abort whole program" means *all* later expressions are left as `None`.
16//!
17//! The default strategy is [`RecoveryStrategy::SkipOnError`] — the most
18//! tolerant mode, which matches the intent of partial error recovery.
19
20use super::diagnostic::Severity;
21
22/// Configurable error-recovery policy for the tolerant compiler.
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
24pub enum RecoveryStrategy {
25    /// Skip only the offending expression on *any* blocking diagnostic
26    /// (Error or Fatal). Every well-formed expression is still compiled.
27    ///
28    /// This is the most tolerant mode and the default for partial error
29    /// recovery.
30    #[default]
31    SkipOnError,
32
33    /// Skip the offending expression on [`Severity::Error`] but abort the
34    /// entire program on [`Severity::Fatal`].
35    SkipOnFatal,
36
37    /// Abort on the first blocking diagnostic (Error or Fatal). Warnings and
38    /// Infos are still collected but never cause an abort.
39    ///
40    /// This is functionally equivalent to the pre-existing strict
41    /// compilation mode but also returns the partial diagnostics collected
42    /// so far.
43    AbortOnAny,
44}
45
46impl RecoveryStrategy {
47    /// Decide what the driver should do for a diagnostic of this severity.
48    pub fn decide(self, severity: Severity) -> RecoveryAction {
49        match (self, severity) {
50            // Non-blocking severities never alter control flow.
51            (_, Severity::Info) | (_, Severity::Warning) => RecoveryAction::Continue,
52
53            // SkipOnError: never abort — always skip just this expression.
54            (RecoveryStrategy::SkipOnError, Severity::Error) => RecoveryAction::SkipExpression,
55            (RecoveryStrategy::SkipOnError, Severity::Fatal) => RecoveryAction::SkipExpression,
56
57            // SkipOnFatal: skip on Error, abort on Fatal.
58            (RecoveryStrategy::SkipOnFatal, Severity::Error) => RecoveryAction::SkipExpression,
59            (RecoveryStrategy::SkipOnFatal, Severity::Fatal) => RecoveryAction::AbortProgram,
60
61            // AbortOnAny: abort on any blocking severity.
62            (RecoveryStrategy::AbortOnAny, Severity::Error) => RecoveryAction::AbortProgram,
63            (RecoveryStrategy::AbortOnAny, Severity::Fatal) => RecoveryAction::AbortProgram,
64        }
65    }
66}
67
68/// Action the tolerant driver takes after reporting a diagnostic.
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
70pub enum RecoveryAction {
71    /// Keep compiling the current expression and then move on.
72    Continue,
73    /// Drop the current expression (slot becomes `None`) and proceed to the
74    /// next expression.
75    SkipExpression,
76    /// Stop compilation immediately; remaining expressions become `None`.
77    AbortProgram,
78}
79
80impl RecoveryAction {
81    /// Returns `true` when this action aborts the whole program.
82    pub fn is_abort(self) -> bool {
83        matches!(self, RecoveryAction::AbortProgram)
84    }
85
86    /// Returns `true` when this action drops the current expression slot.
87    pub fn drops_expression(self) -> bool {
88        matches!(
89            self,
90            RecoveryAction::SkipExpression | RecoveryAction::AbortProgram
91        )
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn default_is_skip_on_error() {
101        assert_eq!(RecoveryStrategy::default(), RecoveryStrategy::SkipOnError);
102    }
103
104    #[test]
105    fn skip_on_error_never_aborts() {
106        let s = RecoveryStrategy::SkipOnError;
107        assert_eq!(s.decide(Severity::Info), RecoveryAction::Continue);
108        assert_eq!(s.decide(Severity::Warning), RecoveryAction::Continue);
109        assert_eq!(s.decide(Severity::Error), RecoveryAction::SkipExpression);
110        assert_eq!(s.decide(Severity::Fatal), RecoveryAction::SkipExpression);
111    }
112
113    #[test]
114    fn skip_on_fatal_aborts_only_on_fatal() {
115        let s = RecoveryStrategy::SkipOnFatal;
116        assert_eq!(s.decide(Severity::Warning), RecoveryAction::Continue);
117        assert_eq!(s.decide(Severity::Error), RecoveryAction::SkipExpression);
118        assert_eq!(s.decide(Severity::Fatal), RecoveryAction::AbortProgram);
119    }
120
121    #[test]
122    fn abort_on_any_aborts_on_both_error_and_fatal() {
123        let s = RecoveryStrategy::AbortOnAny;
124        assert_eq!(s.decide(Severity::Info), RecoveryAction::Continue);
125        assert_eq!(s.decide(Severity::Warning), RecoveryAction::Continue);
126        assert_eq!(s.decide(Severity::Error), RecoveryAction::AbortProgram);
127        assert_eq!(s.decide(Severity::Fatal), RecoveryAction::AbortProgram);
128    }
129
130    #[test]
131    fn action_classifiers() {
132        assert!(RecoveryAction::AbortProgram.is_abort());
133        assert!(!RecoveryAction::SkipExpression.is_abort());
134        assert!(!RecoveryAction::Continue.is_abort());
135
136        assert!(!RecoveryAction::Continue.drops_expression());
137        assert!(RecoveryAction::SkipExpression.drops_expression());
138        assert!(RecoveryAction::AbortProgram.drops_expression());
139    }
140}