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, hash::BuildHasher, num::NonZeroUsize, sync::Arc};
10
11#[derive(Clone, Copy)]
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
58struct DiagCtxtInner {
59 emitter: Box<DynEmitter>,
60
61 flags: DiagCtxtFlags,
62
63 err_count: usize,
68 deduplicated_err_count: usize,
69 warn_count: usize,
71 deduplicated_warn_count: usize,
72 note_count: usize,
74 deduplicated_note_count: usize,
75
76 emitted_diagnostics: FxHashSet<u64>,
79}
80
81impl DiagCtxt {
82 pub fn new(emitter: Box<DynEmitter>) -> Self {
84 Self {
85 inner: Mutex::new(DiagCtxtInner {
86 emitter,
87 flags: DiagCtxtFlags::default(),
88 err_count: 0,
89 deduplicated_err_count: 0,
90 warn_count: 0,
91 deduplicated_warn_count: 0,
92 note_count: 0,
93 deduplicated_note_count: 0,
94 emitted_diagnostics: FxHashSet::default(),
95 }),
96 }
97 }
98
99 pub fn new_early() -> Self {
102 Self::with_stderr_emitter(None).with_flags(|flags| flags.track_diagnostics = false)
103 }
104
105 pub fn with_test_emitter(source_map: Option<Arc<SourceMap>>) -> Self {
107 Self::new(Box::new(HumanEmitter::test().source_map(source_map)))
108 }
109
110 pub fn with_stderr_emitter(source_map: Option<Arc<SourceMap>>) -> Self {
112 Self::with_stderr_emitter_and_color(source_map, ColorChoice::Auto)
113 }
114
115 pub fn with_stderr_emitter_and_color(
117 source_map: Option<Arc<SourceMap>>,
118 color_choice: ColorChoice,
119 ) -> Self {
120 Self::new(Box::new(HumanEmitter::stderr(color_choice).source_map(source_map)))
121 }
122
123 pub fn with_silent_emitter(fatal_note: Option<String>) -> Self {
127 let fatal_emitter = HumanEmitter::stderr(Default::default());
128 Self::new(Box::new(SilentEmitter::new(fatal_emitter).with_note(fatal_note)))
129 .disable_warnings()
130 }
131
132 pub fn with_buffer_emitter(
134 source_map: Option<Arc<SourceMap>>,
135 color_choice: ColorChoice,
136 ) -> Self {
137 Self::new(Box::new(HumanBufferEmitter::new(color_choice).source_map(source_map)))
138 }
139
140 pub fn from_opts(opts: &solar_config::Opts) -> Self {
156 let source_map = Arc::new(SourceMap::empty());
157 let emitter: Box<DynEmitter> = match opts.error_format {
158 ErrorFormat::Human => {
159 let human = HumanEmitter::stderr(opts.color)
160 .source_map(Some(source_map))
161 .ui_testing(opts.unstable.ui_testing)
162 .human_kind(opts.error_format_human)
163 .terminal_width(opts.diagnostic_width);
164 Box::new(human)
165 }
166 #[cfg(feature = "json")]
167 ErrorFormat::Json | ErrorFormat::RustcJson => {
168 let writer = Box::new(std::io::BufWriter::new(std::io::stderr()));
170 let json = crate::diagnostics::JsonEmitter::new(writer, source_map)
171 .pretty(opts.pretty_json_err)
172 .rustc_like(matches!(opts.error_format, ErrorFormat::RustcJson))
173 .ui_testing(opts.unstable.ui_testing)
174 .human_kind(opts.error_format_human)
175 .terminal_width(opts.diagnostic_width);
176 Box::new(json)
177 }
178 format => unimplemented!("{format:?}"),
179 };
180 Self::new(emitter).with_flags(|flags| flags.update_from_opts(opts))
181 }
182
183 pub fn make_silent(&self, fatal_note: Option<String>, emit_fatal: bool) {
185 self.wrap_emitter(|prev| {
186 Box::new(SilentEmitter::new_boxed(emit_fatal.then_some(prev)).with_note(fatal_note))
187 });
188 }
189
190 pub fn set_emitter(&self, emitter: Box<DynEmitter>) -> Box<DynEmitter> {
192 std::mem::replace(&mut self.inner.lock().emitter, emitter)
193 }
194
195 pub fn wrap_emitter(&self, f: impl FnOnce(Box<DynEmitter>) -> Box<DynEmitter>) {
197 struct FakeEmitter;
198 impl crate::diagnostics::Emitter for FakeEmitter {
199 fn emit_diagnostic(&mut self, _diagnostic: &mut Diag) {}
200 }
201
202 let mut inner = self.inner.lock();
203 let prev = std::mem::replace(&mut inner.emitter, Box::new(FakeEmitter));
204 inner.emitter = f(prev);
205 }
206
207 pub fn source_map(&self) -> Option<Arc<SourceMap>> {
209 self.inner.lock().emitter.source_map().cloned()
210 }
211
212 pub fn source_map_mut(&mut self) -> Option<&Arc<SourceMap>> {
214 self.inner.get_mut().emitter.source_map()
215 }
216
217 pub fn with_flags(mut self, f: impl FnOnce(&mut DiagCtxtFlags)) -> Self {
219 self.set_flags_mut(f);
220 self
221 }
222
223 pub fn set_flags(&self, f: impl FnOnce(&mut DiagCtxtFlags)) {
225 f(&mut self.inner.lock().flags);
226 }
227
228 pub fn set_flags_mut(&mut self, f: impl FnOnce(&mut DiagCtxtFlags)) {
230 f(&mut self.inner.get_mut().flags);
231 }
232
233 pub fn disable_warnings(self) -> Self {
235 self.with_flags(|f| f.can_emit_warnings = false)
236 }
237
238 pub fn track_diagnostics(&self) -> bool {
240 self.inner.lock().flags.track_diagnostics
241 }
242
243 #[inline]
245 pub fn emit_diagnostic(&self, mut diagnostic: Diag) -> Result<(), ErrorGuaranteed> {
246 self.emit_diagnostic_without_consuming(&mut diagnostic)
247 }
248
249 pub(super) fn emit_diagnostic_without_consuming(
254 &self,
255 diagnostic: &mut Diag,
256 ) -> Result<(), ErrorGuaranteed> {
257 self.inner.lock().emit_diagnostic_without_consuming(diagnostic)
258 }
259
260 pub fn err_count(&self) -> usize {
262 self.inner.lock().err_count
263 }
264
265 pub fn has_errors(&self) -> Result<(), ErrorGuaranteed> {
267 if self.inner.lock().has_errors() { Err(ErrorGuaranteed::new_unchecked()) } else { Ok(()) }
268 }
269
270 pub fn warn_count(&self) -> usize {
272 self.inner.lock().warn_count
273 }
274
275 pub fn note_count(&self) -> usize {
277 self.inner.lock().note_count
278 }
279
280 #[inline]
298 pub fn emitted_diagnostics_result(
299 &self,
300 ) -> Option<Result<EmittedDiagnostics, EmittedDiagnostics>> {
301 let inner = self.inner.lock();
302 let diags = EmittedDiagnostics(inner.emitter.local_buffer()?.to_string());
303 Some(if inner.has_errors() { Err(diags) } else { Ok(diags) })
304 }
305
306 pub fn emitted_diagnostics(&self) -> Option<EmittedDiagnostics> {
311 let inner = self.inner.lock();
312 Some(EmittedDiagnostics(inner.emitter.local_buffer()?.to_string()))
313 }
314
315 pub fn emitted_errors(&self) -> Option<Result<(), EmittedDiagnostics>> {
320 let inner = self.inner.lock();
321 let buffer = inner.emitter.local_buffer()?;
322 Some(if inner.has_errors() { Err(EmittedDiagnostics(buffer.to_string())) } else { Ok(()) })
323 }
324
325 pub fn print_error_count(&self) -> Result {
327 self.inner.lock().print_error_count()
328 }
329}
330
331impl DiagCtxt {
335 #[track_caller]
337 pub fn diag<G: EmissionGuarantee>(
338 &self,
339 level: Level,
340 msg: impl Into<DiagMsg>,
341 ) -> DiagBuilder<'_, G> {
342 DiagBuilder::new(self, level, msg)
343 }
344
345 #[track_caller]
347 pub fn bug(&self, msg: impl Into<DiagMsg>) -> DiagBuilder<'_, BugAbort> {
348 self.diag(Level::Bug, msg)
349 }
350
351 #[track_caller]
353 pub fn fatal(&self, msg: impl Into<DiagMsg>) -> DiagBuilder<'_, FatalAbort> {
354 self.diag(Level::Fatal, msg)
355 }
356
357 #[track_caller]
359 pub fn err(&self, msg: impl Into<DiagMsg>) -> DiagBuilder<'_, ErrorGuaranteed> {
360 self.diag(Level::Error, msg)
361 }
362
363 #[track_caller]
367 pub fn warn(&self, msg: impl Into<DiagMsg>) -> DiagBuilder<'_, ()> {
368 self.diag(Level::Warning, msg)
369 }
370
371 #[track_caller]
373 pub fn help(&self, msg: impl Into<DiagMsg>) -> DiagBuilder<'_, ()> {
374 self.diag(Level::Help, msg)
375 }
376
377 #[track_caller]
379 pub fn note(&self, msg: impl Into<DiagMsg>) -> DiagBuilder<'_, ()> {
380 self.diag(Level::Note, msg)
381 }
382}
383
384impl DiagCtxtInner {
385 fn emit_diagnostic(&mut self, mut diagnostic: Diag) -> Result<(), ErrorGuaranteed> {
386 self.emit_diagnostic_without_consuming(&mut diagnostic)
387 }
388
389 fn emit_diagnostic_without_consuming(
390 &mut self,
391 diagnostic: &mut Diag,
392 ) -> Result<(), ErrorGuaranteed> {
393 if diagnostic.level == Level::Warning && !self.flags.can_emit_warnings {
394 return Ok(());
395 }
396
397 if diagnostic.level == Level::Allow {
398 return Ok(());
399 }
400
401 if matches!(diagnostic.level, Level::Error | Level::Fatal) && self.treat_err_as_bug() {
402 diagnostic.level = Level::Bug;
403 }
404
405 let already_emitted = self.insert_diagnostic(diagnostic);
406 if !(self.flags.deduplicate_diagnostics && already_emitted) {
407 diagnostic.children.retain(|sub| {
409 if !matches!(sub.level, Level::OnceNote | Level::OnceHelp) {
410 return true;
411 }
412 let sub_already_emitted = self.insert_diagnostic(sub);
413 !sub_already_emitted
414 });
415
416 self.emitter.emit_diagnostic(diagnostic);
423 if diagnostic.is_error() {
424 self.deduplicated_err_count += 1;
425 } else if diagnostic.level == Level::Warning {
426 self.deduplicated_warn_count += 1;
427 } else if diagnostic.is_note() {
428 self.deduplicated_note_count += 1;
429 }
430 }
431
432 if diagnostic.is_error() {
433 self.bump_err_count();
434 Err(ErrorGuaranteed::new_unchecked())
435 } else {
436 if diagnostic.level == Level::Warning {
437 self.bump_warn_count();
438 } else if diagnostic.is_note() {
439 self.bump_note_count();
440 }
441 Ok(())
443 }
444 }
445
446 fn print_error_count(&mut self) -> Result {
447 if self.treat_err_as_bug() {
450 return Ok(());
451 }
452
453 let errors = match self.deduplicated_err_count {
454 0 => None,
455 1 => Some(Cow::from("aborting due to 1 previous error")),
456 count => Some(Cow::from(format!("aborting due to {count} previous errors"))),
457 };
458
459 let mut others = Vec::with_capacity(2);
460 match self.deduplicated_warn_count {
461 1 => others.push(Cow::from("1 warning emitted")),
462 count if count > 1 => others.push(Cow::from(format!("{count} warnings emitted"))),
463 _ => {}
464 }
465 match self.deduplicated_note_count {
466 1 => others.push(Cow::from("1 note emitted")),
467 count if count > 1 => others.push(Cow::from(format!("{count} notes emitted"))),
468 _ => {}
469 }
470
471 match (errors, others.is_empty()) {
472 (None, true) => Ok(()),
473 (None, false) => {
474 if self.flags.track_diagnostics {
476 let msg = others.join(", ");
477 self.emitter.emit_diagnostic(&mut Diag::new(Level::Warning, msg));
478 }
479 Ok(())
480 }
481 (Some(e), true) => self.emit_diagnostic(Diag::new(Level::Error, e)),
482 (Some(e), false) => self
483 .emit_diagnostic(Diag::new(Level::Error, format!("{}; {}", e, others.join(", ")))),
484 }
485 }
486
487 fn insert_diagnostic<H: std::hash::Hash>(&mut self, diag: &H) -> bool {
490 let hash = solar_data_structures::map::rustc_hash::FxBuildHasher.hash_one(diag);
491 !self.emitted_diagnostics.insert(hash)
492 }
493
494 fn treat_err_as_bug(&self) -> bool {
495 self.flags.treat_err_as_bug.is_some_and(|c| self.err_count >= c.get())
496 }
497
498 fn bump_err_count(&mut self) {
499 self.err_count += 1;
500 self.panic_if_treat_err_as_bug();
501 }
502
503 fn bump_warn_count(&mut self) {
504 self.warn_count += 1;
505 }
506
507 fn bump_note_count(&mut self) {
508 self.note_count += 1;
509 }
510
511 fn has_errors(&self) -> bool {
512 self.err_count > 0
513 }
514
515 fn panic_if_treat_err_as_bug(&self) {
516 if self.treat_err_as_bug() {
517 match (self.err_count, self.flags.treat_err_as_bug.unwrap().get()) {
518 (1, 1) => panic!("aborting due to `-Z treat-err-as-bug=1`"),
519 (count, val) => {
520 panic!("aborting after {count} errors due to `-Z treat-err-as-bug={val}`")
521 }
522 }
523 }
524 }
525}