use std::io::Write;
use std::sync::atomic::{AtomicBool, Ordering};
use super::kind::{Diagnostic, DiagnosticKind};
use super::strict::should_panic;
static DIAGNOSTICS_SUPPRESSED: AtomicBool = AtomicBool::new(false);
static VERBOSE_DIAGNOSTICS: AtomicBool = AtomicBool::new(false);
pub fn suppress_diagnostics(suppress: bool) {
DIAGNOSTICS_SUPPRESSED.store(suppress, Ordering::Relaxed);
}
pub fn set_verbose(verbose: bool) {
VERBOSE_DIAGNOSTICS.store(verbose, Ordering::Relaxed);
}
pub fn is_suppressed() -> bool {
DIAGNOSTICS_SUPPRESSED.load(Ordering::Relaxed)
}
pub fn emit(diag: &Diagnostic) {
if is_suppressed() {
return;
}
#[cfg(any(debug_assertions, feature = "diagnostics"))]
{
emit_to_stderr(diag);
}
if diag.kind == DiagnosticKind::Error && should_panic() {
panic!(
"[framealloc][{}] {}\nStrict mode enabled - errors are fatal.",
diag.code, diag.message
);
}
}
pub fn emit_with_context(diag: &Diagnostic, context: &str) {
if is_suppressed() {
return;
}
#[cfg(any(debug_assertions, feature = "diagnostics"))]
{
emit_to_stderr_with_context(diag, context);
}
if diag.kind == DiagnosticKind::Error && should_panic() {
panic!(
"[framealloc][{}] {}\nContext: {}\nStrict mode enabled - errors are fatal.",
diag.code, diag.message, context
);
}
}
#[cfg(any(debug_assertions, feature = "diagnostics"))]
fn emit_to_stderr(diag: &Diagnostic) {
let mut stderr = std::io::stderr();
let verbose = VERBOSE_DIAGNOSTICS.load(Ordering::Relaxed);
let _ = writeln!(
stderr,
"[framealloc][{}] {}: {}",
diag.code,
diag.kind.prefix(),
diag.message
);
if let Some(note) = diag.note {
let _ = writeln!(stderr, " note: {}", note);
}
if let Some(help) = diag.help {
let _ = writeln!(stderr, " help: {}", help);
}
if verbose && diag.kind == DiagnosticKind::Error {
let _ = writeln!(stderr, " hint: set RUST_BACKTRACE=1 for a backtrace");
}
let _ = writeln!(stderr);
}
#[cfg(any(debug_assertions, feature = "diagnostics"))]
fn emit_to_stderr_with_context(diag: &Diagnostic, context: &str) {
let mut stderr = std::io::stderr();
let _ = writeln!(
stderr,
"[framealloc][{}] {}: {}",
diag.code,
diag.kind.prefix(),
diag.message
);
let _ = writeln!(stderr, " context: {}", context);
if let Some(note) = diag.note {
let _ = writeln!(stderr, " note: {}", note);
}
if let Some(help) = diag.help {
let _ = writeln!(stderr, " help: {}", help);
}
let _ = writeln!(stderr);
}
#[cfg(feature = "log")]
pub fn emit_to_log(diag: &Diagnostic) {
match diag.kind {
DiagnosticKind::Error => {
log::error!("[{}] {}", diag.code, diag.message);
}
DiagnosticKind::Warning => {
log::warn!("[{}] {}", diag.code, diag.message);
}
DiagnosticKind::Note | DiagnosticKind::Help => {
log::info!("[{}] {}", diag.code, diag.message);
}
}
if let Some(note) = diag.note {
log::info!(" note: {}", note);
}
if let Some(help) = diag.help {
log::info!(" help: {}", help);
}
}
pub trait DiagnosticSink: Send + Sync {
fn emit(&self, diag: &Diagnostic);
}
#[derive(Default)]
pub struct CollectingSink {
diagnostics: std::sync::Mutex<Vec<Diagnostic>>,
}
impl CollectingSink {
pub fn new() -> Self {
Self::default()
}
pub fn diagnostics(&self) -> Vec<Diagnostic> {
self.diagnostics.lock().unwrap().clone()
}
pub fn clear(&self) {
self.diagnostics.lock().unwrap().clear();
}
pub fn has_errors(&self) -> bool {
self.diagnostics
.lock()
.unwrap()
.iter()
.any(|d| d.kind == DiagnosticKind::Error)
}
}
impl DiagnosticSink for CollectingSink {
fn emit(&self, diag: &Diagnostic) {
self.diagnostics.lock().unwrap().push(diag.clone());
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::diagnostics::kind::FA001;
#[test]
fn test_collecting_sink() {
let sink = CollectingSink::new();
sink.emit(&FA001);
assert_eq!(sink.diagnostics().len(), 1);
assert!(sink.has_errors());
sink.clear();
assert_eq!(sink.diagnostics().len(), 0);
}
#[test]
fn test_suppression() {
suppress_diagnostics(true);
assert!(is_suppressed());
suppress_diagnostics(false);
assert!(!is_suppressed());
}
}