miden_diagnostics/
diagnostic.rs

1use crate::*;
2
3/// Constructs an in-flight diagnostic using the builder pattern
4pub struct InFlightDiagnostic<'h> {
5    handler: &'h DiagnosticsHandler,
6    file_id: Option<SourceId>,
7    diagnostic: Diagnostic,
8    severity: Severity,
9}
10impl<'h> InFlightDiagnostic<'h> {
11    pub(crate) fn new(handler: &'h DiagnosticsHandler, severity: Severity) -> Self {
12        Self {
13            handler,
14            file_id: None,
15            diagnostic: Diagnostic::new(severity),
16            severity,
17        }
18    }
19
20    /// Returns the severity level of this diagnostic
21    pub fn severity(&self) -> Severity {
22        self.severity
23    }
24
25    /// Returns whether this diagnostic should be generated
26    /// with verbose detail. Intended to be used when building
27    /// diagnostics in-flight by formatting functions which do
28    /// not know what the current diagnostic configuration is
29    pub fn verbose(&self) -> bool {
30        use crate::term::DisplayStyle;
31        matches!(self.handler.display.display_style, DisplayStyle::Rich)
32    }
33
34    /// Sets the current source file to which this diagnostic applies
35    pub fn set_source_file(mut self, filename: impl Into<FileName>) -> Self {
36        let filename = filename.into();
37        let file_id = self.handler.codemap.get_file_id(&filename);
38        self.file_id = file_id;
39        self
40    }
41
42    /// Sets the diagnostic message to `message`
43    pub fn with_message(mut self, message: impl ToString) -> Self {
44        self.diagnostic.message = message.to_string();
45        self
46    }
47
48    /// Adds a primary label for `span` to this diagnostic, with no label message.
49    pub fn with_primary_span(mut self, span: SourceSpan) -> Self {
50        self.diagnostic
51            .labels
52            .push(Label::primary(span.source_id(), span));
53        self
54    }
55
56    /// Adds a primary label for `span` to this diagnostic, with the given message
57    ///
58    /// A primary label is one which should be rendered as the relevant source code
59    /// at which a diagnostic originates. Secondary labels are used for related items
60    /// involved in the diagnostic.
61    pub fn with_primary_label(mut self, span: SourceSpan, message: impl ToString) -> Self {
62        self.diagnostic
63            .labels
64            .push(Label::primary(span.source_id(), span).with_message(message.to_string()));
65        self
66    }
67
68    /// Adds a secondary label for `span` to this diagnostic, with the given message
69    ///
70    /// A secondary label is used to point out related items in the source code which
71    /// are relevant to the diagnostic, but which are not themselves the point at which
72    /// the diagnostic originates.
73    pub fn with_secondary_label(mut self, span: SourceSpan, message: impl ToString) -> Self {
74        self.diagnostic
75            .labels
76            .push(Label::secondary(span.source_id(), span).with_message(message.to_string()));
77        self
78    }
79
80    /// Like `with_primary_label`, but rather than a [SourceSpan], it accepts a
81    /// line and column number, which will be mapped to an appropriate span by
82    /// the [CodeMap].
83    pub fn with_primary_label_line_and_col(
84        self,
85        line: u32,
86        column: u32,
87        message: Option<String>,
88    ) -> Self {
89        let file_id = self.file_id;
90        self.with_label_and_file_id(LabelStyle::Primary, file_id, line, column, message)
91    }
92
93    /// This is a lower-level function for adding labels to diagnostics, providing
94    /// full control over its style, content, and location in the source code.
95    pub fn with_label(
96        self,
97        style: LabelStyle,
98        filename: Option<FileName>,
99        line: u32,
100        column: u32,
101        message: Option<String>,
102    ) -> Self {
103        if let Some(name) = filename {
104            let id = self.handler.lookup_file_id(name);
105            self.with_label_and_file_id(style, id, line, column, message)
106        } else {
107            self
108        }
109    }
110
111    fn with_label_and_file_id(
112        mut self,
113        style: LabelStyle,
114        file_id: Option<SourceId>,
115        line: u32,
116        _column: u32,
117        message: Option<String>,
118    ) -> Self {
119        if let Some(id) = file_id {
120            let source_file = self.handler.codemap.get(id).unwrap();
121            let line_index = (line - 1).into();
122            let span = source_file
123                .line_span(line_index)
124                .expect("invalid line index");
125            let label = if let Some(msg) = message {
126                Label::new(style, id, span).with_message(msg)
127            } else {
128                Label::new(style, id, span)
129            };
130            self.diagnostic.labels.push(label);
131            self
132        } else {
133            self
134        }
135    }
136
137    /// Adds a note to the diagnostic
138    ///
139    /// Notes are used for explaining general concepts or suggestions
140    /// related to a diagnostic, and are not associated with any particular
141    /// source location. They are always rendered after the other diagnostic
142    /// content.
143    pub fn with_note(mut self, note: impl ToString) -> Self {
144        self.diagnostic.notes.push(note.to_string());
145        self
146    }
147
148    /// Like `with_note`, but is intended for use cases where the
149    /// fluent/builder pattern used here is cumbersome.
150    pub fn add_note(&mut self, note: impl ToString) {
151        self.diagnostic.notes.push(note.to_string());
152    }
153
154    /// Consume this [InFlightDiagnostic] and extract the underlying [Diagnostic]
155    pub fn take(self) -> Diagnostic {
156        self.diagnostic
157    }
158
159    /// Emit the underlying [Diagnostic] via the [DiagnosticHandler]
160    pub fn emit(self) {
161        self.handler.emit(self.diagnostic);
162    }
163}