Skip to main content

noirc_errors/
reporter.rs

1use std::io::IsTerminal;
2
3use crate::{Location, Span};
4use codespan_reporting::diagnostic::{Diagnostic, Label};
5use codespan_reporting::files::Files;
6use codespan_reporting::term;
7use codespan_reporting::term::termcolor::{ColorChoice, StandardStream};
8
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct CustomDiagnostic {
11    pub file: fm::FileId,
12    pub message: String,
13    pub secondaries: Vec<CustomLabel>,
14    pub notes: Vec<String>,
15    pub kind: DiagnosticKind,
16    pub deprecated: bool,
17    pub unnecessary: bool,
18
19    /// An optional call stack to display the full runtime call stack
20    /// leading up to a runtime error. If this is empty it will not be displayed.
21    pub call_stack: Vec<Location>,
22}
23
24#[derive(Debug, Copy, Clone, PartialEq, Eq)]
25pub enum DiagnosticKind {
26    Error,
27    Bug,
28    Warning,
29    Info,
30}
31
32/// A count of errors that have been already reported to stderr
33#[derive(Debug, Copy, Clone)]
34pub struct ReportedErrors {
35    pub error_count: u32,
36}
37
38impl CustomDiagnostic {
39    pub fn from_message(msg: &str, file: fm::FileId) -> CustomDiagnostic {
40        Self {
41            file,
42            message: msg.to_owned(),
43            secondaries: Vec::new(),
44            notes: Vec::new(),
45            kind: DiagnosticKind::Error,
46            deprecated: false,
47            unnecessary: false,
48            call_stack: Default::default(),
49        }
50    }
51
52    fn simple_with_kind(
53        primary_message: String,
54        secondary_message: String,
55        secondary_location: Location,
56        kind: DiagnosticKind,
57    ) -> CustomDiagnostic {
58        CustomDiagnostic {
59            file: secondary_location.file,
60            message: primary_message,
61            secondaries: vec![CustomLabel::new(secondary_message, secondary_location)],
62            notes: Vec::new(),
63            kind,
64            deprecated: false,
65            unnecessary: false,
66            call_stack: Default::default(),
67        }
68    }
69
70    pub fn simple_error(
71        primary_message: String,
72        secondary_message: String,
73        secondary_location: Location,
74    ) -> CustomDiagnostic {
75        Self::simple_with_kind(
76            primary_message,
77            secondary_message,
78            secondary_location,
79            DiagnosticKind::Error,
80        )
81    }
82
83    pub fn simple_warning(
84        primary_message: String,
85        secondary_message: String,
86        secondary_location: Location,
87    ) -> CustomDiagnostic {
88        Self::simple_with_kind(
89            primary_message,
90            secondary_message,
91            secondary_location,
92            DiagnosticKind::Warning,
93        )
94    }
95
96    pub fn simple_info(
97        primary_message: String,
98        secondary_message: String,
99        secondary_location: Location,
100    ) -> CustomDiagnostic {
101        Self::simple_with_kind(
102            primary_message,
103            secondary_message,
104            secondary_location,
105            DiagnosticKind::Info,
106        )
107    }
108
109    pub fn simple_bug(
110        primary_message: String,
111        secondary_message: String,
112        secondary_location: Location,
113    ) -> CustomDiagnostic {
114        CustomDiagnostic {
115            file: secondary_location.file,
116            message: primary_message,
117            secondaries: vec![CustomLabel::new(secondary_message, secondary_location)],
118            notes: Vec::new(),
119            kind: DiagnosticKind::Bug,
120            deprecated: false,
121            unnecessary: false,
122            call_stack: Default::default(),
123        }
124    }
125
126    pub fn with_call_stack(mut self, call_stack: Vec<Location>) -> Self {
127        self.call_stack = call_stack;
128        self
129    }
130
131    pub fn add_note(&mut self, message: String) {
132        self.notes.push(message);
133    }
134
135    pub fn add_secondary(&mut self, message: String, location: Location) {
136        self.secondaries.push(CustomLabel::new(message, location));
137    }
138
139    pub fn is_error(&self) -> bool {
140        matches!(self.kind, DiagnosticKind::Error)
141    }
142
143    pub fn is_warning(&self) -> bool {
144        matches!(self.kind, DiagnosticKind::Warning)
145    }
146
147    pub fn is_info(&self) -> bool {
148        matches!(self.kind, DiagnosticKind::Info)
149    }
150
151    pub fn is_bug(&self) -> bool {
152        matches!(self.kind, DiagnosticKind::Bug)
153    }
154}
155
156impl std::fmt::Display for CustomDiagnostic {
157    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158        write!(f, "{}", self.message)?;
159
160        for secondary in &self.secondaries {
161            write!(f, "\nsecondary: {}", secondary.message)?;
162        }
163
164        for note in &self.notes {
165            write!(f, "\nnote: {note}")?;
166        }
167
168        Ok(())
169    }
170}
171
172#[derive(Debug, Clone, PartialEq, Eq)]
173pub struct CustomLabel {
174    pub message: String,
175    pub location: Location,
176}
177
178impl CustomLabel {
179    fn new(message: String, location: Location) -> CustomLabel {
180        CustomLabel { message, location }
181    }
182}
183
184/// Writes the given diagnostics to stderr and returns the count
185/// of diagnostics that were errors.
186pub fn report_all<'files>(
187    files: &'files impl Files<'files, FileId = fm::FileId>,
188    diagnostics: &[CustomDiagnostic],
189    deny_warnings: bool,
190    silence_warnings: bool,
191) -> ReportedErrors {
192    // Report warnings before any errors
193    let (warnings_and_bugs, mut errors): (Vec<_>, _) =
194        diagnostics.iter().partition(|item| !item.is_error());
195
196    let (warnings, mut bugs): (Vec<_>, _) =
197        warnings_and_bugs.iter().partition(|item| item.is_warning());
198    let mut diagnostics = if silence_warnings { Vec::new() } else { warnings };
199    diagnostics.append(&mut bugs);
200    diagnostics.append(&mut errors);
201
202    let error_count =
203        diagnostics.iter().map(|error| error.report(files, deny_warnings) as u32).sum();
204
205    ReportedErrors { error_count }
206}
207
208impl CustomDiagnostic {
209    /// Print the report; return true if it was an error.
210    pub fn report<'files>(
211        &self,
212        files: &'files impl Files<'files, FileId = fm::FileId>,
213        deny_warnings: bool,
214    ) -> bool {
215        report(files, self, deny_warnings)
216    }
217}
218
219/// Report the given diagnostic, and return true if it was an error
220pub fn report<'files>(
221    files: &'files impl Files<'files, FileId = fm::FileId>,
222    custom_diagnostic: &CustomDiagnostic,
223    deny_warnings: bool,
224) -> bool {
225    let color_choice =
226        if std::io::stderr().is_terminal() { ColorChoice::Auto } else { ColorChoice::Never };
227    let writer = StandardStream::stderr(color_choice);
228    let config = term::Config::default();
229
230    let stack_trace = stack_trace(files, &custom_diagnostic.call_stack);
231    let diagnostic = convert_diagnostic(custom_diagnostic, stack_trace, deny_warnings);
232    term::emit(&mut writer.lock(), &config, files, &diagnostic).unwrap();
233
234    deny_warnings || custom_diagnostic.is_error()
235}
236
237fn convert_diagnostic(
238    cd: &CustomDiagnostic,
239    stack_trace: String,
240    deny_warnings: bool,
241) -> Diagnostic<fm::FileId> {
242    let diagnostic = match (cd.kind, deny_warnings) {
243        (DiagnosticKind::Warning, false) => Diagnostic::warning(),
244        (DiagnosticKind::Info, _) => Diagnostic::note(),
245        (DiagnosticKind::Bug, ..) => Diagnostic::bug(),
246        _ => Diagnostic::error(),
247    };
248
249    let secondary_labels = cd
250        .secondaries
251        .iter()
252        .map(|custom_label| {
253            let location = custom_label.location;
254            let span = location.span;
255            let start_span = span.start() as usize;
256            let end_span = span.end() as usize;
257            let file = location.file;
258            Label::secondary(file, start_span..end_span).with_message(&custom_label.message)
259        })
260        .collect();
261
262    let mut notes = cd.notes.clone();
263    notes.push(stack_trace);
264
265    diagnostic.with_message(&cd.message).with_labels(secondary_labels).with_notes(notes)
266}
267
268pub fn stack_trace<'files>(
269    files: &'files impl Files<'files, FileId = fm::FileId>,
270    call_stack: &[Location],
271) -> String {
272    if call_stack.is_empty() {
273        return String::new();
274    }
275
276    let mut result = "Call stack:\n".to_string();
277
278    for (i, call_item) in call_stack.iter().enumerate() {
279        let path = files.name(call_item.file).expect("should get file path");
280        let source = files.source(call_item.file).expect("should get file source");
281
282        let (line, column) = line_and_column_from_span(source.as_ref(), &call_item.span);
283        result += &format!("{}. {}:{}:{}\n", i + 1, path, line, column);
284    }
285
286    result
287}
288
289pub fn line_and_column_from_span(source: &str, span: &Span) -> (u32, u32) {
290    let mut line = 1;
291    let mut column = 0;
292
293    for (i, char) in source.chars().enumerate() {
294        column += 1;
295
296        if char == '\n' {
297            line += 1;
298            column = 0;
299        }
300
301        if span.start() <= i as u32 {
302            break;
303        }
304    }
305
306    (line, column)
307}