1use alloc::{
2 boxed::Box,
3 collections::BTreeMap,
4 fmt::{self, Display},
5 format,
6 string::{String, ToString},
7 sync::Arc,
8 vec::Vec,
9};
10use core::sync::atomic::{AtomicUsize, Ordering};
11
12pub use miden_assembly::diagnostics::{
13 miette,
14 miette::MietteDiagnostic as AdHocDiagnostic,
15 reporting,
16 reporting::{PrintDiagnostic, ReportHandlerOpts},
17 Diagnostic, Label, LabeledSpan, RelatedError, RelatedLabel, Report, Severity, WrapErr,
18};
19pub use miden_core::debuginfo::*;
20pub use midenc_hir_macros::Spanned;
21
22#[cfg(feature = "std")]
23pub use crate::emitter::CaptureEmitter;
24pub use crate::emitter::{Buffer, DefaultEmitter, Emitter, NullEmitter};
25use crate::{ColorChoice, Verbosity, Warnings};
26
27#[derive(Default, Debug, Copy, Clone)]
28pub struct DiagnosticsConfig {
29 pub verbosity: Verbosity,
30 pub warnings: Warnings,
31}
32
33pub struct DiagnosticsHandler {
34 emitter: Arc<dyn Emitter>,
35 source_manager: Arc<dyn SourceManager>,
36 err_count: AtomicUsize,
37 verbosity: Verbosity,
38 warnings: Warnings,
39 silent: bool,
40}
41
42impl Default for DiagnosticsHandler {
43 fn default() -> Self {
44 let emitter = Arc::new(DefaultEmitter::new(ColorChoice::Auto));
45 let source_manager = Arc::new(DefaultSourceManager::default());
46 Self::new(Default::default(), source_manager, emitter)
47 }
48}
49
50unsafe impl Send for DiagnosticsHandler {}
53unsafe impl Sync for DiagnosticsHandler {}
54
55impl DiagnosticsHandler {
56 pub fn new(
59 config: DiagnosticsConfig,
60 source_manager: Arc<dyn SourceManager>,
61 emitter: Arc<dyn Emitter>,
62 ) -> Self {
63 let warnings = match config.warnings {
64 Warnings::Error => Warnings::Error,
65 _ if config.verbosity > Verbosity::Warning => Warnings::None,
66 warnings => warnings,
67 };
68 Self {
69 emitter,
70 source_manager,
71 err_count: AtomicUsize::new(0),
72 verbosity: config.verbosity,
73 warnings,
74 silent: config.verbosity == Verbosity::Silent,
75 }
76 }
77
78 #[inline]
79 pub fn source_manager(&self) -> Arc<dyn SourceManager> {
80 self.source_manager.clone()
81 }
82
83 #[inline]
84 pub fn source_manager_ref(&self) -> &dyn SourceManager {
85 self.source_manager.as_ref()
86 }
87
88 pub fn has_errors(&self) -> bool {
90 self.err_count.load(Ordering::Relaxed) > 0
91 }
92
93 #[track_caller]
95 pub fn abort_if_errors(&self) {
96 if self.has_errors() {
97 panic!("Compiler has encountered unexpected errors. See diagnostics for details.")
98 }
99 }
100
101 pub fn report(&self, report: impl Into<Report>) {
103 self.emit(report.into())
104 }
105
106 pub fn error(&self, error: impl ToString) {
108 self.emit(Report::msg(error.to_string()));
109 }
110
111 pub fn warn(&self, warning: impl ToString) {
115 if matches!(self.warnings, Warnings::Error) {
116 return self.error(warning);
117 }
118 let diagnostic = AdHocDiagnostic::new(warning.to_string()).with_severity(Severity::Warning);
119 self.emit(diagnostic);
120 }
121
122 pub fn info(&self, message: impl ToString) {
124 if self.verbosity > Verbosity::Info {
125 return;
126 }
127 let diagnostic = AdHocDiagnostic::new(message.to_string()).with_severity(Severity::Advice);
128 self.emit(diagnostic);
129 }
130
131 pub fn diagnostic(&self, severity: Severity) -> InFlightDiagnosticBuilder<'_> {
136 InFlightDiagnosticBuilder::new(self, severity)
137 }
138
139 #[inline(never)]
141 pub fn emit(&self, diagnostic: impl Into<Report>) {
142 let diagnostic: Report = diagnostic.into();
143 let diagnostic = match diagnostic.severity() {
144 Some(Severity::Advice) if self.verbosity > Verbosity::Info => return,
145 Some(Severity::Warning) => match self.warnings {
146 Warnings::None => return,
147 Warnings::All => diagnostic,
148 Warnings::Error => {
149 self.err_count.fetch_add(1, Ordering::Relaxed);
150 Report::from(WarningAsError::from(diagnostic))
151 }
152 },
153 Some(Severity::Error) => {
154 self.err_count.fetch_add(1, Ordering::Relaxed);
155 diagnostic
156 }
157 _ => diagnostic,
158 };
159
160 if self.silent {
161 return;
162 }
163
164 self.write_report(diagnostic);
165 }
166
167 #[cfg(feature = "std")]
168 fn write_report(&self, diagnostic: Report) {
169 use std::io::Write;
170
171 let mut buffer = self.emitter.buffer();
172 let printer = PrintDiagnostic::new(diagnostic);
173 write!(&mut buffer, "{printer}").expect("failed to write diagnostic to buffer");
174 self.emitter.print(buffer).unwrap();
175 }
176
177 #[cfg(not(feature = "std"))]
178 fn write_report(&self, diagnostic: Report) {
179 use core::fmt::Write;
180
181 let mut buffer = self.emitter.buffer();
182 let printer = PrintDiagnostic::new(diagnostic);
183 write!(&mut buffer, "{printer}").expect("failed to write diagnostic to buffer");
184 self.emitter.print(buffer).unwrap();
185 }
186}
187
188#[derive(thiserror::Error, Diagnostic, Debug)]
189#[error("{}", .report)]
190#[diagnostic(
191 severity(Error),
192 help("this warning was promoted to an error via --warnings-as-errors")
193)]
194struct WarningAsError {
195 #[diagnostic_source]
196 report: Report,
197}
198impl From<Report> for WarningAsError {
199 fn from(report: Report) -> Self {
200 Self { report }
201 }
202}
203
204pub struct InFlightDiagnosticBuilder<'h> {
206 handler: &'h DiagnosticsHandler,
207 diagnostic: InFlightDiagnostic,
208 primary_source_id: Option<SourceId>,
210 references: BTreeMap<SourceId, RelatedLabel>,
212}
213impl<'h> InFlightDiagnosticBuilder<'h> {
214 pub(crate) fn new(handler: &'h DiagnosticsHandler, severity: Severity) -> Self {
215 Self {
216 handler,
217 diagnostic: InFlightDiagnostic::new(severity),
218 primary_source_id: None,
219 references: BTreeMap::default(),
220 }
221 }
222
223 pub fn with_message(mut self, message: impl ToString) -> Self {
225 self.diagnostic.message = message.to_string();
226 self
227 }
228
229 pub fn with_code(mut self, code: impl ToString) -> Self {
231 self.diagnostic.code = Some(code.to_string());
232 self
233 }
234
235 pub fn with_url(mut self, url: impl ToString) -> Self {
237 self.diagnostic.url = Some(url.to_string());
238 self
239 }
240
241 pub fn with_primary_span(mut self, span: SourceSpan) -> Self {
243 use miden_assembly::diagnostics::LabeledSpan;
244
245 assert!(self.diagnostic.labels.is_empty(), "cannot set the primary span more than once");
246 let source_id = span.source_id();
247 let source_file = self.handler.source_manager.get(source_id).ok();
248 self.primary_source_id = Some(source_id);
249 self.diagnostic.source_code = source_file;
250 self.diagnostic.labels.push(LabeledSpan::new_primary_with_span(None, span));
251 self
252 }
253
254 pub fn with_primary_label(mut self, span: SourceSpan, message: impl ToString) -> Self {
260 use miden_assembly::diagnostics::LabeledSpan;
261
262 assert!(self.diagnostic.labels.is_empty(), "cannot set the primary span more than once");
263 let source_id = span.source_id();
264 let source_file = self.handler.source_manager.get(source_id).ok();
265 self.primary_source_id = Some(source_id);
266 self.diagnostic.source_code = source_file;
267 self.diagnostic
268 .labels
269 .push(LabeledSpan::new_primary_with_span(Some(message.to_string()), span));
270 self
271 }
272
273 pub fn with_secondary_label(mut self, span: SourceSpan, message: impl ToString) -> Self {
279 use miden_assembly::diagnostics::LabeledSpan;
280
281 assert!(
282 !self.diagnostic.labels.is_empty(),
283 "must set a primary label before any secondary labels"
284 );
285 let source_id = span.source_id();
286 if source_id != self.primary_source_id.unwrap_or_default() {
287 let related = self.references.entry(source_id).or_insert_with(|| {
288 let source_file = self.handler.source_manager.get(source_id).ok();
289 RelatedLabel::advice("see diagnostics for more information")
290 .with_source_file(source_file)
291 });
292 related.labels.push(Label::new(span, message.to_string()));
293 } else {
294 self.diagnostic
295 .labels
296 .push(LabeledSpan::new_with_span(Some(message.to_string()), span));
297 }
298 self
299 }
300
301 pub fn with_help(mut self, note: impl ToString) -> Self {
308 self.diagnostic.help = Some(note.to_string());
309 self
310 }
311
312 pub fn into_report(mut self) -> Report {
314 if self.diagnostic.message.is_empty() {
315 self.diagnostic.message = "reported".into();
316 }
317 self.diagnostic.related.extend(self.references.into_values());
318 Report::from(self.diagnostic)
319 }
320
321 pub fn emit(self) {
323 let handler = self.handler;
324 handler.emit(self.into_report());
325 }
326}
327
328#[derive(Default)]
329struct InFlightDiagnostic {
330 source_code: Option<Arc<SourceFile>>,
331 severity: Option<Severity>,
332 message: String,
333 code: Option<String>,
334 help: Option<String>,
335 url: Option<String>,
336 labels: Vec<LabeledSpan>,
337 related: Vec<RelatedLabel>,
338}
339
340impl InFlightDiagnostic {
341 fn new(severity: Severity) -> Self {
342 Self {
343 severity: Some(severity),
344 ..Default::default()
345 }
346 }
347}
348
349impl fmt::Display for InFlightDiagnostic {
350 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
351 write!(f, "{}", &self.message)
352 }
353}
354
355impl fmt::Debug for InFlightDiagnostic {
356 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
357 write!(f, "{}", &self.message)
358 }
359}
360
361impl core::error::Error for InFlightDiagnostic {}
362
363impl Diagnostic for InFlightDiagnostic {
364 fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
365 self.code.as_ref().map(Box::new).map(|c| c as Box<dyn Display>)
366 }
367
368 fn severity(&self) -> Option<Severity> {
369 self.severity
370 }
371
372 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
373 self.help.as_ref().map(Box::new).map(|c| c as Box<dyn Display>)
374 }
375
376 fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
377 self.url.as_ref().map(Box::new).map(|c| c as Box<dyn Display>)
378 }
379
380 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
381 if self.labels.is_empty() {
382 return None;
383 }
384 let iter = self.labels.iter().cloned();
385 Some(Box::new(iter) as Box<dyn Iterator<Item = LabeledSpan>>)
386 }
387
388 fn related(&self) -> Option<Box<dyn Iterator<Item = &dyn Diagnostic> + '_>> {
389 if self.related.is_empty() {
390 return None;
391 }
392
393 let iter = self.related.iter().map(|r| r as &dyn Diagnostic);
394 Some(Box::new(iter) as Box<dyn Iterator<Item = &dyn Diagnostic>>)
395 }
396
397 fn diagnostic_source(&self) -> Option<&(dyn Diagnostic + '_)> {
398 None
399 }
400}
401
402pub use self::into_diagnostic::{DiagnosticError, IntoDiagnostic};
403
404mod into_diagnostic {
405 use alloc::boxed::Box;
406
407 #[derive(Debug)]
410 pub struct DiagnosticError<E>(Box<E>);
411 impl<E> DiagnosticError<E> {
412 pub fn new(error: E) -> Self {
413 Self(Box::new(error))
414 }
415 }
416 impl<E: core::fmt::Debug + core::fmt::Display + 'static> miden_assembly::diagnostics::Diagnostic
417 for DiagnosticError<E>
418 {
419 }
420 impl<E: core::fmt::Display> core::fmt::Display for DiagnosticError<E> {
421 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
422 core::fmt::Display::fmt(self.0.as_ref(), f)
423 }
424 }
425 impl<E: core::fmt::Debug + core::fmt::Display + 'static> core::error::Error for DiagnosticError<E> {
426 default fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
427 None
428 }
429
430 default fn cause(&self) -> Option<&dyn core::error::Error> {
431 self.source()
432 }
433 }
434 impl<E: core::error::Error + 'static> core::error::Error for DiagnosticError<E> {
435 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
436 self.0.source()
437 }
438 }
439 unsafe impl<E: Send> Send for DiagnosticError<E> {}
440 unsafe impl<E: Sync> Sync for DiagnosticError<E> {}
441
442 pub trait IntoDiagnostic<T, E> {
454 fn into_diagnostic(self) -> Result<T, super::Report>;
457 }
458
459 impl<T, E: core::fmt::Debug + core::fmt::Display + Sync + Send + 'static> IntoDiagnostic<T, E>
460 for Result<T, E>
461 {
462 fn into_diagnostic(self) -> Result<T, super::Report> {
463 self.map_err(|e| DiagnosticError::new(e).into())
464 }
465 }
466}