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