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