use crate::*;
use std::{
cell::RefCell,
convert::Infallible,
fmt::{self, Debug, Display},
ops::Deref,
sync::{Arc, LazyLock, Mutex},
};
use thiserror::Error;
#[derive(Debug, Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum Level {
Debug = 0,
Warning = 1,
#[default]
Error = 2,
Internal = 3,
}
impl Display for Level {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let msg = match self {
Level::Debug => "debug",
Level::Warning => "warning",
Level::Error => "error",
Level::Internal => "internal error",
};
f.write_str(msg)
}
}
#[derive(Debug, Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum NoteKind {
#[default]
Note = 0,
Trace = 1,
}
#[derive(Debug, Clone, Copy, Error)]
pub struct DiagnosticEmitted;
impl Display for DiagnosticEmitted {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "see previous error messages")
}
}
pub trait Emit {
fn emit(&self) -> DiagnosticEmitted;
}
impl Emit for Infallible {
fn emit(&self) -> DiagnosticEmitted {
DiagnosticEmitted
}
}
impl Emit for Diagnostic {
fn emit(&self) -> DiagnosticEmitted {
Diagnostic::emitter().emit(Level::Error, self.clone());
DiagnosticEmitted
}
}
pub trait Diagnosable: Display + Located {
fn level(&self) -> Level {
Level::Error
}
fn notes(&self) -> DiagnosticEmitted {
DiagnosticEmitted
}
}
impl<T: Diagnosable> Emit for T {
fn emit(&self) -> DiagnosticEmitted {
Diagnostic::emitter().emit(self.level(), Diagnostic::new(self.span(), self));
self.notes()
}
}
impl<T: Emit> From<T> for DiagnosticEmitted {
fn from(err: T) -> Self {
err.emit()
}
}
pub type Result<T, E = DiagnosticEmitted> = std::result::Result<T, E>;
pub trait Emitter {
fn emit(&self, level: Level, diag: Diagnostic);
fn note(&self, kind: NoteKind, note: Diagnostic);
}
impl<E: Deref<Target: Emitter>> Emitter for Mutex<E> {
fn emit(&self, level: Level, diag: Diagnostic) {
self.lock().unwrap().emit(level, diag)
}
fn note(&self, kind: NoteKind, note: Diagnostic) {
self.lock().unwrap().note(kind, note)
}
}
struct DefaultGlobalEmitter;
struct GlobalEmitter;
impl Emitter for DefaultGlobalEmitter {
fn emit(&self, level: Level, diag: Diagnostic) {
DEFAULT_GLOBAL_EMITTER.emit(level, diag)
}
fn note(&self, kind: NoteKind, note: Diagnostic) {
DEFAULT_GLOBAL_EMITTER.note(kind, note)
}
}
impl Emitter for GlobalEmitter {
fn emit(&self, level: Level, diag: Diagnostic) {
GLOBAL_EMITTER.with_borrow(|e| e.emit(level, diag))
}
fn note(&self, kind: NoteKind, note: Diagnostic) {
GLOBAL_EMITTER.with_borrow(|e| e.note(kind, note))
}
}
static DEFAULT_GLOBAL_EMITTER: LazyLock<Mutex<Arc<dyn Send + Sync + Emitter>>> =
LazyLock::new(|| Mutex::new(Arc::new(StdErrEmitter::new())));
thread_local! {
static GLOBAL_EMITTER: RefCell<Arc<dyn Emitter>> = RefCell::new(Arc::new(DefaultGlobalEmitter));
}
#[derive(Debug, Clone)]
pub struct Diagnostic {
pub span: Option<Span>,
pub msg: String,
}
impl Diagnostic {
pub fn new(span: Option<Span>, msg: impl Display) -> Diagnostic {
Diagnostic {
span,
msg: format!("{msg}"),
}
}
pub fn default_global_emitter() -> &'static dyn Emitter {
&DefaultGlobalEmitter
}
pub fn set_default_global_emitter(emitter: impl 'static + Send + Sync + Emitter) {
*DEFAULT_GLOBAL_EMITTER.lock().unwrap() = Arc::new(emitter);
}
pub fn emitter() -> &'static dyn Emitter {
&GlobalEmitter
}
pub fn with<E, F, R>(emitter: E, f: F) -> R
where
E: 'static + Emitter,
F: std::panic::UnwindSafe + FnOnce() -> R,
{
let old = GLOBAL_EMITTER.with_borrow(|e| e.clone());
GLOBAL_EMITTER.set(Arc::new(emitter));
let result = std::panic::catch_unwind(f);
GLOBAL_EMITTER.set(old);
match result {
Ok(v) => v,
Err(e) => std::panic::resume_unwind(e),
}
}
}
pub struct NullEmitter;
impl Emitter for NullEmitter {
fn emit(&self, _level: Level, _diag: Diagnostic) {}
fn note(&self, _kind: NoteKind, _note: Diagnostic) {}
}
pub struct BatchEmitter<'e> {
emitter: &'e dyn Emitter,
batched: RefCell<Vec<Batched>>,
}
enum Batched {
Diagnostic(Level, Diagnostic),
Note(NoteKind, Diagnostic),
}
impl<'e> BatchEmitter<'e> {
pub fn new(emitter: &'e dyn Emitter) -> BatchEmitter<'e> {
BatchEmitter {
emitter,
batched: RefCell::default(),
}
}
pub fn commit(self) {
for batched in self.batched.into_inner() {
match batched {
Batched::Diagnostic(level, diag) => self.emitter.emit(level, diag),
Batched::Note(kind, diag) => self.emitter.note(kind, diag),
}
}
}
}
impl Emitter for BatchEmitter<'_> {
fn emit(&self, level: Level, diag: Diagnostic) {
self.batched
.borrow_mut()
.push(Batched::Diagnostic(level, diag));
}
fn note(&self, kind: NoteKind, note: Diagnostic) {
self.batched.borrow_mut().push(Batched::Note(kind, note));
}
}
pub struct StdErrEmitter {
trace_emitted: Mutex<bool>,
note_emitted: Mutex<bool>,
}
impl StdErrEmitter {
pub fn new() -> StdErrEmitter {
StdErrEmitter {
trace_emitted: Mutex::new(false),
note_emitted: Mutex::new(false),
}
}
fn print(&self, preamble: impl Display, span: Option<Span>, msg: impl Display) {
if let Some(span) = span {
eprint!("{span}:")
}
eprintln!("{preamble}: {msg}");
}
}
impl Default for StdErrEmitter {
fn default() -> Self {
StdErrEmitter::new()
}
}
impl Emitter for StdErrEmitter {
fn emit(&self, level: Level, diag: Diagnostic) {
let mut note_emitted = self.note_emitted.lock().unwrap();
let mut trace_emitted = self.trace_emitted.lock().unwrap();
if *note_emitted {
eprintln!()
}
self.print(level, diag.span, diag.msg);
*trace_emitted = false;
*note_emitted = false;
}
fn note(&self, kind: NoteKind, note: Diagnostic) {
if kind == NoteKind::Trace {
let mut trace_emitted = self.trace_emitted.lock().unwrap();
if *trace_emitted {
return;
} else {
*trace_emitted = true;
}
}
*self.note_emitted.lock().unwrap() = true;
self.print("note", note.span, note.msg);
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! diagnose_impl {
($ty:ident::$level:ident, $emitter:expr, $span:expr, $arg:literal) => {
$crate::diagnose_impl!(
impl $ty, $level, $emitter, $span.clone().into(), format!($arg)
)
};
($ty:ident::$level:ident, $emitter:expr, $span:expr, $arg:expr) => {
$crate::diagnose_impl!(
impl $ty, $level, $emitter, $span.clone().into(), $arg
)
};
($ty:ident::$level:ident, $emitter:expr, $span:expr, $($args:tt)+) => {
$crate::diagnose_impl!(
impl $ty, $level, $emitter, $span.clone().into(), format!($($args)*)
)
};
(impl NoteKind, $level:ident, $emitter:expr, $span:expr, $arg:expr) => {
$crate::Emitter::note(
$emitter,
$crate::NoteKind::$level,
$crate::Diagnostic::new($span, $arg),
)
};
(impl Level, $level:ident, $emitter:expr, $span:expr, $arg:expr) => {
$crate::Emitter::emit(
$emitter,
$crate::Level::$level,
$crate::Diagnostic::new($span, $arg),
)
};
}
#[macro_export]
macro_rules! diagnose {
($ty:ident::$level:ident, $emitter:expr, $span:expr, $($args:tt)+) => {
$crate::diagnose_impl!($ty::$level, $emitter, $span, $($args)*)
}
}
#[macro_export]
macro_rules! note {
($($args:tt)+) => {
$crate::diagnose!(NoteKind::Note, $crate::Diagnostic::emitter(), $($args)*)
};
}
#[macro_export]
macro_rules! trace {
($($args:tt)+) => {
$crate::diagnose!(NoteKind::Trace, $crate::Diagnostic::emitter(), $($args)*)
};
}
#[macro_export]
macro_rules! internal {
($($args:tt)+) => {
$crate::diagnose!(Level::Internal, $crate::Diagnostic::emitter(), $($args)*)
};
}
#[macro_export]
macro_rules! error {
($($args:tt)+) => {
$crate::diagnose!(Level::Error, $crate::Diagnostic::emitter(), $($args)*)
};
}
#[macro_export]
macro_rules! warning {
($($args:tt)+) => {
$crate::diagnose!(Level::Warning, $crate::Diagnostic::emitter(), $($args)*)
};
}
#[macro_export]
macro_rules! debug {
($($args:tt)+) => {
$crate::diagnose!(Level::Debug, $crate::Diagnostic::emitter(), $($args)*)
};
}