miden_utils_diagnostics/
related.rs

1use alloc::{borrow::Cow, boxed::Box, sync::Arc, vec::Vec};
2use core::fmt;
3
4use miden_debug_types::{SourceFile, SourceSpan};
5
6use super::{Diagnostic, Label, LabeledSpan, Report, Severity, SourceCode};
7
8// RELATED LABEL
9// ================================================================================================
10
11/// This type is used to associate a more complex label or set of labels with some other error.
12///
13/// In particular, it is used to reference related bits of source code distinct from that of the
14/// original error. A related label can have a distinct severity, its own message, and its own
15/// sub-labels, and may reference code in a completely different source file that the original
16/// error.
17#[derive(Debug, Clone)]
18pub struct RelatedLabel {
19    /// The severity for this related label
20    pub severity: Severity,
21    /// The message for this label
22    pub message: Cow<'static, str>,
23    /// The sub-labels for this label
24    pub labels: Vec<Label>,
25    /// If provided as a standalone diagnostic, the help message to provide
26    pub help: Option<Cow<'static, str>>,
27    /// The source file to use when rendering source spans of this label as snippets.
28    pub file: Option<Arc<SourceFile>>,
29}
30
31impl fmt::Display for RelatedLabel {
32    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
33        f.write_str(self.message.as_ref())
34    }
35}
36
37#[cfg(feature = "std")]
38impl std::error::Error for RelatedLabel {
39    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
40        None
41    }
42}
43
44#[cfg(not(feature = "std"))]
45impl miette::StdError for RelatedLabel {
46    fn source(&self) -> Option<&(dyn miette::StdError + 'static)> {
47        None
48    }
49}
50
51impl RelatedLabel {
52    pub fn new<S>(severity: Severity, message: S) -> Self
53    where
54        Cow<'static, str>: From<S>,
55    {
56        Self {
57            severity,
58            message: Cow::from(message),
59            help: None,
60            labels: vec![],
61            file: None,
62        }
63    }
64
65    pub fn error<S>(message: S) -> Self
66    where
67        Cow<'static, str>: From<S>,
68    {
69        Self::new(Severity::Error, message)
70    }
71
72    pub fn warning<S>(message: S) -> Self
73    where
74        Cow<'static, str>: From<S>,
75    {
76        Self::new(Severity::Warning, message)
77    }
78
79    pub fn advice<S>(message: S) -> Self
80    where
81        Cow<'static, str>: From<S>,
82    {
83        Self::new(Severity::Advice, message)
84    }
85
86    pub fn with_source_file(mut self, file: Option<Arc<SourceFile>>) -> Self {
87        self.file = file;
88        self
89    }
90
91    pub fn with_labeled_span<S>(self, span: SourceSpan, message: S) -> Self
92    where
93        Cow<'static, str>: From<S>,
94    {
95        let range = span.into_range();
96        self.with_label(Label::new((range.start as usize)..(range.end as usize), message))
97    }
98
99    pub fn with_label(mut self, label: Label) -> Self {
100        self.labels.push(label);
101        self
102    }
103
104    pub fn with_labels<I>(mut self, labels: I) -> Self
105    where
106        I: IntoIterator<Item = Label>,
107    {
108        self.labels.extend(labels);
109        self
110    }
111
112    pub fn with_help<S>(mut self, help: S) -> Self
113    where
114        Cow<'static, str>: From<S>,
115    {
116        self.help = Some(help.into());
117        self
118    }
119}
120
121impl Diagnostic for RelatedLabel {
122    fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
123        None
124    }
125    fn severity(&self) -> Option<Severity> {
126        Some(self.severity)
127    }
128    fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
129        self.help.as_deref().map(|help| Box::new(help) as Box<_>)
130    }
131    fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
132        None
133    }
134    fn source_code(&self) -> Option<&dyn SourceCode> {
135        self.file.as_ref().map(|f| f as &dyn SourceCode)
136    }
137    fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
138        if self.labels.is_empty() {
139            None
140        } else {
141            Some(Box::new(self.labels.iter().cloned().map(|l| l.into())))
142        }
143    }
144    fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
145        None
146    }
147    fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
148        None
149    }
150}
151
152// RELATED ERROR
153// ================================================================================================
154
155/// This type allows rolling up a diagnostic into a parent error
156///
157/// This is necessary as [Report] cannot be used as the source error when deriving [Diagnostic].
158#[derive(Debug)]
159pub struct RelatedError(Report);
160
161impl RelatedError {
162    pub fn into_report(self) -> Report {
163        self.0
164    }
165
166    #[inline(always)]
167    pub fn as_diagnostic(&self) -> &dyn Diagnostic {
168        self.0.as_ref()
169    }
170}
171
172impl Diagnostic for RelatedError {
173    fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
174        self.as_diagnostic().code()
175    }
176    fn severity(&self) -> Option<Severity> {
177        self.as_diagnostic().severity()
178    }
179    fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
180        self.as_diagnostic().help()
181    }
182    fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
183        self.as_diagnostic().url()
184    }
185    fn source_code(&self) -> Option<&dyn SourceCode> {
186        self.as_diagnostic().source_code()
187    }
188    fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
189        self.as_diagnostic().labels()
190    }
191    fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
192        self.as_diagnostic().related()
193    }
194    fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
195        self.as_diagnostic().diagnostic_source()
196    }
197}
198
199impl fmt::Display for RelatedError {
200    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
201        fmt::Display::fmt(&self.0, f)
202    }
203}
204
205#[cfg(feature = "std")]
206impl std::error::Error for RelatedError {
207    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
208        AsRef::<dyn std::error::Error>::as_ref(&self.0).source()
209    }
210}
211
212#[cfg(not(feature = "std"))]
213impl miette::StdError for RelatedError {
214    fn source(&self) -> Option<&(dyn miette::StdError + 'static)> {
215        AsRef::<dyn miette::StdError>::as_ref(&self.0).source()
216    }
217}
218
219impl From<Report> for RelatedError {
220    fn from(report: Report) -> Self {
221        Self(report)
222    }
223}
224
225impl RelatedError {
226    pub const fn new(report: Report) -> Self {
227        Self(report)
228    }
229
230    pub fn wrap<E>(error: E) -> Self
231    where
232        E: Diagnostic + Send + Sync + 'static,
233    {
234        Self(Report::new_boxed(Box::new(error)))
235    }
236}