use super::{
emitter::HumanEmitter, BugAbort, Diagnostic, DiagnosticBuilder, DiagnosticMessage, DynEmitter,
EmissionGuarantee, EmittedDiagnostics, ErrorGuaranteed, FatalAbort, HumanBufferEmitter, Level,
SilentEmitter,
};
use crate::{Result, SourceMap};
use anstream::ColorChoice;
use solar_data_structures::{map::FxHashSet, sync::Lock};
use std::{borrow::Cow, hash::BuildHasher, num::NonZeroUsize, sync::Arc};
#[derive(Clone, Copy)]
pub struct DiagCtxtFlags {
pub can_emit_warnings: bool,
pub treat_err_as_bug: Option<NonZeroUsize>,
pub deduplicate_diagnostics: bool,
pub track_diagnostics: bool,
}
impl Default for DiagCtxtFlags {
fn default() -> Self {
Self {
can_emit_warnings: true,
treat_err_as_bug: None,
deduplicate_diagnostics: true,
track_diagnostics: cfg!(debug_assertions),
}
}
}
pub struct DiagCtxt {
inner: Lock<DiagCtxtInner>,
}
struct DiagCtxtInner {
emitter: Box<DynEmitter>,
flags: DiagCtxtFlags,
err_count: usize,
deduplicated_err_count: usize,
warn_count: usize,
deduplicated_warn_count: usize,
emitted_diagnostics: FxHashSet<u64>,
}
impl DiagCtxt {
pub fn new(emitter: Box<DynEmitter>) -> Self {
Self {
inner: Lock::new(DiagCtxtInner {
emitter,
flags: DiagCtxtFlags::default(),
err_count: 0,
deduplicated_err_count: 0,
warn_count: 0,
deduplicated_warn_count: 0,
emitted_diagnostics: FxHashSet::default(),
}),
}
}
pub fn new_early() -> Self {
Self::with_stderr_emitter(None).set_flags(|flags| flags.track_diagnostics = false)
}
pub fn with_test_emitter(source_map: Option<Arc<SourceMap>>) -> Self {
Self::new(Box::new(HumanEmitter::test().source_map(source_map)))
}
pub fn with_stderr_emitter(source_map: Option<Arc<SourceMap>>) -> Self {
Self::with_stderr_emitter_and_color(source_map, ColorChoice::Auto)
}
pub fn with_stderr_emitter_and_color(
source_map: Option<Arc<SourceMap>>,
color_choice: ColorChoice,
) -> Self {
Self::new(Box::new(HumanEmitter::stderr(color_choice).source_map(source_map)))
}
pub fn with_silent_emitter(fatal_note: Option<String>) -> Self {
let fatal_dcx = Self::with_stderr_emitter(None).disable_warnings();
Self::new(Box::new(SilentEmitter::new(fatal_dcx).with_note(fatal_note))).disable_warnings()
}
pub fn with_buffer_emitter(
source_map: Option<Arc<SourceMap>>,
color_choice: ColorChoice,
) -> Self {
Self::new(Box::new(HumanBufferEmitter::new(color_choice).source_map(source_map)))
}
pub fn source_map(&self) -> Option<Arc<SourceMap>> {
self.inner.lock().emitter.source_map().cloned()
}
pub fn source_map_mut(&mut self) -> Option<&Arc<SourceMap>> {
self.inner.get_mut().emitter.source_map()
}
pub fn set_flags(mut self, f: impl FnOnce(&mut DiagCtxtFlags)) -> Self {
f(&mut self.inner.get_mut().flags);
self
}
pub fn disable_warnings(self) -> Self {
self.set_flags(|f| f.can_emit_warnings = false)
}
pub fn track_diagnostics(&self) -> bool {
self.inner.lock().flags.track_diagnostics
}
#[inline]
pub fn emit_diagnostic(&self, mut diagnostic: Diagnostic) -> Result<(), ErrorGuaranteed> {
self.emit_diagnostic_without_consuming(&mut diagnostic)
}
pub(super) fn emit_diagnostic_without_consuming(
&self,
diagnostic: &mut Diagnostic,
) -> Result<(), ErrorGuaranteed> {
self.inner.lock().emit_diagnostic_without_consuming(diagnostic)
}
pub fn err_count(&self) -> usize {
self.inner.lock().err_count
}
pub fn has_errors(&self) -> Result<(), ErrorGuaranteed> {
if self.inner.lock().has_errors() {
Err(ErrorGuaranteed::new_unchecked())
} else {
Ok(())
}
}
pub fn emitted_diagnostics(&self) -> Option<EmittedDiagnostics> {
let inner = self.inner.lock();
Some(EmittedDiagnostics(inner.emitter.local_buffer()?.to_string()))
}
pub fn emitted_errors(&self) -> Option<Result<(), EmittedDiagnostics>> {
let inner = self.inner.lock();
let buffer = inner.emitter.local_buffer()?;
Some(if inner.has_errors() { Err(EmittedDiagnostics(buffer.to_string())) } else { Ok(()) })
}
pub fn print_error_count(&self) -> Result {
self.inner.lock().print_error_count()
}
}
impl DiagCtxt {
#[track_caller]
pub fn diag<G: EmissionGuarantee>(
&self,
level: Level,
msg: impl Into<DiagnosticMessage>,
) -> DiagnosticBuilder<'_, G> {
DiagnosticBuilder::new(self, level, msg)
}
#[track_caller]
pub fn bug(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, BugAbort> {
self.diag(Level::Bug, msg)
}
#[track_caller]
pub fn fatal(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, FatalAbort> {
self.diag(Level::Fatal, msg)
}
#[track_caller]
pub fn err(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ErrorGuaranteed> {
self.diag(Level::Error, msg)
}
#[track_caller]
pub fn warn(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
self.diag(Level::Warning, msg)
}
#[track_caller]
pub fn help(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
self.diag(Level::Help, msg)
}
#[track_caller]
pub fn note(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
self.diag(Level::Note, msg)
}
}
impl DiagCtxtInner {
fn emit_diagnostic(&mut self, mut diagnostic: Diagnostic) -> Result<(), ErrorGuaranteed> {
self.emit_diagnostic_without_consuming(&mut diagnostic)
}
fn emit_diagnostic_without_consuming(
&mut self,
diagnostic: &mut Diagnostic,
) -> Result<(), ErrorGuaranteed> {
if diagnostic.level == Level::Warning && !self.flags.can_emit_warnings {
return Ok(());
}
if diagnostic.level == Level::Allow {
return Ok(());
}
if matches!(diagnostic.level, Level::Error | Level::Fatal) && self.treat_err_as_bug() {
diagnostic.level = Level::Bug;
}
let already_emitted = self.insert_diagnostic(diagnostic);
if !(self.flags.deduplicate_diagnostics && already_emitted) {
diagnostic.children.retain(|sub| {
if !matches!(sub.level, Level::OnceNote | Level::OnceHelp) {
return true;
}
let sub_already_emitted = self.insert_diagnostic(sub);
!sub_already_emitted
});
self.emitter.emit_diagnostic(diagnostic);
if diagnostic.is_error() {
self.deduplicated_err_count += 1;
} else if diagnostic.level == Level::Warning {
self.deduplicated_warn_count += 1;
}
}
if diagnostic.is_error() {
self.bump_err_count();
Err(ErrorGuaranteed::new_unchecked())
} else {
self.bump_warn_count();
Ok(())
}
}
fn print_error_count(&mut self) -> Result {
if self.treat_err_as_bug() {
return Ok(());
}
let warnings = |count| match count {
0 => unreachable!(),
1 => Cow::from("1 warning emitted"),
count => Cow::from(format!("{count} warnings emitted")),
};
let errors = |count| match count {
0 => unreachable!(),
1 => Cow::from("aborting due to 1 previous error"),
count => Cow::from(format!("aborting due to {count} previous errors")),
};
match (self.deduplicated_err_count, self.deduplicated_warn_count) {
(0, 0) => Ok(()),
(0, w) => {
self.emitter.emit_diagnostic(&Diagnostic::new(Level::Warning, warnings(w)));
Ok(())
}
(e, 0) => self.emit_diagnostic(Diagnostic::new(Level::Error, errors(e))),
(e, w) => self.emit_diagnostic(Diagnostic::new(
Level::Error,
format!("{}; {}", errors(e), warnings(w)),
)),
}
}
fn insert_diagnostic<H: std::hash::Hash>(&mut self, diag: &H) -> bool {
let hash = solar_data_structures::map::rustc_hash::FxBuildHasher.hash_one(diag);
!self.emitted_diagnostics.insert(hash)
}
fn treat_err_as_bug(&self) -> bool {
self.flags.treat_err_as_bug.is_some_and(|c| self.err_count >= c.get())
}
fn bump_err_count(&mut self) {
self.err_count += 1;
self.panic_if_treat_err_as_bug();
}
fn bump_warn_count(&mut self) {
self.warn_count += 1;
}
fn has_errors(&self) -> bool {
self.err_count > 0
}
fn panic_if_treat_err_as_bug(&self) {
if self.treat_err_as_bug() {
match (self.err_count, self.flags.treat_err_as_bug.unwrap().get()) {
(1, 1) => panic!("aborting due to `-Z treat-err-as-bug=1`"),
(count, val) => {
panic!("aborting after {count} errors due to `-Z treat-err-as-bug={val}`")
}
}
}
}
}