solar_interface/diagnostics/
context.rs1use super::{
2 emitter::HumanEmitter, BugAbort, Diagnostic, DiagnosticBuilder, DiagnosticMessage, DynEmitter,
3 EmissionGuarantee, EmittedDiagnostics, ErrorGuaranteed, FatalAbort, HumanBufferEmitter, Level,
4 SilentEmitter,
5};
6use crate::{Result, SourceMap};
7use anstream::ColorChoice;
8use solar_data_structures::{map::FxHashSet, sync::Lock};
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
36pub struct DiagCtxt {
40 inner: Lock<DiagCtxtInner>,
41}
42
43struct DiagCtxtInner {
44 emitter: Box<DynEmitter>,
45
46 flags: DiagCtxtFlags,
47
48 err_count: usize,
53 deduplicated_err_count: usize,
54 warn_count: usize,
55 deduplicated_warn_count: usize,
57
58 emitted_diagnostics: FxHashSet<u64>,
61}
62
63impl DiagCtxt {
64 pub fn new(emitter: Box<DynEmitter>) -> Self {
66 Self {
67 inner: Lock::new(DiagCtxtInner {
68 emitter,
69 flags: DiagCtxtFlags::default(),
70 err_count: 0,
71 deduplicated_err_count: 0,
72 warn_count: 0,
73 deduplicated_warn_count: 0,
74 emitted_diagnostics: FxHashSet::default(),
75 }),
76 }
77 }
78
79 pub fn new_early() -> Self {
82 Self::with_stderr_emitter(None).set_flags(|flags| flags.track_diagnostics = false)
83 }
84
85 pub fn with_test_emitter(source_map: Option<Arc<SourceMap>>) -> Self {
87 Self::new(Box::new(HumanEmitter::test().source_map(source_map)))
88 }
89
90 pub fn with_stderr_emitter(source_map: Option<Arc<SourceMap>>) -> Self {
92 Self::with_stderr_emitter_and_color(source_map, ColorChoice::Auto)
93 }
94
95 pub fn with_stderr_emitter_and_color(
97 source_map: Option<Arc<SourceMap>>,
98 color_choice: ColorChoice,
99 ) -> Self {
100 Self::new(Box::new(HumanEmitter::stderr(color_choice).source_map(source_map)))
101 }
102
103 pub fn with_silent_emitter(fatal_note: Option<String>) -> Self {
105 let fatal_dcx = Self::with_stderr_emitter(None).disable_warnings();
106 Self::new(Box::new(SilentEmitter::new(fatal_dcx).with_note(fatal_note))).disable_warnings()
107 }
108
109 pub fn with_buffer_emitter(
111 source_map: Option<Arc<SourceMap>>,
112 color_choice: ColorChoice,
113 ) -> Self {
114 Self::new(Box::new(HumanBufferEmitter::new(color_choice).source_map(source_map)))
115 }
116
117 pub fn source_map(&self) -> Option<Arc<SourceMap>> {
119 self.inner.lock().emitter.source_map().cloned()
120 }
121
122 pub fn source_map_mut(&mut self) -> Option<&Arc<SourceMap>> {
124 self.inner.get_mut().emitter.source_map()
125 }
126
127 pub fn set_flags(mut self, f: impl FnOnce(&mut DiagCtxtFlags)) -> Self {
129 f(&mut self.inner.get_mut().flags);
130 self
131 }
132
133 pub fn disable_warnings(self) -> Self {
135 self.set_flags(|f| f.can_emit_warnings = false)
136 }
137
138 pub fn track_diagnostics(&self) -> bool {
140 self.inner.lock().flags.track_diagnostics
141 }
142
143 #[inline]
145 pub fn emit_diagnostic(&self, mut diagnostic: Diagnostic) -> Result<(), ErrorGuaranteed> {
146 self.emit_diagnostic_without_consuming(&mut diagnostic)
147 }
148
149 pub(super) fn emit_diagnostic_without_consuming(
154 &self,
155 diagnostic: &mut Diagnostic,
156 ) -> Result<(), ErrorGuaranteed> {
157 self.inner.lock().emit_diagnostic_without_consuming(diagnostic)
158 }
159
160 pub fn err_count(&self) -> usize {
162 self.inner.lock().err_count
163 }
164
165 pub fn has_errors(&self) -> Result<(), ErrorGuaranteed> {
167 if self.inner.lock().has_errors() {
168 Err(ErrorGuaranteed::new_unchecked())
169 } else {
170 Ok(())
171 }
172 }
173
174 pub fn emitted_diagnostics(&self) -> Option<EmittedDiagnostics> {
179 let inner = self.inner.lock();
180 Some(EmittedDiagnostics(inner.emitter.local_buffer()?.to_string()))
181 }
182
183 pub fn emitted_errors(&self) -> Option<Result<(), EmittedDiagnostics>> {
188 let inner = self.inner.lock();
189 let buffer = inner.emitter.local_buffer()?;
190 Some(if inner.has_errors() { Err(EmittedDiagnostics(buffer.to_string())) } else { Ok(()) })
191 }
192
193 pub fn print_error_count(&self) -> Result {
195 self.inner.lock().print_error_count()
196 }
197}
198
199impl DiagCtxt {
203 #[track_caller]
205 pub fn diag<G: EmissionGuarantee>(
206 &self,
207 level: Level,
208 msg: impl Into<DiagnosticMessage>,
209 ) -> DiagnosticBuilder<'_, G> {
210 DiagnosticBuilder::new(self, level, msg)
211 }
212
213 #[track_caller]
215 pub fn bug(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, BugAbort> {
216 self.diag(Level::Bug, msg)
217 }
218
219 #[track_caller]
221 pub fn fatal(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, FatalAbort> {
222 self.diag(Level::Fatal, msg)
223 }
224
225 #[track_caller]
227 pub fn err(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ErrorGuaranteed> {
228 self.diag(Level::Error, msg)
229 }
230
231 #[track_caller]
235 pub fn warn(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
236 self.diag(Level::Warning, msg)
237 }
238
239 #[track_caller]
241 pub fn help(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
242 self.diag(Level::Help, msg)
243 }
244
245 #[track_caller]
247 pub fn note(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
248 self.diag(Level::Note, msg)
249 }
250}
251
252impl DiagCtxtInner {
253 fn emit_diagnostic(&mut self, mut diagnostic: Diagnostic) -> Result<(), ErrorGuaranteed> {
254 self.emit_diagnostic_without_consuming(&mut diagnostic)
255 }
256
257 fn emit_diagnostic_without_consuming(
258 &mut self,
259 diagnostic: &mut Diagnostic,
260 ) -> Result<(), ErrorGuaranteed> {
261 if diagnostic.level == Level::Warning && !self.flags.can_emit_warnings {
262 return Ok(());
263 }
264
265 if diagnostic.level == Level::Allow {
266 return Ok(());
267 }
268
269 if matches!(diagnostic.level, Level::Error | Level::Fatal) && self.treat_err_as_bug() {
270 diagnostic.level = Level::Bug;
271 }
272
273 let already_emitted = self.insert_diagnostic(diagnostic);
274 if !(self.flags.deduplicate_diagnostics && already_emitted) {
275 diagnostic.children.retain(|sub| {
277 if !matches!(sub.level, Level::OnceNote | Level::OnceHelp) {
278 return true;
279 }
280 let sub_already_emitted = self.insert_diagnostic(sub);
281 !sub_already_emitted
282 });
283
284 self.emitter.emit_diagnostic(diagnostic);
291 if diagnostic.is_error() {
292 self.deduplicated_err_count += 1;
293 } else if diagnostic.level == Level::Warning {
294 self.deduplicated_warn_count += 1;
295 }
296 }
297
298 if diagnostic.is_error() {
299 self.bump_err_count();
300 Err(ErrorGuaranteed::new_unchecked())
301 } else {
302 self.bump_warn_count();
303 Ok(())
304 }
305 }
306
307 fn print_error_count(&mut self) -> Result {
308 if self.treat_err_as_bug() {
311 return Ok(());
312 }
313
314 let warnings = |count| match count {
315 0 => unreachable!(),
316 1 => Cow::from("1 warning emitted"),
317 count => Cow::from(format!("{count} warnings emitted")),
318 };
319 let errors = |count| match count {
320 0 => unreachable!(),
321 1 => Cow::from("aborting due to 1 previous error"),
322 count => Cow::from(format!("aborting due to {count} previous errors")),
323 };
324
325 match (self.deduplicated_err_count, self.deduplicated_warn_count) {
326 (0, 0) => Ok(()),
327 (0, w) => {
328 self.emitter.emit_diagnostic(&Diagnostic::new(Level::Warning, warnings(w)));
329 Ok(())
330 }
331 (e, 0) => self.emit_diagnostic(Diagnostic::new(Level::Error, errors(e))),
332 (e, w) => self.emit_diagnostic(Diagnostic::new(
333 Level::Error,
334 format!("{}; {}", errors(e), warnings(w)),
335 )),
336 }
337 }
338
339 fn insert_diagnostic<H: std::hash::Hash>(&mut self, diag: &H) -> bool {
342 let hash = solar_data_structures::map::rustc_hash::FxBuildHasher.hash_one(diag);
343 !self.emitted_diagnostics.insert(hash)
344 }
345
346 fn treat_err_as_bug(&self) -> bool {
347 self.flags.treat_err_as_bug.is_some_and(|c| self.err_count >= c.get())
348 }
349
350 fn bump_err_count(&mut self) {
351 self.err_count += 1;
352 self.panic_if_treat_err_as_bug();
353 }
354
355 fn bump_warn_count(&mut self) {
356 self.warn_count += 1;
357 }
358
359 fn has_errors(&self) -> bool {
360 self.err_count > 0
361 }
362
363 fn panic_if_treat_err_as_bug(&self) {
364 if self.treat_err_as_bug() {
365 match (self.err_count, self.flags.treat_err_as_bug.unwrap().get()) {
366 (1, 1) => panic!("aborting due to `-Z treat-err-as-bug=1`"),
367 (count, val) => {
368 panic!("aborting after {count} errors due to `-Z treat-err-as-bug={val}`")
369 }
370 }
371 }
372 }
373}