miden_assembly/
diagnostics.rs

1use alloc::{borrow::Cow, boxed::Box, sync::Arc, vec::Vec};
2use core::{fmt, ops::Range};
3
4pub use miette::{
5    self, Diagnostic, IntoDiagnostic, LabeledSpan, NamedSource, Report, Result, Severity,
6    SourceCode, WrapErr,
7};
8pub use tracing;
9pub use vm_core::debuginfo::*;
10
11// LABEL
12// ================================================================================================
13
14/// Represents a diagnostic label.
15///
16/// A label is a source span and optional diagnostic text that should be laid out next to the
17/// source snippet.
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct Label {
20    span: miette::SourceSpan,
21    label: Option<Cow<'static, str>>,
22}
23
24impl Label {
25    /// Construct a label for the given range of bytes, expressible as any type which can be
26    /// converted to a [`Range<usize>`], e.g. [miette::SourceSpan].
27    pub fn at<R>(range: R) -> Self
28    where
29        Range<usize>: From<R>,
30    {
31        let range = Range::<usize>::from(range);
32        Self { span: range.into(), label: None }
33    }
34
35    /// Construct a label which points to a specific offset in the source file.
36    pub fn point<L>(at: usize, label: L) -> Self
37    where
38        Cow<'static, str>: From<L>,
39    {
40        Self {
41            span: miette::SourceSpan::from(at),
42            label: Some(Cow::from(label)),
43        }
44    }
45
46    /// Construct a label from the given source range and diagnostic text.
47    pub fn new<R, L>(range: R, label: L) -> Self
48    where
49        Range<usize>: From<R>,
50        Cow<'static, str>: From<L>,
51    {
52        let range = Range::<usize>::from(range);
53        Self {
54            span: range.into(),
55            label: Some(Cow::from(label)),
56        }
57    }
58
59    /// Returns the diagnostic text, the actual "label", for this label.
60    pub fn label(&self) -> Option<&str> {
61        self.label.as_deref()
62    }
63}
64
65impl From<Label> for miette::SourceSpan {
66    #[inline(always)]
67    fn from(label: Label) -> Self {
68        label.span
69    }
70}
71
72impl From<Label> for LabeledSpan {
73    #[inline]
74    fn from(label: Label) -> LabeledSpan {
75        if let Some(message) = label.label {
76            LabeledSpan::at(label.span, message)
77        } else {
78            LabeledSpan::underline(label.span)
79        }
80    }
81}
82
83// RELATED LABEL
84// ================================================================================================
85
86/// This type is used to associate a more complex label or set of labels with some other error.
87///
88/// In particular, it is used to reference related bits of source code distinct from that of the
89/// original error. A related label can have a distinct severity, its own message, and its own
90/// sub-labels, and may reference code in a completely different source file that the original
91/// error.
92#[derive(Debug)]
93pub struct RelatedLabel {
94    /// The severity for this related label
95    pub severity: Severity,
96    /// The message for this label
97    pub message: Cow<'static, str>,
98    /// The sub-labels for this label
99    pub labels: Vec<Label>,
100    /// The source file to use when rendering source spans of this label as snippets.
101    pub file: Option<Arc<SourceFile>>,
102}
103
104impl fmt::Display for RelatedLabel {
105    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
106        f.write_str(self.message.as_ref())
107    }
108}
109
110#[cfg(feature = "std")]
111impl std::error::Error for RelatedLabel {
112    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
113        None
114    }
115}
116
117#[cfg(not(feature = "std"))]
118impl miette::StdError for RelatedLabel {
119    fn source(&self) -> Option<&(dyn miette::StdError + 'static)> {
120        None
121    }
122}
123
124impl RelatedLabel {
125    pub fn new<S>(severity: Severity, message: S) -> Self
126    where
127        Cow<'static, str>: From<S>,
128    {
129        Self {
130            severity,
131            message: Cow::from(message),
132            labels: vec![],
133            file: None,
134        }
135    }
136
137    pub fn error<S>(message: S) -> Self
138    where
139        Cow<'static, str>: From<S>,
140    {
141        Self::new(Severity::Error, message)
142    }
143
144    #[allow(unused)]
145    pub fn warning<S>(message: S) -> Self
146    where
147        Cow<'static, str>: From<S>,
148    {
149        Self::new(Severity::Warning, message)
150    }
151
152    #[allow(unused)]
153    pub fn advice<S>(message: S) -> Self
154    where
155        Cow<'static, str>: From<S>,
156    {
157        Self::new(Severity::Advice, message)
158    }
159
160    pub fn with_source_file(mut self, file: Option<Arc<SourceFile>>) -> Self {
161        self.file = file;
162        self
163    }
164
165    pub fn with_labeled_span<S>(self, span: SourceSpan, message: S) -> Self
166    where
167        Cow<'static, str>: From<S>,
168    {
169        let range = span.into_range();
170        self.with_label(Label::new((range.start as usize)..(range.end as usize), message))
171    }
172
173    pub fn with_label(mut self, label: Label) -> Self {
174        self.labels.push(label);
175        self
176    }
177
178    #[allow(unused)]
179    pub fn with_labels<I>(mut self, labels: I) -> Self
180    where
181        I: IntoIterator<Item = Label>,
182    {
183        self.labels.extend(labels);
184        self
185    }
186}
187
188impl Diagnostic for RelatedLabel {
189    fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
190        None
191    }
192    fn severity(&self) -> Option<Severity> {
193        Some(self.severity)
194    }
195    fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
196        None
197    }
198    fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
199        None
200    }
201    fn source_code(&self) -> Option<&dyn SourceCode> {
202        self.file.as_ref().map(|f| f as &dyn SourceCode)
203    }
204    fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
205        if self.labels.is_empty() {
206            None
207        } else {
208            Some(Box::new(self.labels.iter().cloned().map(|l| l.into())))
209        }
210    }
211    fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
212        None
213    }
214    fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
215        None
216    }
217}
218
219// RELATED ERROR
220// ================================================================================================
221
222/// This type allows rolling up a diagnostic into a parent error
223///
224/// This is necessary as [Report] cannot be used as the source error when deriving [Diagnostic].
225#[derive(Debug)]
226pub struct RelatedError(Report);
227
228impl RelatedError {
229    pub fn into_report(self) -> Report {
230        self.0
231    }
232
233    #[inline(always)]
234    pub fn as_diagnostic(&self) -> &dyn Diagnostic {
235        self.0.as_ref()
236    }
237}
238
239impl Diagnostic for RelatedError {
240    fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
241        self.as_diagnostic().code()
242    }
243    fn severity(&self) -> Option<Severity> {
244        self.as_diagnostic().severity()
245    }
246    fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
247        self.as_diagnostic().help()
248    }
249    fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
250        self.as_diagnostic().url()
251    }
252    fn source_code(&self) -> Option<&dyn SourceCode> {
253        self.as_diagnostic().source_code()
254    }
255    fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
256        self.as_diagnostic().labels()
257    }
258    fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
259        self.as_diagnostic().related()
260    }
261    fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
262        self.as_diagnostic().diagnostic_source()
263    }
264}
265
266impl fmt::Display for RelatedError {
267    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
268        fmt::Display::fmt(&self.0, f)
269    }
270}
271
272#[cfg(feature = "std")]
273impl std::error::Error for RelatedError {
274    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
275        AsRef::<dyn std::error::Error>::as_ref(&self.0).source()
276    }
277}
278
279#[cfg(not(feature = "std"))]
280impl miette::StdError for RelatedError {
281    fn source(&self) -> Option<&(dyn miette::StdError + 'static)> {
282        AsRef::<dyn miette::StdError>::as_ref(&self.0).source()
283    }
284}
285
286impl From<Report> for RelatedError {
287    fn from(report: Report) -> Self {
288        Self(report)
289    }
290}
291
292impl RelatedError {
293    pub const fn new(report: Report) -> Self {
294        Self(report)
295    }
296
297    pub fn wrap<E>(error: E) -> Self
298    where
299        E: Diagnostic + Send + Sync + 'static,
300    {
301        Self(Report::new_boxed(Box::new(error)))
302    }
303}
304
305// REPORTING
306// ================================================================================================
307
308/// Rendering and error reporting implementation details.
309pub mod reporting {
310    use core::fmt;
311
312    pub use miette::{
313        DebugReportHandler, JSONReportHandler, NarratableReportHandler, ReportHandler, set_hook,
314    };
315    #[cfg(feature = "std")]
316    pub use miette::{GraphicalReportHandler, GraphicalTheme, set_panic_hook};
317
318    pub type ReportHandlerOpts = miette::MietteHandlerOpts;
319
320    #[cfg(feature = "std")]
321    pub type DefaultReportHandler = miette::GraphicalReportHandler;
322
323    #[cfg(not(feature = "std"))]
324    pub type DefaultReportHandler = miette::DebugReportHandler;
325
326    /// A type that can be used to render a [super::Diagnostic] via [core::fmt::Display]
327    pub struct PrintDiagnostic<D, R = DefaultReportHandler> {
328        handler: R,
329        diag: D,
330    }
331
332    impl<D: AsRef<dyn super::Diagnostic>> PrintDiagnostic<D> {
333        pub fn new(diag: D) -> Self {
334            Self { handler: Default::default(), diag }
335        }
336        #[cfg(feature = "std")]
337        pub fn new_without_color(diag: D) -> Self {
338            Self {
339                handler: DefaultReportHandler::new_themed(GraphicalTheme::none()),
340                diag,
341            }
342        }
343        #[cfg(not(feature = "std"))]
344        pub fn new_without_color(diag: D) -> Self {
345            Self::new(diag)
346        }
347    }
348
349    impl<D: AsRef<dyn super::Diagnostic>> PrintDiagnostic<D, NarratableReportHandler> {
350        pub fn narrated(diag: D) -> Self {
351            Self {
352                handler: NarratableReportHandler::default(),
353                diag,
354            }
355        }
356    }
357
358    impl<D: AsRef<dyn super::Diagnostic>> PrintDiagnostic<D, JSONReportHandler> {
359        pub fn json(diag: D) -> Self {
360            Self { handler: JSONReportHandler, diag }
361        }
362    }
363
364    impl<D: AsRef<dyn super::Diagnostic>> fmt::Display for PrintDiagnostic<D> {
365        fn fmt(&self, f: &mut fmt::Formatter) -> core::fmt::Result {
366            self.handler.render_report(f, self.diag.as_ref())
367        }
368    }
369
370    impl<D: AsRef<dyn super::Diagnostic>> fmt::Display for PrintDiagnostic<D, NarratableReportHandler> {
371        fn fmt(&self, f: &mut fmt::Formatter) -> core::fmt::Result {
372            self.handler.render_report(f, self.diag.as_ref())
373        }
374    }
375
376    impl<D: AsRef<dyn super::Diagnostic>> fmt::Display for PrintDiagnostic<D, JSONReportHandler> {
377        fn fmt(&self, f: &mut fmt::Formatter) -> core::fmt::Result {
378            self.handler.render_report(f, self.diag.as_ref())
379        }
380    }
381}