solar_interface/diagnostics/
context.rs1use super::{
2 BugAbort, Diag, DiagBuilder, DiagMsg, DynEmitter, EmissionGuarantee, EmittedDiagnostics,
3 ErrorGuaranteed, FatalAbort, HumanBufferEmitter, Level, MultiSpan, SilentEmitter,
4 emitter::HumanEmitter,
5};
6use crate::{Result, SourceMap, Span};
7use anstream::ColorChoice;
8use solar_config::{CompileOpts, ErrorFormat};
9use solar_data_structures::{map::FxHashSet, sync::Mutex};
10use std::{borrow::Cow, fmt, hash::BuildHasher, num::NonZeroUsize, sync::Arc};
11
12#[derive(Clone, Copy, Debug)]
14pub struct DiagCtxtFlags {
15 pub can_emit_warnings: bool,
17 pub treat_err_as_bug: Option<NonZeroUsize>,
19 pub deduplicate_diagnostics: bool,
21 pub track_diagnostics: bool,
24}
25
26impl Default for DiagCtxtFlags {
27 fn default() -> Self {
28 Self {
29 can_emit_warnings: true,
30 treat_err_as_bug: None,
31 deduplicate_diagnostics: true,
32 track_diagnostics: cfg!(debug_assertions),
33 }
34 }
35}
36
37impl DiagCtxtFlags {
38 pub fn update_from_opts(&mut self, opts: &CompileOpts) {
45 self.deduplicate_diagnostics &= !opts.unstable.ui_testing;
46 self.track_diagnostics &= !opts.unstable.ui_testing;
47 self.track_diagnostics |= opts.unstable.track_diagnostics;
48 self.can_emit_warnings &= !opts.no_warnings;
49 }
50}
51
52pub struct DiagCtxt {
56 inner: Mutex<DiagCtxtInner>,
57}
58
59impl fmt::Debug for DiagCtxt {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 let mut s = f.debug_struct("DiagCtxt");
62 if let Some(inner) = self.inner.try_lock() {
63 s.field("flags", &inner.flags)
64 .field("err_count", &inner.err_count)
65 .field("warn_count", &inner.warn_count)
66 .field("note_count", &inner.note_count);
67 } else {
68 s.field("inner", &format_args!("<locked>"));
69 }
70 s.finish_non_exhaustive()
71 }
72}
73
74struct DiagCtxtInner {
75 emitter: Box<DynEmitter>,
76
77 flags: DiagCtxtFlags,
78 allowed_diagnostic_codes: FxHashSet<String>,
79
80 err_count: usize,
85 deduplicated_err_count: usize,
86 warn_count: usize,
88 deduplicated_warn_count: usize,
89 note_count: usize,
91 deduplicated_note_count: usize,
92
93 emitted_diagnostics: FxHashSet<u64>,
96}
97
98impl DiagCtxt {
99 pub fn new(emitter: Box<DynEmitter>) -> Self {
101 Self {
102 inner: Mutex::new(DiagCtxtInner {
103 emitter,
104 flags: DiagCtxtFlags::default(),
105 allowed_diagnostic_codes: FxHashSet::default(),
106 err_count: 0,
107 deduplicated_err_count: 0,
108 warn_count: 0,
109 deduplicated_warn_count: 0,
110 note_count: 0,
111 deduplicated_note_count: 0,
112 emitted_diagnostics: FxHashSet::default(),
113 }),
114 }
115 }
116
117 pub fn new_early() -> Self {
120 Self::with_stderr_emitter(None).with_flags(|flags| flags.track_diagnostics = false)
121 }
122
123 pub fn with_test_emitter(source_map: Option<Arc<SourceMap>>) -> Self {
125 Self::new(Box::new(HumanEmitter::test().source_map(source_map)))
126 }
127
128 pub fn with_stderr_emitter(source_map: Option<Arc<SourceMap>>) -> Self {
130 Self::with_stderr_emitter_and_color(source_map, ColorChoice::Auto)
131 }
132
133 pub fn with_stderr_emitter_and_color(
135 source_map: Option<Arc<SourceMap>>,
136 color_choice: ColorChoice,
137 ) -> Self {
138 Self::new(Box::new(HumanEmitter::stderr(color_choice).source_map(source_map)))
139 }
140
141 pub fn with_silent_emitter(fatal_note: Option<String>) -> Self {
145 let fatal_emitter = HumanEmitter::stderr(Default::default());
146 Self::new(Box::new(SilentEmitter::new(fatal_emitter).with_note(fatal_note)))
147 .disable_warnings()
148 }
149
150 pub fn with_buffer_emitter(
152 source_map: Option<Arc<SourceMap>>,
153 color_choice: ColorChoice,
154 ) -> Self {
155 Self::new(Box::new(HumanBufferEmitter::new(color_choice).source_map(source_map)))
156 }
157
158 pub fn from_opts(opts: &solar_config::CompileOpts) -> Self {
174 let source_map = Arc::new(SourceMap::empty());
175 let emitter: Box<DynEmitter> = match opts.error_format {
176 ErrorFormat::Human => {
177 let human = HumanEmitter::stderr(opts.color)
178 .source_map(Some(source_map))
179 .ui_testing(opts.unstable.ui_testing)
180 .human_kind(opts.error_format_human)
181 .terminal_width(opts.diagnostic_width);
182 Box::new(human)
183 }
184 #[cfg(feature = "json")]
185 ErrorFormat::Json | ErrorFormat::RustcJson => {
186 let writer = Box::new(std::io::BufWriter::new(std::io::stderr()));
188 let json = crate::diagnostics::JsonEmitter::new(writer, source_map)
189 .pretty(opts.pretty_json_err)
190 .rustc_like(matches!(opts.error_format, ErrorFormat::RustcJson))
191 .ui_testing(opts.unstable.ui_testing)
192 .human_kind(opts.error_format_human)
193 .terminal_width(opts.diagnostic_width);
194 Box::new(json)
195 }
196 format => unimplemented!("{format:?}"),
197 };
198 Self::new(emitter)
199 .with_flags(|flags| flags.update_from_opts(opts))
200 .with_allowed_diagnostic_codes(opts.allow.iter().cloned())
201 }
202
203 pub fn with_allowed_diagnostic_codes(
205 mut self,
206 codes: impl IntoIterator<Item = String>,
207 ) -> Self {
208 self.set_allowed_diagnostic_codes_mut(codes);
209 self
210 }
211
212 pub fn make_silent(&self, fatal_note: Option<String>, emit_fatal: bool) {
214 self.wrap_emitter(|prev| {
215 Box::new(SilentEmitter::new_boxed(emit_fatal.then_some(prev)).with_note(fatal_note))
216 });
217 }
218
219 pub fn set_emitter(&self, emitter: Box<DynEmitter>) -> Box<DynEmitter> {
221 std::mem::replace(&mut self.inner.lock().emitter, emitter)
222 }
223
224 pub fn wrap_emitter(&self, f: impl FnOnce(Box<DynEmitter>) -> Box<DynEmitter>) {
226 struct FakeEmitter;
227 impl crate::diagnostics::Emitter for FakeEmitter {
228 fn emit_diagnostic(&mut self, _diagnostic: &mut Diag) {}
229 }
230
231 let mut inner = self.inner.lock();
232 let prev = std::mem::replace(&mut inner.emitter, Box::new(FakeEmitter));
233 inner.emitter = f(prev);
234 }
235
236 pub fn source_map(&self) -> Option<Arc<SourceMap>> {
238 self.inner.lock().emitter.source_map().cloned()
239 }
240
241 pub fn source_map_mut(&mut self) -> Option<&Arc<SourceMap>> {
243 self.inner.get_mut().emitter.source_map()
244 }
245
246 pub fn with_flags(mut self, f: impl FnOnce(&mut DiagCtxtFlags)) -> Self {
248 self.set_flags_mut(f);
249 self
250 }
251
252 pub fn set_flags(&self, f: impl FnOnce(&mut DiagCtxtFlags)) {
254 f(&mut self.inner.lock().flags);
255 }
256
257 pub fn set_flags_mut(&mut self, f: impl FnOnce(&mut DiagCtxtFlags)) {
259 f(&mut self.inner.get_mut().flags);
260 }
261
262 pub fn set_allowed_diagnostic_codes(&self, codes: impl IntoIterator<Item = String>) {
264 self.inner.lock().allowed_diagnostic_codes.extend(codes);
265 }
266
267 pub fn set_allowed_diagnostic_codes_mut(&mut self, codes: impl IntoIterator<Item = String>) {
269 self.inner.get_mut().allowed_diagnostic_codes.extend(codes);
270 }
271
272 pub fn disable_warnings(self) -> Self {
274 self.with_flags(|f| f.can_emit_warnings = false)
275 }
276
277 pub fn track_diagnostics(&self) -> bool {
279 self.inner.lock().flags.track_diagnostics
280 }
281
282 #[inline]
284 pub fn emit_diagnostic(&self, mut diagnostic: Diag) -> Result<(), ErrorGuaranteed> {
285 self.emit_diagnostic_without_consuming(&mut diagnostic)
286 }
287
288 pub(super) fn emit_diagnostic_without_consuming(
293 &self,
294 diagnostic: &mut Diag,
295 ) -> Result<(), ErrorGuaranteed> {
296 self.inner.lock().emit_diagnostic_without_consuming(diagnostic)
297 }
298
299 pub fn err_count(&self) -> usize {
301 self.inner.lock().err_count
302 }
303
304 pub fn has_errors(&self) -> Result<(), ErrorGuaranteed> {
306 if self.inner.lock().has_errors() { Err(ErrorGuaranteed::new_unchecked()) } else { Ok(()) }
307 }
308
309 pub fn warn_count(&self) -> usize {
311 self.inner.lock().warn_count
312 }
313
314 pub fn note_count(&self) -> usize {
316 self.inner.lock().note_count
317 }
318
319 #[inline]
337 pub fn emitted_diagnostics_result(
338 &self,
339 ) -> Option<Result<EmittedDiagnostics, EmittedDiagnostics>> {
340 let inner = self.inner.lock();
341 let diags = EmittedDiagnostics(inner.emitter.local_buffer()?.to_string());
342 Some(if inner.has_errors() { Err(diags) } else { Ok(diags) })
343 }
344
345 pub fn emitted_diagnostics(&self) -> Option<EmittedDiagnostics> {
350 let inner = self.inner.lock();
351 Some(EmittedDiagnostics(inner.emitter.local_buffer()?.to_string()))
352 }
353
354 pub fn emitted_errors(&self) -> Option<Result<(), EmittedDiagnostics>> {
359 let inner = self.inner.lock();
360 let buffer = inner.emitter.local_buffer()?;
361 Some(if inner.has_errors() { Err(EmittedDiagnostics(buffer.to_string())) } else { Ok(()) })
362 }
363
364 pub fn print_error_count(&self) -> Result {
366 self.inner.lock().print_error_count()
367 }
368}
369
370impl DiagCtxt {
374 #[track_caller]
376 pub fn diag<G: EmissionGuarantee>(
377 &self,
378 level: Level,
379 msg: impl Into<DiagMsg>,
380 ) -> DiagBuilder<'_, G> {
381 DiagBuilder::new(self, level, msg)
382 }
383
384 #[track_caller]
386 #[cold]
387 pub fn bug(&self, msg: impl Into<DiagMsg>) -> DiagBuilder<'_, BugAbort> {
388 self.diag(Level::Bug, msg)
389 }
390
391 #[track_caller]
393 #[cold]
394 pub fn fatal(&self, msg: impl Into<DiagMsg>) -> DiagBuilder<'_, FatalAbort> {
395 self.diag(Level::Fatal, msg)
396 }
397
398 #[track_caller]
400 pub fn err(&self, msg: impl Into<DiagMsg>) -> DiagBuilder<'_, ErrorGuaranteed> {
401 self.diag(Level::Error, msg)
402 }
403
404 #[track_caller]
406 pub fn emit_err(&self, span: impl Into<MultiSpan>, msg: impl Into<DiagMsg>) -> ErrorGuaranteed {
407 self.err(msg).span(span).emit()
408 }
409
410 #[track_caller]
412 pub fn emit_err_note(
413 &self,
414 span: impl Into<MultiSpan>,
415 msg: impl Into<DiagMsg>,
416 note: impl Into<DiagMsg>,
417 ) -> ErrorGuaranteed {
418 self.err(msg).span(span).note(note).emit()
419 }
420
421 #[track_caller]
423 pub fn emit_err_span_note(
424 &self,
425 span: impl Into<MultiSpan>,
426 msg: impl Into<DiagMsg>,
427 note_span: impl Into<MultiSpan>,
428 note: impl Into<DiagMsg>,
429 ) -> ErrorGuaranteed {
430 self.err(msg).span(span).span_note(note_span, note).emit()
431 }
432
433 #[track_caller]
435 pub fn emit_err_label(
436 &self,
437 span: impl Into<MultiSpan>,
438 msg: impl Into<DiagMsg>,
439 label_span: Span,
440 label: impl Into<DiagMsg>,
441 ) -> ErrorGuaranteed {
442 self.err(msg).span(span).span_label(label_span, label).emit()
443 }
444
445 #[track_caller]
449 pub fn warn(&self, msg: impl Into<DiagMsg>) -> DiagBuilder<'_, ()> {
450 self.diag(Level::Warning, msg)
451 }
452
453 #[track_caller]
455 pub fn help(&self, msg: impl Into<DiagMsg>) -> DiagBuilder<'_, ()> {
456 self.diag(Level::Help, msg)
457 }
458
459 #[track_caller]
461 pub fn note(&self, msg: impl Into<DiagMsg>) -> DiagBuilder<'_, ()> {
462 self.diag(Level::Note, msg)
463 }
464}
465
466impl DiagCtxtInner {
467 fn emit_diagnostic(&mut self, mut diagnostic: Diag) -> Result<(), ErrorGuaranteed> {
468 self.emit_diagnostic_without_consuming(&mut diagnostic)
469 }
470
471 fn emit_diagnostic_without_consuming(
472 &mut self,
473 diagnostic: &mut Diag,
474 ) -> Result<(), ErrorGuaranteed> {
475 if self.is_allowed_diagnostic(diagnostic) {
476 return Ok(());
477 }
478
479 if diagnostic.level == Level::Warning && !self.flags.can_emit_warnings {
480 return Ok(());
481 }
482
483 if diagnostic.level == Level::Allow {
484 return Ok(());
485 }
486
487 if matches!(diagnostic.level, Level::Error | Level::Fatal) && self.treat_err_as_bug() {
488 diagnostic.level = Level::Bug;
489 }
490
491 let already_emitted = self.insert_diagnostic(diagnostic);
492 if !(self.flags.deduplicate_diagnostics && already_emitted) {
493 diagnostic.children.retain(|sub| {
495 if !matches!(sub.level, Level::OnceNote | Level::OnceHelp) {
496 return true;
497 }
498 let sub_already_emitted = self.insert_diagnostic(sub);
499 !sub_already_emitted
500 });
501
502 self.emitter.emit_diagnostic(diagnostic);
509 if diagnostic.is_error() {
510 self.deduplicated_err_count += 1;
511 } else if diagnostic.level == Level::Warning {
512 self.deduplicated_warn_count += 1;
513 } else if diagnostic.is_note() {
514 self.deduplicated_note_count += 1;
515 }
516 }
517
518 if diagnostic.is_error() {
519 self.bump_err_count();
520 Err(ErrorGuaranteed::new_unchecked())
521 } else {
522 if diagnostic.level == Level::Warning {
523 self.bump_warn_count();
524 } else if diagnostic.is_note() {
525 self.bump_note_count();
526 }
527 Ok(())
529 }
530 }
531
532 fn print_error_count(&mut self) -> Result {
533 if self.treat_err_as_bug() {
536 return Ok(());
537 }
538
539 let errors = match self.deduplicated_err_count {
540 0 => None,
541 1 => Some(Cow::from("aborting due to 1 previous error")),
542 count => Some(Cow::from(format!("aborting due to {count} previous errors"))),
543 };
544
545 let mut others = Vec::with_capacity(2);
546 match self.deduplicated_warn_count {
547 1 => others.push(Cow::from("1 warning emitted")),
548 count if count > 1 => others.push(Cow::from(format!("{count} warnings emitted"))),
549 _ => {}
550 }
551 match self.deduplicated_note_count {
552 1 => others.push(Cow::from("1 note emitted")),
553 count if count > 1 => others.push(Cow::from(format!("{count} notes emitted"))),
554 _ => {}
555 }
556
557 match (errors, others.is_empty()) {
558 (None, true) => Ok(()),
559 (None, false) => {
560 if self.flags.track_diagnostics {
562 let msg = others.join(", ");
563 self.emitter.emit_diagnostic(&mut Diag::new(Level::Warning, msg));
564 }
565 Ok(())
566 }
567 (Some(e), true) => self.emit_diagnostic(Diag::new(Level::Error, e)),
568 (Some(e), false) => self
569 .emit_diagnostic(Diag::new(Level::Error, format!("{}; {}", e, others.join(", ")))),
570 }
571 }
572
573 fn insert_diagnostic<H: std::hash::Hash>(&mut self, diag: &H) -> bool {
576 let hash = solar_data_structures::map::rustc_hash::FxBuildHasher.hash_one(diag);
577 !self.emitted_diagnostics.insert(hash)
578 }
579
580 fn treat_err_as_bug(&self) -> bool {
581 self.flags.treat_err_as_bug.is_some_and(|c| self.err_count >= c.get())
582 }
583
584 fn is_allowed_diagnostic(&self, diagnostic: &Diag) -> bool {
585 diagnostic.level == Level::Warning
586 && diagnostic.id().is_some_and(|id| self.allowed_diagnostic_codes.contains(id))
587 }
588
589 fn bump_err_count(&mut self) {
590 self.err_count += 1;
591 self.panic_if_treat_err_as_bug();
592 }
593
594 fn bump_warn_count(&mut self) {
595 self.warn_count += 1;
596 }
597
598 fn bump_note_count(&mut self) {
599 self.note_count += 1;
600 }
601
602 fn has_errors(&self) -> bool {
603 self.err_count > 0
604 }
605
606 fn panic_if_treat_err_as_bug(&self) {
607 if self.treat_err_as_bug() {
608 match (self.err_count, self.flags.treat_err_as_bug.unwrap().get()) {
609 (1, 1) => panic!("aborting due to `-Z treat-err-as-bug=1`"),
610 (count, val) => {
611 panic!("aborting after {count} errors due to `-Z treat-err-as-bug={val}`")
612 }
613 }
614 }
615 }
616}