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