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_data_structures::{map::FxHashSet, sync::Lock};
8use std::{borrow::Cow, hash::BuildHasher, num::NonZeroUsize, sync::Arc};
9
10#[derive(Clone, Copy)]
12pub struct DiagCtxtFlags {
13 pub can_emit_warnings: bool,
15 pub treat_err_as_bug: Option<NonZeroUsize>,
17 pub deduplicate_diagnostics: bool,
19 pub track_diagnostics: bool,
22}
23
24impl Default for DiagCtxtFlags {
25 fn default() -> Self {
26 Self {
27 can_emit_warnings: true,
28 treat_err_as_bug: None,
29 deduplicate_diagnostics: true,
30 track_diagnostics: cfg!(debug_assertions),
31 }
32 }
33}
34
35pub struct DiagCtxt {
39 inner: Lock<DiagCtxtInner>,
40}
41
42struct DiagCtxtInner {
43 emitter: Box<DynEmitter>,
44
45 flags: DiagCtxtFlags,
46
47 err_count: usize,
52 deduplicated_err_count: usize,
53 warn_count: usize,
54 deduplicated_warn_count: usize,
56
57 emitted_diagnostics: FxHashSet<u64>,
60}
61
62impl DiagCtxt {
63 pub fn new(emitter: Box<DynEmitter>) -> Self {
65 Self {
66 inner: Lock::new(DiagCtxtInner {
67 emitter,
68 flags: DiagCtxtFlags::default(),
69 err_count: 0,
70 deduplicated_err_count: 0,
71 warn_count: 0,
72 deduplicated_warn_count: 0,
73 emitted_diagnostics: FxHashSet::default(),
74 }),
75 }
76 }
77
78 pub fn new_early() -> Self {
81 Self::with_stderr_emitter(None).set_flags(|flags| flags.track_diagnostics = false)
82 }
83
84 pub fn with_test_emitter(source_map: Option<Arc<SourceMap>>) -> Self {
86 Self::new(Box::new(HumanEmitter::test().source_map(source_map)))
87 }
88
89 pub fn with_stderr_emitter(source_map: Option<Arc<SourceMap>>) -> Self {
91 Self::with_stderr_emitter_and_color(source_map, ColorChoice::Auto)
92 }
93
94 pub fn with_stderr_emitter_and_color(
96 source_map: Option<Arc<SourceMap>>,
97 color_choice: ColorChoice,
98 ) -> Self {
99 Self::new(Box::new(HumanEmitter::stderr(color_choice).source_map(source_map)))
100 }
101
102 pub fn with_silent_emitter(fatal_note: Option<String>) -> Self {
106 let fatal_emitter = HumanEmitter::stderr(Default::default());
107 Self::new(Box::new(SilentEmitter::new(fatal_emitter).with_note(fatal_note)))
108 .disable_warnings()
109 }
110
111 pub fn with_buffer_emitter(
113 source_map: Option<Arc<SourceMap>>,
114 color_choice: ColorChoice,
115 ) -> Self {
116 Self::new(Box::new(HumanBufferEmitter::new(color_choice).source_map(source_map)))
117 }
118
119 pub fn make_silent(&self, fatal_note: Option<String>, emit_fatal: bool) {
121 self.wrap_emitter(|prev| {
122 Box::new(SilentEmitter::new_boxed(emit_fatal.then_some(prev)).with_note(fatal_note))
123 });
124 }
125
126 pub fn set_emitter(&self, emitter: Box<DynEmitter>) {
128 self.inner.lock().emitter = emitter;
129 }
130
131 pub fn wrap_emitter(&self, f: impl FnOnce(Box<DynEmitter>) -> Box<DynEmitter>) {
133 struct FakeEmitter;
134 impl crate::diagnostics::Emitter for FakeEmitter {
135 fn emit_diagnostic(&mut self, _diagnostic: &Diag) {}
136 }
137
138 let mut inner = self.inner.lock();
139 let prev = std::mem::replace(&mut inner.emitter, Box::new(FakeEmitter));
140 inner.emitter = f(prev);
141 }
142
143 pub fn source_map(&self) -> Option<Arc<SourceMap>> {
145 self.inner.lock().emitter.source_map().cloned()
146 }
147
148 pub fn source_map_mut(&mut self) -> Option<&Arc<SourceMap>> {
150 self.inner.get_mut().emitter.source_map()
151 }
152
153 pub fn set_flags(mut self, f: impl FnOnce(&mut DiagCtxtFlags)) -> Self {
155 self.set_flags_mut(f);
156 self
157 }
158
159 pub fn set_flags_mut(&mut self, f: impl FnOnce(&mut DiagCtxtFlags)) {
161 f(&mut self.inner.get_mut().flags);
162 }
163
164 pub fn disable_warnings(self) -> Self {
166 self.set_flags(|f| f.can_emit_warnings = false)
167 }
168
169 pub fn track_diagnostics(&self) -> bool {
171 self.inner.lock().flags.track_diagnostics
172 }
173
174 #[inline]
176 pub fn emit_diagnostic(&self, mut diagnostic: Diag) -> Result<(), ErrorGuaranteed> {
177 self.emit_diagnostic_without_consuming(&mut diagnostic)
178 }
179
180 pub(super) fn emit_diagnostic_without_consuming(
185 &self,
186 diagnostic: &mut Diag,
187 ) -> Result<(), ErrorGuaranteed> {
188 self.inner.lock().emit_diagnostic_without_consuming(diagnostic)
189 }
190
191 pub fn err_count(&self) -> usize {
193 self.inner.lock().err_count
194 }
195
196 pub fn has_errors(&self) -> Result<(), ErrorGuaranteed> {
198 if self.inner.lock().has_errors() { Err(ErrorGuaranteed::new_unchecked()) } else { Ok(()) }
199 }
200
201 pub fn emitted_diagnostics(&self) -> Option<EmittedDiagnostics> {
206 let inner = self.inner.lock();
207 Some(EmittedDiagnostics(inner.emitter.local_buffer()?.to_string()))
208 }
209
210 pub fn emitted_errors(&self) -> Option<Result<(), EmittedDiagnostics>> {
215 let inner = self.inner.lock();
216 let buffer = inner.emitter.local_buffer()?;
217 Some(if inner.has_errors() { Err(EmittedDiagnostics(buffer.to_string())) } else { Ok(()) })
218 }
219
220 pub fn print_error_count(&self) -> Result {
222 self.inner.lock().print_error_count()
223 }
224}
225
226impl DiagCtxt {
230 #[track_caller]
232 pub fn diag<G: EmissionGuarantee>(
233 &self,
234 level: Level,
235 msg: impl Into<DiagMsg>,
236 ) -> DiagBuilder<'_, G> {
237 DiagBuilder::new(self, level, msg)
238 }
239
240 #[track_caller]
242 pub fn bug(&self, msg: impl Into<DiagMsg>) -> DiagBuilder<'_, BugAbort> {
243 self.diag(Level::Bug, msg)
244 }
245
246 #[track_caller]
248 pub fn fatal(&self, msg: impl Into<DiagMsg>) -> DiagBuilder<'_, FatalAbort> {
249 self.diag(Level::Fatal, msg)
250 }
251
252 #[track_caller]
254 pub fn err(&self, msg: impl Into<DiagMsg>) -> DiagBuilder<'_, ErrorGuaranteed> {
255 self.diag(Level::Error, msg)
256 }
257
258 #[track_caller]
262 pub fn warn(&self, msg: impl Into<DiagMsg>) -> DiagBuilder<'_, ()> {
263 self.diag(Level::Warning, msg)
264 }
265
266 #[track_caller]
268 pub fn help(&self, msg: impl Into<DiagMsg>) -> DiagBuilder<'_, ()> {
269 self.diag(Level::Help, msg)
270 }
271
272 #[track_caller]
274 pub fn note(&self, msg: impl Into<DiagMsg>) -> DiagBuilder<'_, ()> {
275 self.diag(Level::Note, msg)
276 }
277}
278
279impl DiagCtxtInner {
280 fn emit_diagnostic(&mut self, mut diagnostic: Diag) -> Result<(), ErrorGuaranteed> {
281 self.emit_diagnostic_without_consuming(&mut diagnostic)
282 }
283
284 fn emit_diagnostic_without_consuming(
285 &mut self,
286 diagnostic: &mut Diag,
287 ) -> Result<(), ErrorGuaranteed> {
288 if diagnostic.level == Level::Warning && !self.flags.can_emit_warnings {
289 return Ok(());
290 }
291
292 if diagnostic.level == Level::Allow {
293 return Ok(());
294 }
295
296 if matches!(diagnostic.level, Level::Error | Level::Fatal) && self.treat_err_as_bug() {
297 diagnostic.level = Level::Bug;
298 }
299
300 let already_emitted = self.insert_diagnostic(diagnostic);
301 if !(self.flags.deduplicate_diagnostics && already_emitted) {
302 diagnostic.children.retain(|sub| {
304 if !matches!(sub.level, Level::OnceNote | Level::OnceHelp) {
305 return true;
306 }
307 let sub_already_emitted = self.insert_diagnostic(sub);
308 !sub_already_emitted
309 });
310
311 self.emitter.emit_diagnostic(diagnostic);
318 if diagnostic.is_error() {
319 self.deduplicated_err_count += 1;
320 } else if diagnostic.level == Level::Warning {
321 self.deduplicated_warn_count += 1;
322 }
323 }
324
325 if diagnostic.is_error() {
326 self.bump_err_count();
327 Err(ErrorGuaranteed::new_unchecked())
328 } else {
329 self.bump_warn_count();
330 Ok(())
331 }
332 }
333
334 fn print_error_count(&mut self) -> Result {
335 if self.treat_err_as_bug() {
338 return Ok(());
339 }
340
341 let warnings = |count| match count {
342 0 => unreachable!(),
343 1 => Cow::from("1 warning emitted"),
344 count => Cow::from(format!("{count} warnings emitted")),
345 };
346 let errors = |count| match count {
347 0 => unreachable!(),
348 1 => Cow::from("aborting due to 1 previous error"),
349 count => Cow::from(format!("aborting due to {count} previous errors")),
350 };
351
352 match (self.deduplicated_err_count, self.deduplicated_warn_count) {
353 (0, 0) => Ok(()),
354 (0, w) => {
355 if self.flags.track_diagnostics {
357 self.emitter.emit_diagnostic(&Diag::new(Level::Warning, warnings(w)));
358 }
359 Ok(())
360 }
361 (e, 0) => self.emit_diagnostic(Diag::new(Level::Error, errors(e))),
362 (e, w) => self.emit_diagnostic(Diag::new(
363 Level::Error,
364 format!("{}; {}", errors(e), warnings(w)),
365 )),
366 }
367 }
368
369 fn insert_diagnostic<H: std::hash::Hash>(&mut self, diag: &H) -> bool {
372 let hash = solar_data_structures::map::rustc_hash::FxBuildHasher.hash_one(diag);
373 !self.emitted_diagnostics.insert(hash)
374 }
375
376 fn treat_err_as_bug(&self) -> bool {
377 self.flags.treat_err_as_bug.is_some_and(|c| self.err_count >= c.get())
378 }
379
380 fn bump_err_count(&mut self) {
381 self.err_count += 1;
382 self.panic_if_treat_err_as_bug();
383 }
384
385 fn bump_warn_count(&mut self) {
386 self.warn_count += 1;
387 }
388
389 fn has_errors(&self) -> bool {
390 self.err_count > 0
391 }
392
393 fn panic_if_treat_err_as_bug(&self) {
394 if self.treat_err_as_bug() {
395 match (self.err_count, self.flags.treat_err_as_bug.unwrap().get()) {
396 (1, 1) => panic!("aborting due to `-Z treat-err-as-bug=1`"),
397 (count, val) => {
398 panic!("aborting after {count} errors due to `-Z treat-err-as-bug={val}`")
399 }
400 }
401 }
402 }
403}