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