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