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, IntoDiagnostic, Label, LabeledSpan, RelatedError, RelatedLabel, Report, Severity,
18 WrapErr,
19};
20pub use miden_core::debuginfo::*;
21pub use midenc_hir_macros::Spanned;
22
23#[cfg(feature = "std")]
24pub use crate::emitter::CaptureEmitter;
25pub use crate::emitter::{Buffer, DefaultEmitter, Emitter, NullEmitter};
26use crate::{ColorChoice, Verbosity, Warnings};
27
28#[derive(Default, Debug, Copy, Clone)]
29pub struct DiagnosticsConfig {
30 pub verbosity: Verbosity,
31 pub warnings: Warnings,
32}
33
34pub struct DiagnosticsHandler {
35 emitter: Arc<dyn Emitter>,
36 source_manager: Arc<dyn SourceManager>,
37 err_count: AtomicUsize,
38 verbosity: Verbosity,
39 warnings: Warnings,
40 silent: bool,
41}
42
43impl Default for DiagnosticsHandler {
44 fn default() -> Self {
45 let emitter = Arc::new(DefaultEmitter::new(ColorChoice::Auto));
46 let source_manager = Arc::new(DefaultSourceManager::default());
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>,
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> {
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 let out = PrintDiagnostic::new(diagnostic).to_string();
181 self.emitter.print(out).unwrap();
182 }
183}
184
185#[derive(thiserror::Error, Diagnostic, Debug)]
186#[error("{}", .report)]
187#[diagnostic(
188 severity(Error),
189 help("this warning was promoted to an error via --warnings-as-errors")
190)]
191struct WarningAsError {
192 #[diagnostic_source]
193 report: Report,
194}
195impl From<Report> for WarningAsError {
196 fn from(report: Report) -> Self {
197 Self { report }
198 }
199}
200
201pub struct InFlightDiagnosticBuilder<'h> {
203 handler: &'h DiagnosticsHandler,
204 diagnostic: InFlightDiagnostic,
205 primary_source_id: Option<SourceId>,
207 references: BTreeMap<SourceId, RelatedLabel>,
209}
210impl<'h> InFlightDiagnosticBuilder<'h> {
211 pub(crate) fn new(handler: &'h DiagnosticsHandler, severity: Severity) -> Self {
212 Self {
213 handler,
214 diagnostic: InFlightDiagnostic::new(severity),
215 primary_source_id: None,
216 references: BTreeMap::default(),
217 }
218 }
219
220 pub fn with_message(mut self, message: impl ToString) -> Self {
222 self.diagnostic.message = message.to_string();
223 self
224 }
225
226 pub fn with_code(mut self, code: impl ToString) -> Self {
228 self.diagnostic.code = Some(code.to_string());
229 self
230 }
231
232 pub fn with_url(mut self, url: impl ToString) -> Self {
234 self.diagnostic.url = Some(url.to_string());
235 self
236 }
237
238 pub fn with_primary_span(mut self, span: SourceSpan) -> Self {
240 use miden_assembly::diagnostics::LabeledSpan;
241
242 assert!(self.diagnostic.labels.is_empty(), "cannot set the primary span more than once");
243 let source_id = span.source_id();
244 let source_file = self.handler.source_manager.get(source_id).ok();
245 self.primary_source_id = Some(source_id);
246 self.diagnostic.source_code = source_file;
247 self.diagnostic.labels.push(LabeledSpan::new_primary_with_span(None, span));
248 self
249 }
250
251 pub fn with_primary_label(mut self, span: SourceSpan, message: impl ToString) -> Self {
257 use miden_assembly::diagnostics::LabeledSpan;
258
259 assert!(self.diagnostic.labels.is_empty(), "cannot set the primary span more than once");
260 let source_id = span.source_id();
261 let source_file = self.handler.source_manager.get(source_id).ok();
262 self.primary_source_id = Some(source_id);
263 self.diagnostic.source_code = source_file;
264 self.diagnostic
265 .labels
266 .push(LabeledSpan::new_primary_with_span(Some(message.to_string()), span));
267 self
268 }
269
270 pub fn with_secondary_label(mut self, span: SourceSpan, message: impl ToString) -> Self {
276 use miden_assembly::diagnostics::LabeledSpan;
277
278 assert!(
279 !self.diagnostic.labels.is_empty(),
280 "must set a primary label before any secondary labels"
281 );
282 let source_id = span.source_id();
283 if source_id != self.primary_source_id.unwrap_or_default() {
284 let related = self.references.entry(source_id).or_insert_with(|| {
285 let source_file = self.handler.source_manager.get(source_id).ok();
286 RelatedLabel::advice("see diagnostics for more information")
287 .with_source_file(source_file)
288 });
289 related.labels.push(Label::new(span, message.to_string()));
290 } else {
291 self.diagnostic
292 .labels
293 .push(LabeledSpan::new_with_span(Some(message.to_string()), span));
294 }
295 self
296 }
297
298 pub fn with_help(mut self, note: impl ToString) -> Self {
305 self.diagnostic.help = Some(note.to_string());
306 self
307 }
308
309 pub fn into_report(mut self) -> Report {
311 if self.diagnostic.message.is_empty() {
312 self.diagnostic.message = "reported".into();
313 }
314 self.diagnostic.related.extend(self.references.into_values());
315 Report::from(self.diagnostic)
316 }
317
318 pub fn emit(self) {
320 let handler = self.handler;
321 handler.emit(self.into_report());
322 }
323}
324
325#[derive(Default)]
326struct InFlightDiagnostic {
327 source_code: Option<Arc<SourceFile>>,
328 severity: Option<Severity>,
329 message: String,
330 code: Option<String>,
331 help: Option<String>,
332 url: Option<String>,
333 labels: Vec<LabeledSpan>,
334 related: Vec<RelatedLabel>,
335}
336
337impl InFlightDiagnostic {
338 fn new(severity: Severity) -> Self {
339 Self {
340 severity: Some(severity),
341 ..Default::default()
342 }
343 }
344}
345
346impl fmt::Display for InFlightDiagnostic {
347 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
348 write!(f, "{}", &self.message)
349 }
350}
351
352impl fmt::Debug for InFlightDiagnostic {
353 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
354 write!(f, "{}", &self.message)
355 }
356}
357
358impl core::error::Error for InFlightDiagnostic {}
359
360impl Diagnostic for InFlightDiagnostic {
361 fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
362 self.code.as_ref().map(Box::new).map(|c| c as Box<dyn Display>)
363 }
364
365 fn severity(&self) -> Option<Severity> {
366 self.severity
367 }
368
369 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
370 self.help.as_ref().map(Box::new).map(|c| c as Box<dyn Display>)
371 }
372
373 fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
374 self.url.as_ref().map(Box::new).map(|c| c as Box<dyn Display>)
375 }
376
377 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
378 if self.labels.is_empty() {
379 return None;
380 }
381 let iter = self.labels.iter().cloned();
382 Some(Box::new(iter) as Box<dyn Iterator<Item = LabeledSpan>>)
383 }
384
385 fn related(&self) -> Option<Box<dyn Iterator<Item = &dyn Diagnostic> + '_>> {
386 if self.related.is_empty() {
387 return None;
388 }
389
390 let iter = self.related.iter().map(|r| r as &dyn Diagnostic);
391 Some(Box::new(iter) as Box<dyn Iterator<Item = &dyn Diagnostic>>)
392 }
393
394 fn diagnostic_source(&self) -> Option<&(dyn Diagnostic + '_)> {
395 None
396 }
397}