use core::cmp::Ordering;
use std::collections::BinaryHeap;
use crate::span::Span;
pub trait NotificationAcceptor<T> {
fn report(&mut self, notification: Notification<T>);
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq)]
#[non_exhaustive]
pub enum NotificationSeverity {
Info,
Warning,
Error,
Fatal,
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct Notification<T = ()> {
message: String,
severity: NotificationSeverity,
span: Option<Span>,
fix: Option<String>,
issue: Option<String>,
extra: Option<T>,
}
impl<T> Notification<T> {
#[inline]
pub fn message(&self) -> &str {
&self.message
}
#[inline]
#[must_use]
pub const fn span(&self) -> &Option<Span> {
&self.span
}
#[inline]
#[must_use]
pub const fn severity(&self) -> &NotificationSeverity {
&self.severity
}
#[inline]
#[must_use]
pub const fn fix(&self) -> &Option<String> {
&self.fix
}
#[inline]
#[must_use]
pub const fn issue(&self) -> &Option<String> {
&self.issue
}
#[inline]
#[must_use]
pub const fn extra(&self) -> &Option<T> {
&self.extra
}
#[inline]
pub fn report<A: NotificationAcceptor<T>>(self, acceptor: &mut A) {
acceptor.report(self);
}
}
impl<T> PartialEq for Notification<T> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.span == other.span
}
}
impl<T> Eq for Notification<T> {}
impl<T> PartialOrd for Notification<T> {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<T> Ord for Notification<T> {
#[inline]
#[expect(clippy::option_if_let_else)]
fn cmp(&self, other: &Self) -> Ordering {
if let Some(first) = self.span {
other.span.map_or(Ordering::Greater, |s| first.cmp(&s))
} else if other.span.is_some() {
Ordering::Less
} else {
Ordering::Equal
}
}
}
#[derive(Debug)]
pub struct NotificationBuilder<T = ()> {
result: Notification<T>,
}
impl<T> NotificationBuilder<T> {
#[inline]
#[expect(clippy::needless_pass_by_value)]
pub fn new<S: ToString>(message: S) -> Self {
Self {
result: Notification {
message: message.to_string(),
severity: NotificationSeverity::Info,
span: None,
fix: None,
issue: None,
extra: None,
},
}
}
#[inline]
#[must_use]
pub const fn span(mut self, span: Span) -> Self {
self.result.span = Some(span);
self
}
#[inline]
#[must_use]
pub const fn severity(mut self, severity: NotificationSeverity) -> Self {
self.result.severity = severity;
self
}
#[inline]
#[must_use]
#[expect(clippy::needless_pass_by_value)]
pub fn fix<S: ToString>(mut self, fix: S) -> Self {
self.result.fix = Some(fix.to_string());
self
}
#[inline]
#[must_use]
#[expect(clippy::needless_pass_by_value)]
pub fn issue<S: ToString>(mut self, issue: S) -> Self {
self.result.issue = Some(issue.to_string());
self
}
#[inline]
#[must_use]
pub fn extra(mut self, extra: T) -> Self {
self.result.extra = Some(extra);
self
}
#[inline]
pub fn build(self) -> Notification<T> {
self.result
}
#[inline]
pub fn report<A: NotificationAcceptor<T>>(self, acceptor: &mut A) {
acceptor.report(self.result);
}
}
#[derive(Debug)]
pub struct NotificationList<T> {
heap: BinaryHeap<Notification<T>>,
is_valid: bool,
}
impl<T> NotificationList<T> {
#[inline]
#[must_use]
pub const fn new() -> Self {
Self {
heap: BinaryHeap::new(),
is_valid: true,
}
}
#[inline]
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
Self {
heap: BinaryHeap::with_capacity(capacity),
is_valid: true,
}
}
#[inline]
pub fn push(&mut self, notification: Notification<T>) {
if self.is_valid && notification.severity >= NotificationSeverity::Error {
self.is_valid = false;
}
self.heap.push(notification);
}
#[inline]
#[must_use]
pub fn into_sorted_vec(self) -> Vec<Notification<T>> {
self.heap.into_sorted_vec()
}
#[inline]
#[must_use]
pub const fn is_valid(&self) -> bool {
self.is_valid
}
}
impl<T> Default for NotificationList<T> {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl<T> NotificationList<T> {
#[inline]
pub fn handle<F: Fn(Notification<T>, Option<(usize, usize)>)>(self, code: &str, predicate: F) {
let mut notifications = self.into_sorted_vec().into_iter().peekable();
let chars = code.chars();
let mut current_index = 0usize;
let mut current_col = 1usize;
let mut current_line = 1usize;
for char in chars {
loop {
let Some(notification) = notifications.peek() else {
return;
};
let Some(span) = notification.span else {
let owned = unsafe { notifications.next().unwrap_unchecked() };
predicate(owned, None);
continue;
};
if span.index() == current_index {
let owned = unsafe { notifications.next().unwrap_unchecked() };
predicate(owned, Some((current_line, current_col)));
} else {
break;
}
}
unsafe {
current_index = current_index.unchecked_add(1);
}
if char == '\n' {
current_line = current_line.saturating_add(1);
current_col = 1;
} else {
current_col = current_col.saturating_add(1);
}
}
}
}
#[macro_export]
macro_rules! format_notification {
($($x:tt)*) => {
$crate::notification::NotificationBuilder::<_>::new(std::format!($($x)*))
}
}