use super::{
BugAbort, Diag, DiagBuilder, DiagMsg, DynEmitter, EmissionGuarantee, EmittedDiagnostics,
ErrorGuaranteed, FatalAbort, HumanBufferEmitter, Level, SilentEmitter, emitter::HumanEmitter,
};
use crate::{Result, SourceMap};
use anstream::ColorChoice;
use solar_config::{ErrorFormat, Opts};
use solar_data_structures::{map::FxHashSet, sync::Mutex};
use std::{borrow::Cow, fmt, hash::BuildHasher, num::NonZeroUsize, sync::Arc};
#[derive(Clone, Copy, Debug)]
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),
}
}
}
impl DiagCtxtFlags {
pub fn update_from_opts(&mut self, opts: &Opts) {
self.deduplicate_diagnostics &= !opts.unstable.ui_testing;
self.track_diagnostics &= !opts.unstable.ui_testing;
self.track_diagnostics |= opts.unstable.track_diagnostics;
self.can_emit_warnings |= !opts.no_warnings;
}
}
pub struct DiagCtxt {
inner: Mutex<DiagCtxtInner>,
}
impl fmt::Debug for DiagCtxt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("DiagCtxt");
if let Some(inner) = self.inner.try_lock() {
s.field("flags", &inner.flags)
.field("err_count", &inner.err_count)
.field("warn_count", &inner.warn_count)
.field("note_count", &inner.note_count);
} else {
s.field("inner", &format_args!("<locked>"));
}
s.finish_non_exhaustive()
}
}
struct DiagCtxtInner {
emitter: Box<DynEmitter>,
flags: DiagCtxtFlags,
err_count: usize,
deduplicated_err_count: usize,
warn_count: usize,
deduplicated_warn_count: usize,
note_count: usize,
deduplicated_note_count: usize,
emitted_diagnostics: FxHashSet<u64>,
}
impl DiagCtxt {
pub fn new(emitter: Box<DynEmitter>) -> Self {
Self {
inner: Mutex::new(DiagCtxtInner {
emitter,
flags: DiagCtxtFlags::default(),
err_count: 0,
deduplicated_err_count: 0,
warn_count: 0,
deduplicated_warn_count: 0,
note_count: 0,
deduplicated_note_count: 0,
emitted_diagnostics: FxHashSet::default(),
}),
}
}
pub fn new_early() -> Self {
Self::with_stderr_emitter(None).with_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_emitter = HumanEmitter::stderr(Default::default());
Self::new(Box::new(SilentEmitter::new(fatal_emitter).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 from_opts(opts: &solar_config::Opts) -> Self {
let source_map = Arc::new(SourceMap::empty());
let emitter: Box<DynEmitter> = match opts.error_format {
ErrorFormat::Human => {
let human = HumanEmitter::stderr(opts.color)
.source_map(Some(source_map))
.ui_testing(opts.unstable.ui_testing)
.human_kind(opts.error_format_human)
.terminal_width(opts.diagnostic_width);
Box::new(human)
}
#[cfg(feature = "json")]
ErrorFormat::Json | ErrorFormat::RustcJson => {
let writer = Box::new(std::io::BufWriter::new(std::io::stderr()));
let json = crate::diagnostics::JsonEmitter::new(writer, source_map)
.pretty(opts.pretty_json_err)
.rustc_like(matches!(opts.error_format, ErrorFormat::RustcJson))
.ui_testing(opts.unstable.ui_testing)
.human_kind(opts.error_format_human)
.terminal_width(opts.diagnostic_width);
Box::new(json)
}
format => unimplemented!("{format:?}"),
};
Self::new(emitter).with_flags(|flags| flags.update_from_opts(opts))
}
pub fn make_silent(&self, fatal_note: Option<String>, emit_fatal: bool) {
self.wrap_emitter(|prev| {
Box::new(SilentEmitter::new_boxed(emit_fatal.then_some(prev)).with_note(fatal_note))
});
}
pub fn set_emitter(&self, emitter: Box<DynEmitter>) -> Box<DynEmitter> {
std::mem::replace(&mut self.inner.lock().emitter, emitter)
}
pub fn wrap_emitter(&self, f: impl FnOnce(Box<DynEmitter>) -> Box<DynEmitter>) {
struct FakeEmitter;
impl crate::diagnostics::Emitter for FakeEmitter {
fn emit_diagnostic(&mut self, _diagnostic: &mut Diag) {}
}
let mut inner = self.inner.lock();
let prev = std::mem::replace(&mut inner.emitter, Box::new(FakeEmitter));
inner.emitter = f(prev);
}
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 with_flags(mut self, f: impl FnOnce(&mut DiagCtxtFlags)) -> Self {
self.set_flags_mut(f);
self
}
pub fn set_flags(&self, f: impl FnOnce(&mut DiagCtxtFlags)) {
f(&mut self.inner.lock().flags);
}
pub fn set_flags_mut(&mut self, f: impl FnOnce(&mut DiagCtxtFlags)) {
f(&mut self.inner.get_mut().flags);
}
pub fn disable_warnings(self) -> Self {
self.with_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: Diag) -> Result<(), ErrorGuaranteed> {
self.emit_diagnostic_without_consuming(&mut diagnostic)
}
pub(super) fn emit_diagnostic_without_consuming(
&self,
diagnostic: &mut Diag,
) -> 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 warn_count(&self) -> usize {
self.inner.lock().warn_count
}
pub fn note_count(&self) -> usize {
self.inner.lock().note_count
}
#[inline]
pub fn emitted_diagnostics_result(
&self,
) -> Option<Result<EmittedDiagnostics, EmittedDiagnostics>> {
let inner = self.inner.lock();
let diags = EmittedDiagnostics(inner.emitter.local_buffer()?.to_string());
Some(if inner.has_errors() { Err(diags) } else { Ok(diags) })
}
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<DiagMsg>,
) -> DiagBuilder<'_, G> {
DiagBuilder::new(self, level, msg)
}
#[track_caller]
#[cold]
pub fn bug(&self, msg: impl Into<DiagMsg>) -> DiagBuilder<'_, BugAbort> {
self.diag(Level::Bug, msg)
}
#[track_caller]
#[cold]
pub fn fatal(&self, msg: impl Into<DiagMsg>) -> DiagBuilder<'_, FatalAbort> {
self.diag(Level::Fatal, msg)
}
#[track_caller]
pub fn err(&self, msg: impl Into<DiagMsg>) -> DiagBuilder<'_, ErrorGuaranteed> {
self.diag(Level::Error, msg)
}
#[track_caller]
pub fn warn(&self, msg: impl Into<DiagMsg>) -> DiagBuilder<'_, ()> {
self.diag(Level::Warning, msg)
}
#[track_caller]
pub fn help(&self, msg: impl Into<DiagMsg>) -> DiagBuilder<'_, ()> {
self.diag(Level::Help, msg)
}
#[track_caller]
pub fn note(&self, msg: impl Into<DiagMsg>) -> DiagBuilder<'_, ()> {
self.diag(Level::Note, msg)
}
}
impl DiagCtxtInner {
fn emit_diagnostic(&mut self, mut diagnostic: Diag) -> Result<(), ErrorGuaranteed> {
self.emit_diagnostic_without_consuming(&mut diagnostic)
}
fn emit_diagnostic_without_consuming(
&mut self,
diagnostic: &mut Diag,
) -> 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;
} else if diagnostic.is_note() {
self.deduplicated_note_count += 1;
}
}
if diagnostic.is_error() {
self.bump_err_count();
Err(ErrorGuaranteed::new_unchecked())
} else {
if diagnostic.level == Level::Warning {
self.bump_warn_count();
} else if diagnostic.is_note() {
self.bump_note_count();
}
Ok(())
}
}
fn print_error_count(&mut self) -> Result {
if self.treat_err_as_bug() {
return Ok(());
}
let errors = match self.deduplicated_err_count {
0 => None,
1 => Some(Cow::from("aborting due to 1 previous error")),
count => Some(Cow::from(format!("aborting due to {count} previous errors"))),
};
let mut others = Vec::with_capacity(2);
match self.deduplicated_warn_count {
1 => others.push(Cow::from("1 warning emitted")),
count if count > 1 => others.push(Cow::from(format!("{count} warnings emitted"))),
_ => {}
}
match self.deduplicated_note_count {
1 => others.push(Cow::from("1 note emitted")),
count if count > 1 => others.push(Cow::from(format!("{count} notes emitted"))),
_ => {}
}
match (errors, others.is_empty()) {
(None, true) => Ok(()),
(None, false) => {
if self.flags.track_diagnostics {
let msg = others.join(", ");
self.emitter.emit_diagnostic(&mut Diag::new(Level::Warning, msg));
}
Ok(())
}
(Some(e), true) => self.emit_diagnostic(Diag::new(Level::Error, e)),
(Some(e), false) => self
.emit_diagnostic(Diag::new(Level::Error, format!("{}; {}", e, others.join(", ")))),
}
}
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 bump_note_count(&mut self) {
self.note_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}`")
}
}
}
}
}