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)]
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    #[allow(unused)]
73    pub fn warning<S>(message: S) -> Self
74    where
75        Cow<'static, str>: From<S>,
76    {
77        Self::new(Severity::Warning, message)
78    }
79
80    #[allow(unused)]
81    pub fn advice<S>(message: S) -> Self
82    where
83        Cow<'static, str>: From<S>,
84    {
85        Self::new(Severity::Advice, message)
86    }
87
88    pub fn with_source_file(mut self, file: Option<Arc<SourceFile>>) -> Self {
89        self.file = file;
90        self
91    }
92
93    pub fn with_labeled_span<S>(self, span: SourceSpan, message: S) -> Self
94    where
95        Cow<'static, str>: From<S>,
96    {
97        let range = span.into_range();
98        self.with_label(Label::new((range.start as usize)..(range.end as usize), message))
99    }
100
101    pub fn with_label(mut self, label: Label) -> Self {
102        self.labels.push(label);
103        self
104    }
105
106    #[allow(unused)]
107    pub fn with_labels<I>(mut self, labels: I) -> Self
108    where
109        I: IntoIterator<Item = Label>,
110    {
111        self.labels.extend(labels);
112        self
113    }
114
115    pub fn with_help<S>(mut self, help: S) -> Self
116    where
117        Cow<'static, str>: From<S>,
118    {
119        self.help = Some(help.into());
120        self
121    }
122}
123
124impl Diagnostic for RelatedLabel {
125    fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
126        None
127    }
128    fn severity(&self) -> Option<Severity> {
129        Some(self.severity)
130    }
131    fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
132        self.help.as_deref().map(|help| Box::new(help) as Box<_>)
133    }
134    fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
135        None
136    }
137    fn source_code(&self) -> Option<&dyn SourceCode> {
138        self.file.as_ref().map(|f| f as &dyn SourceCode)
139    }
140    fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
141        if self.labels.is_empty() {
142            None
143        } else {
144            Some(Box::new(self.labels.iter().cloned().map(|l| l.into())))
145        }
146    }
147    fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
148        None
149    }
150    fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
151        None
152    }
153}
154
155// RELATED ERROR
156// ================================================================================================
157
158/// This type allows rolling up a diagnostic into a parent error
159///
160/// This is necessary as [Report] cannot be used as the source error when deriving [Diagnostic].
161#[derive(Debug)]
162pub struct RelatedError(Report);
163
164impl RelatedError {
165    pub fn into_report(self) -> Report {
166        self.0
167    }
168
169    #[inline(always)]
170    pub fn as_diagnostic(&self) -> &dyn Diagnostic {
171        self.0.as_ref()
172    }
173}
174
175impl Diagnostic for RelatedError {
176    fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
177        self.as_diagnostic().code()
178    }
179    fn severity(&self) -> Option<Severity> {
180        self.as_diagnostic().severity()
181    }
182    fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
183        self.as_diagnostic().help()
184    }
185    fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
186        self.as_diagnostic().url()
187    }
188    fn source_code(&self) -> Option<&dyn SourceCode> {
189        self.as_diagnostic().source_code()
190    }
191    fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
192        self.as_diagnostic().labels()
193    }
194    fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
195        self.as_diagnostic().related()
196    }
197    fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
198        self.as_diagnostic().diagnostic_source()
199    }
200}
201
202impl fmt::Display for RelatedError {
203    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
204        fmt::Display::fmt(&self.0, f)
205    }
206}
207
208#[cfg(feature = "std")]
209impl std::error::Error for RelatedError {
210    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
211        AsRef::<dyn std::error::Error>::as_ref(&self.0).source()
212    }
213}
214
215#[cfg(not(feature = "std"))]
216impl miette::StdError for RelatedError {
217    fn source(&self) -> Option<&(dyn miette::StdError + 'static)> {
218        AsRef::<dyn miette::StdError>::as_ref(&self.0).source()
219    }
220}
221
222impl From<Report> for RelatedError {
223    fn from(report: Report) -> Self {
224        Self(report)
225    }
226}
227
228impl RelatedError {
229    pub const fn new(report: Report) -> Self {
230        Self(report)
231    }
232
233    pub fn wrap<E>(error: E) -> Self
234    where
235        E: Diagnostic + Send + Sync + 'static,
236    {
237        Self(Report::new_boxed(Box::new(error)))
238    }
239}