Skip to main content

rucc_diag/
errors.rs

1//! Collecting diagnostics, and the limit on how many one run will produce.
2//!
3//! Design: `spec/06-lexer-and-parser.md` section 6.8.
4//!
5//! Every pass that reports needs the same three things: somewhere to put the diagnostics, a
6//! count of the ones that are errors, and a point at which it stops. It is here rather than in
7//! the parser that first needed it because semantic analysis needs exactly the same sink and the
8//! limit is one number for the whole compiler, not one per pass that happens to reach it.
9//!
10//! # Why counting errors is not what stops a cascade
11//!
12//! It is not. Every recovery leaves a poisoned node behind and a diagnostic about a poisoned
13//! node is not reported, which is what [`Errors::push_unless`] is for, and the limit here is the
14//! backstop for the file that is broken in more ways than one. A flag saying that something
15//! already went wrong is close enough to work on small inputs and wrong on real ones, because it
16//! either suppresses errors in code that was fine or fails to suppress the third message about
17//! the same broken subexpression.
18
19use crate::{Diagnostic, Severity};
20
21/// How many errors are reported before a pass gives up.
22///
23/// The number is clang's, measured rather than assumed: clang 23.1 stops after twenty with
24/// `too many errors emitted, stopping now`, and gcc 13.3 has no default limit at all and will
25/// print every error a file produces. Twenty is the better default of the two, because the
26/// errors after the twentieth in a file that is this broken are almost always consequences of
27/// the ones before them, and the accepted flag for changing it is gcc's `-fmax-errors=N`, with
28/// zero meaning no limit.
29pub const DEFAULT_ERROR_LIMIT: usize = 20;
30
31/// The diagnostics a pass produced, and the limit on how many it will produce.
32#[derive(Debug)]
33pub struct Errors {
34    diagnostics: Vec<Diagnostic>,
35    errors: usize,
36    limit: usize,
37    stopped: bool,
38}
39
40impl Default for Errors {
41    fn default() -> Self {
42        Errors::new(DEFAULT_ERROR_LIMIT)
43    }
44}
45
46impl Errors {
47    /// A sink that stops after `limit` errors, or that never stops when `limit` is zero.
48    #[must_use]
49    pub fn new(limit: usize) -> Self {
50        Errors { diagnostics: Vec::new(), errors: 0, limit, stopped: false }
51    }
52
53    /// Records a diagnostic.
54    ///
55    /// Once the limit is reached nothing more is recorded, warnings included. The pass is about
56    /// to stop and a warning arriving after the note that says so reads as though the compiler
57    /// carried on regardless.
58    pub fn push(&mut self, diagnostic: Diagnostic) {
59        if self.stopped {
60            return;
61        }
62        let fatal = diagnostic.severity.is_fatal();
63        let span = diagnostic.span;
64        self.diagnostics.push(diagnostic);
65        if fatal {
66            self.errors += 1;
67            if self.limit != 0 && self.errors >= self.limit {
68                self.diagnostics.push(Diagnostic::new(
69                    Severity::Note,
70                    "too many errors emitted, stopping now",
71                    span,
72                ));
73                self.stopped = true;
74            }
75        }
76    }
77
78    /// Records a diagnostic unless `suppressed` says the node it is about is already poisoned.
79    ///
80    /// Every pass that recovers leaves a poisoned node behind, and a message about such a node
81    /// is not reported, which is what actually stops one error becoming twenty. What counts as
82    /// poisoned is a fact about a tree rather than about a diagnostic, so the caller answers the
83    /// question and this only honours the answer.
84    ///
85    /// The suppression is deliberately shallow: the question is whether the node the message is
86    /// about is itself poisoned, not whether anything underneath it is. A poisoned operand makes
87    /// its parent poisoned at the point the parent is built, so the answer propagates through
88    /// the tree rather than through a walk of it, and a walk would make reporting an error cost
89    /// the size of the subtree.
90    pub fn push_unless(&mut self, suppressed: bool, diagnostic: Diagnostic) {
91        if !suppressed {
92            self.push(diagnostic);
93        }
94    }
95
96    /// Whether the pass should stop, because it has reported as many errors as it will.
97    #[inline]
98    #[must_use]
99    pub fn stopped(&self) -> bool {
100        self.stopped
101    }
102
103    /// How many errors have been reported. Warnings and notes are not counted.
104    #[inline]
105    #[must_use]
106    pub fn errors(&self) -> usize {
107        self.errors
108    }
109
110    /// Whether anything was reported at all.
111    #[inline]
112    #[must_use]
113    pub fn is_empty(&self) -> bool {
114        self.diagnostics.is_empty()
115    }
116
117    /// How many diagnostics were reported, of every severity.
118    #[inline]
119    #[must_use]
120    pub fn len(&self) -> usize {
121        self.diagnostics.len()
122    }
123
124    /// What has been reported so far, in the order it was reported.
125    ///
126    /// For a caller that wants to look at the diagnostics and carry on, which is what a test
127    /// does and what a pass that reports at the end of each function will do.
128    #[inline]
129    #[must_use]
130    pub fn diagnostics(&self) -> &[Diagnostic] {
131        &self.diagnostics
132    }
133
134    /// What was reported, in the order it was reported.
135    #[must_use]
136    pub fn finish(self) -> Vec<Diagnostic> {
137        self.diagnostics
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use crate::Span;
144
145    use super::*;
146
147    #[test]
148    fn the_limit_stops_the_run_and_says_so() {
149        let mut errors = Errors::new(3);
150        for _ in 0..10 {
151            errors.push(Diagnostic::error("no", Span::empty_at(0)));
152        }
153        assert!(errors.stopped());
154        assert_eq!(errors.errors(), 3);
155        let diagnostics = errors.finish();
156        assert_eq!(diagnostics.len(), 4);
157        assert_eq!(diagnostics[3].severity, Severity::Note);
158        assert_eq!(diagnostics[3].message, "too many errors emitted, stopping now");
159    }
160
161    #[test]
162    fn a_limit_of_zero_never_stops() {
163        let mut errors = Errors::new(0);
164        for _ in 0..64 {
165            errors.push(Diagnostic::error("no", Span::empty_at(0)));
166        }
167        assert!(!errors.stopped());
168        assert_eq!(errors.len(), 64);
169    }
170
171    #[test]
172    fn warnings_do_not_count_against_the_limit() {
173        let mut errors = Errors::default();
174        assert!(errors.is_empty());
175        for _ in 0..64 {
176            errors.push(Diagnostic::warning("hmm", Span::empty_at(0)));
177        }
178        assert_eq!(errors.errors(), 0);
179        assert!(!errors.stopped());
180    }
181
182    #[test]
183    fn a_message_about_a_poisoned_node_is_held_back() {
184        let mut errors = Errors::default();
185        let at = Span::empty_at(0);
186        errors.push_unless(true, Diagnostic::error("about the broken one", at));
187        assert!(errors.is_empty());
188        errors.push_unless(false, Diagnostic::error("about the good one", at));
189        assert_eq!(errors.len(), 1);
190    }
191}