solar_interface/diagnostics/
context.rs1use super::{
2 BugAbort, Diag, DiagBuilder, DiagMsg, DynEmitter, EmissionGuarantee, EmittedDiagnostics,
3 ErrorGuaranteed, FatalAbort, HumanBufferEmitter, Level, SilentEmitter, emitter::HumanEmitter,
4};
5use crate::{Result, SourceMap};
6use anstream::ColorChoice;
7use solar_config::{ErrorFormat, Opts};
8use solar_data_structures::{map::FxHashSet, sync::Mutex};
9use std::{borrow::Cow, fmt, hash::BuildHasher, num::NonZeroUsize, sync::Arc};
10
11#[derive(Clone, Copy, Debug)]
13pub struct DiagCtxtFlags {
14 pub can_emit_warnings: bool,
16 pub treat_err_as_bug: Option<NonZeroUsize>,
18 pub deduplicate_diagnostics: bool,
20 pub track_diagnostics: bool,
23}
24
25impl Default for DiagCtxtFlags {
26 fn default() -> Self {
27 Self {
28 can_emit_warnings: true,
29 treat_err_as_bug: None,
30 deduplicate_diagnostics: true,
31 track_diagnostics: cfg!(debug_assertions),
32 }
33 }
34}
35
36impl DiagCtxtFlags {
37 pub fn update_from_opts(&mut self, opts: &Opts) {
44 self.deduplicate_diagnostics &= !opts.unstable.ui_testing;
45 self.track_diagnostics &= !opts.unstable.ui_testing;
46 self.track_diagnostics |= opts.unstable.track_diagnostics;
47 self.can_emit_warnings |= !opts.no_warnings;
48 }
49}
50
51pub struct DiagCtxt {
55 inner: Mutex<DiagCtxtInner>,
56}
57
58impl fmt::Debug for DiagCtxt {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 let mut s = f.debug_struct("DiagCtxt");
61 if let Some(inner) = self.inner.try_lock() {
62 s.field("flags", &inner.flags)
63 .field("err_count", &inner.err_count)
64 .field("warn_count", &inner.warn_count)
65 .field("note_count", &inner.note_count);
66 } else {
67 s.field("inner", &format_args!("<locked>"));
68 }
69 s.finish_non_exhaustive()
70 }
71}
72
73struct DiagCtxtInner {
74 emitter: Box<DynEmitter>,
75
76 flags: DiagCtxtFlags,
77
78 err_count: usize,
83 deduplicated_err_count: usize,
84 warn_count: usize,
86 deduplicated_warn_count: usize,
87 note_count: usize,
89 deduplicated_note_count: usize,
90
91 emitted_diagnostics: FxHashSet<u64>,
94}
95
96impl DiagCtxt {
97 pub fn new(emitter: Box<DynEmitter>) -> Self {
99 Self {
100 inner: Mutex::new(DiagCtxtInner {
101 emitter,
102 flags: DiagCtxtFlags::default(),
103 err_count: 0,
104 deduplicated_err_count: 0,
105 warn_count: 0,
106 deduplicated_warn_count: 0,
107 note_count: 0,
108 deduplicated_note_count: 0,
109 emitted_diagnostics: FxHashSet::default(),
110 }),
111 }
112 }
113
114 pub fn new_early() -> Self {
117 Self::with_stderr_emitter(None).with_flags(|flags| flags.track_diagnostics = false)
118 }
119
120 pub fn with_test_emitter(source_map: Option<Arc<SourceMap>>) -> Self {
122 Self::new(Box::new(HumanEmitter::test().source_map(source_map)))
123 }
124
125 pub fn with_stderr_emitter(source_map: Option<Arc<SourceMap>>) -> Self {
127 Self::with_stderr_emitter_and_color(source_map, ColorChoice::Auto)
128 }
129
130 pub fn with_stderr_emitter_and_color(
132 source_map: Option<Arc<SourceMap>>,
133 color_choice: ColorChoice,
134 ) -> Self {
135 Self::new(Box::new(HumanEmitter::stderr(color_choice).source_map(source_map)))
136 }
137
138 pub fn with_silent_emitter(fatal_note: Option<String>) -> Self {
142 let fatal_emitter = HumanEmitter::stderr(Default::default());
143 Self::new(Box::new(SilentEmitter::new(fatal_emitter).with_note(fatal_note)))
144 .disable_warnings()
145 }
146
147 pub fn with_buffer_emitter(
149 source_map: Option<Arc<SourceMap>>,
150 color_choice: ColorChoice,
151 ) -> Self {
152 Self::new(Box::new(HumanBufferEmitter::new(color_choice).source_map(source_map)))
153 }
154
155 pub fn from_opts(opts: &solar_config::Opts) -> Self {
171 let source_map = Arc::new(SourceMap::empty());
172 let emitter: Box<DynEmitter> = match opts.error_format {
173 ErrorFormat::Human => {
174 let human = HumanEmitter::stderr(opts.color)
175 .source_map(Some(source_map))
176 .ui_testing(opts.unstable.ui_testing)
177 .human_kind(opts.error_format_human)
178 .terminal_width(opts.diagnostic_width);
179 Box::new(human)
180 }
181 #[cfg(feature = "json")]
182 ErrorFormat::Json | ErrorFormat::RustcJson => {
183 let writer = Box::new(std::io::BufWriter::new(std::io::stderr()));
185 let json = crate::diagnostics::JsonEmitter::new(writer, source_map)
186 .pretty(opts.pretty_json_err)
187 .rustc_like(matches!(opts.error_format, ErrorFormat::RustcJson))
188 .ui_testing(opts.unstable.ui_testing)
189 .human_kind(opts.error_format_human)
190 .terminal_width(opts.diagnostic_width);
191 Box::new(json)
192 }
193 format => unimplemented!("{format:?}"),
194 };
195 Self::new(emitter).with_flags(|flags| flags.update_from_opts(opts))
196 }
197
198 pub fn make_silent(&self, fatal_note: Option<String>, emit_fatal: bool) {
200 self.wrap_emitter(|prev| {
201 Box::new(SilentEmitter::new_boxed(emit_fatal.then_some(prev)).with_note(fatal_note))
202 });
203 }
204
205 pub fn set_emitter(&self, emitter: Box<DynEmitter>) -> Box<DynEmitter> {
207 std::mem::replace(&mut self.inner.lock().emitter, emitter)
208 }
209
210 pub fn wrap_emitter(&self, f: impl FnOnce(Box<DynEmitter>) -> Box<DynEmitter>) {
212 struct FakeEmitter;
213 impl crate::diagnostics::Emitter for FakeEmitter {
214 fn emit_diagnostic(&mut self, _diagnostic: &mut Diag) {}
215 }
216
217 let mut inner = self.inner.lock();
218 let prev = std::mem::replace(&mut inner.emitter, Box::new(FakeEmitter));
219 inner.emitter = f(prev);
220 }
221
222 pub fn source_map(&self) -> Option<Arc<SourceMap>> {
224 self.inner.lock().emitter.source_map().cloned()
225 }
226
227 pub fn source_map_mut(&mut self) -> Option<&Arc<SourceMap>> {
229 self.inner.get_mut().emitter.source_map()
230 }
231
232 pub fn with_flags(mut self, f: impl FnOnce(&mut DiagCtxtFlags)) -> Self {
234 self.set_flags_mut(f);
235 self
236 }
237
238 pub fn set_flags(&self, f: impl FnOnce(&mut DiagCtxtFlags)) {
240 f(&mut self.inner.lock().flags);
241 }
242
243 pub fn set_flags_mut(&mut self, f: impl FnOnce(&mut DiagCtxtFlags)) {
245 f(&mut self.inner.get_mut().flags);
246 }
247
248 pub fn disable_warnings(self) -> Self {
250 self.with_flags(|f| f.can_emit_warnings = false)
251 }
252
253 pub fn track_diagnostics(&self) -> bool {
255 self.inner.lock().flags.track_diagnostics
256 }
257
258 #[inline]
260 pub fn emit_diagnostic(&self, mut diagnostic: Diag) -> Result<(), ErrorGuaranteed> {
261 self.emit_diagnostic_without_consuming(&mut diagnostic)
262 }
263
264 pub(super) fn emit_diagnostic_without_consuming(
269 &self,
270 diagnostic: &mut Diag,
271 ) -> Result<(), ErrorGuaranteed> {
272 self.inner.lock().emit_diagnostic_without_consuming(diagnostic)
273 }
274
275 pub fn err_count(&self) -> usize {
277 self.inner.lock().err_count
278 }
279
280 pub fn has_errors(&self) -> Result<(), ErrorGuaranteed> {
282 if self.inner.lock().has_errors() { Err(ErrorGuaranteed::new_unchecked()) } else { Ok(()) }
283 }
284
285 pub fn warn_count(&self) -> usize {
287 self.inner.lock().warn_count
288 }
289
290 pub fn note_count(&self) -> usize {
292 self.inner.lock().note_count
293 }
294
295 #[inline]
313 pub fn emitted_diagnostics_result(
314 &self,
315 ) -> Option<Result<EmittedDiagnostics, EmittedDiagnostics>> {
316 let inner = self.inner.lock();
317 let diags = EmittedDiagnostics(inner.emitter.local_buffer()?.to_string());
318 Some(if inner.has_errors() { Err(diags) } else { Ok(diags) })
319 }
320
321 pub fn emitted_diagnostics(&self) -> Option<EmittedDiagnostics> {
326 let inner = self.inner.lock();
327 Some(EmittedDiagnostics(inner.emitter.local_buffer()?.to_string()))
328 }
329
330 pub fn emitted_errors(&self) -> Option<Result<(), EmittedDiagnostics>> {
335 let inner = self.inner.lock();
336 let buffer = inner.emitter.local_buffer()?;
337 Some(if inner.has_errors() { Err(EmittedDiagnostics(buffer.to_string())) } else { Ok(()) })
338 }
339
340 pub fn print_error_count(&self) -> Result {
342 self.inner.lock().print_error_count()
343 }
344}
345
346impl DiagCtxt {
350 #[track_caller]
352 pub fn diag<G: EmissionGuarantee>(
353 &self,
354 level: Level,
355 msg: impl Into<DiagMsg>,
356 ) -> DiagBuilder<'_, G> {
357 DiagBuilder::new(self, level, msg)
358 }
359
360 #[track_caller]
362 #[cold]
363 pub fn bug(&self, msg: impl Into<DiagMsg>) -> DiagBuilder<'_, BugAbort> {
364 self.diag(Level::Bug, msg)
365 }
366
367 #[track_caller]
369 #[cold]
370 pub fn fatal(&self, msg: impl Into<DiagMsg>) -> DiagBuilder<'_, FatalAbort> {
371 self.diag(Level::Fatal, msg)
372 }
373
374 #[track_caller]
376 pub fn err(&self, msg: impl Into<DiagMsg>) -> DiagBuilder<'_, ErrorGuaranteed> {
377 self.diag(Level::Error, msg)
378 }
379
380 #[track_caller]
384 pub fn warn(&self, msg: impl Into<DiagMsg>) -> DiagBuilder<'_, ()> {
385 self.diag(Level::Warning, msg)
386 }
387
388 #[track_caller]
390 pub fn help(&self, msg: impl Into<DiagMsg>) -> DiagBuilder<'_, ()> {
391 self.diag(Level::Help, msg)
392 }
393
394 #[track_caller]
396 pub fn note(&self, msg: impl Into<DiagMsg>) -> DiagBuilder<'_, ()> {
397 self.diag(Level::Note, msg)
398 }
399}
400
401impl DiagCtxtInner {
402 fn emit_diagnostic(&mut self, mut diagnostic: Diag) -> Result<(), ErrorGuaranteed> {
403 self.emit_diagnostic_without_consuming(&mut diagnostic)
404 }
405
406 fn emit_diagnostic_without_consuming(
407 &mut self,
408 diagnostic: &mut Diag,
409 ) -> Result<(), ErrorGuaranteed> {
410 if diagnostic.level == Level::Warning && !self.flags.can_emit_warnings {
411 return Ok(());
412 }
413
414 if diagnostic.level == Level::Allow {
415 return Ok(());
416 }
417
418 if matches!(diagnostic.level, Level::Error | Level::Fatal) && self.treat_err_as_bug() {
419 diagnostic.level = Level::Bug;
420 }
421
422 let already_emitted = self.insert_diagnostic(diagnostic);
423 if !(self.flags.deduplicate_diagnostics && already_emitted) {
424 diagnostic.children.retain(|sub| {
426 if !matches!(sub.level, Level::OnceNote | Level::OnceHelp) {
427 return true;
428 }
429 let sub_already_emitted = self.insert_diagnostic(sub);
430 !sub_already_emitted
431 });
432
433 self.emitter.emit_diagnostic(diagnostic);
440 if diagnostic.is_error() {
441 self.deduplicated_err_count += 1;
442 } else if diagnostic.level == Level::Warning {
443 self.deduplicated_warn_count += 1;
444 } else if diagnostic.is_note() {
445 self.deduplicated_note_count += 1;
446 }
447 }
448
449 if diagnostic.is_error() {
450 self.bump_err_count();
451 Err(ErrorGuaranteed::new_unchecked())
452 } else {
453 if diagnostic.level == Level::Warning {
454 self.bump_warn_count();
455 } else if diagnostic.is_note() {
456 self.bump_note_count();
457 }
458 Ok(())
460 }
461 }
462
463 fn print_error_count(&mut self) -> Result {
464 if self.treat_err_as_bug() {
467 return Ok(());
468 }
469
470 let errors = match self.deduplicated_err_count {
471 0 => None,
472 1 => Some(Cow::from("aborting due to 1 previous error")),
473 count => Some(Cow::from(format!("aborting due to {count} previous errors"))),
474 };
475
476 let mut others = Vec::with_capacity(2);
477 match self.deduplicated_warn_count {
478 1 => others.push(Cow::from("1 warning emitted")),
479 count if count > 1 => others.push(Cow::from(format!("{count} warnings emitted"))),
480 _ => {}
481 }
482 match self.deduplicated_note_count {
483 1 => others.push(Cow::from("1 note emitted")),
484 count if count > 1 => others.push(Cow::from(format!("{count} notes emitted"))),
485 _ => {}
486 }
487
488 match (errors, others.is_empty()) {
489 (None, true) => Ok(()),
490 (None, false) => {
491 if self.flags.track_diagnostics {
493 let msg = others.join(", ");
494 self.emitter.emit_diagnostic(&mut Diag::new(Level::Warning, msg));
495 }
496 Ok(())
497 }
498 (Some(e), true) => self.emit_diagnostic(Diag::new(Level::Error, e)),
499 (Some(e), false) => self
500 .emit_diagnostic(Diag::new(Level::Error, format!("{}; {}", e, others.join(", ")))),
501 }
502 }
503
504 fn insert_diagnostic<H: std::hash::Hash>(&mut self, diag: &H) -> bool {
507 let hash = solar_data_structures::map::rustc_hash::FxBuildHasher.hash_one(diag);
508 !self.emitted_diagnostics.insert(hash)
509 }
510
511 fn treat_err_as_bug(&self) -> bool {
512 self.flags.treat_err_as_bug.is_some_and(|c| self.err_count >= c.get())
513 }
514
515 fn bump_err_count(&mut self) {
516 self.err_count += 1;
517 self.panic_if_treat_err_as_bug();
518 }
519
520 fn bump_warn_count(&mut self) {
521 self.warn_count += 1;
522 }
523
524 fn bump_note_count(&mut self) {
525 self.note_count += 1;
526 }
527
528 fn has_errors(&self) -> bool {
529 self.err_count > 0
530 }
531
532 fn panic_if_treat_err_as_bug(&self) {
533 if self.treat_err_as_bug() {
534 match (self.err_count, self.flags.treat_err_as_bug.unwrap().get()) {
535 (1, 1) => panic!("aborting due to `-Z treat-err-as-bug=1`"),
536 (count, val) => {
537 panic!("aborting after {count} errors due to `-Z treat-err-as-bug={val}`")
538 }
539 }
540 }
541 }
542}