extern crate proc_macro;
use std::hash::{Hash, Hasher};
use itertools::Itertools;
use proc_macro2::{Ident, Literal, Span, TokenStream};
use quote::quote_spanned;
use serde::{Deserialize, Serialize};
use crate::pretty_span::{PrettySpan, make_source_path_relative};
#[non_exhaustive]
#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Level {
Error,
Warning,
Note,
Help,
}
impl Level {
pub fn iter() -> std::array::IntoIter<Self, 4> {
[Self::Error, Self::Warning, Self::Note, Self::Help].into_iter()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Diagnostic<S = Span> {
pub span: S,
pub level: Level,
pub message: String,
}
impl Diagnostic {
pub fn spanned(span: Span, level: Level, message: impl Into<String>) -> Self {
let message = message.into();
Self {
span,
level,
message,
}
}
pub fn try_emit(&self) -> Result<(), TokenStream> {
#[cfg(nightly)]
if proc_macro::is_available() {
let pm_diag = match self.level {
Level::Error => self.span.unwrap().error(&*self.message),
Level::Warning => self.span.unwrap().warning(&*self.message),
Level::Note => self.span.unwrap().note(&*self.message),
Level::Help => self.span.unwrap().help(&*self.message),
};
pm_diag.emit();
return Ok(());
}
Err(self.to_tokens())
}
pub fn to_tokens(&self) -> TokenStream {
let msg_lit: Literal = Literal::string(&self.message);
let unique_ident = {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
self.level.hash(&mut hasher);
self.message.hash(&mut hasher);
let hash = hasher.finish();
Ident::new(&format!("diagnostic_{}", hash), self.span)
};
if Level::Error == self.level {
quote_spanned! {self.span=>
{
::core::compile_error!(#msg_lit);
}
}
} else {
let level_ident = Ident::new(&format!("{:?}", self.level), self.span);
quote_spanned! {self.span=>
{
#[allow(dead_code, non_snake_case)]
fn #unique_ident() {
#[deprecated = #msg_lit]
struct #level_ident {}
#[warn(deprecated)]
#level_ident {};
}
}
}
}
}
pub fn to_serde(&self) -> Diagnostic<SerdeSpan> {
let Self {
span,
level,
message,
} = self;
Diagnostic {
span: (*span).into(),
level: *level,
message: message.clone(),
}
}
}
impl From<syn::Error> for Diagnostic {
fn from(value: syn::Error) -> Self {
Self::spanned(value.span(), Level::Error, value.to_string())
}
}
impl std::fmt::Display for Diagnostic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "{:?}: {}", self.level, self.message)?;
write!(f, " --> {}", PrettySpan(self.span))?;
Ok(())
}
}
impl std::fmt::Display for Diagnostic<SerdeSpan> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "{:?}: {}", self.level, self.message)?;
write!(f, " --> {}", self.span)?;
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerdeSpan {
pub file: Option<String>,
pub line: usize,
pub column: usize,
}
impl From<Span> for SerdeSpan {
fn from(span: Span) -> Self {
#[cfg_attr(
not(nightly),
expect(unused_labels, reason = "conditional compilation")
)]
let file = 'a: {
#[cfg(nightly)]
if proc_macro::is_available() {
break 'a Some(span.unwrap().file());
}
None
};
Self {
file,
line: span.start().line,
column: span.start().column,
}
}
}
impl std::fmt::Display for SerdeSpan {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}:{}:{}",
self.file
.as_ref()
.map(make_source_path_relative)
.map(|path| path.display().to_string())
.as_deref()
.unwrap_or("unknown"),
self.line,
self.column
)
}
}
pub struct Diagnostics<S = Span> {
diagnostics: Vec<Diagnostic<S>>,
}
impl<S> Default for Diagnostics<S> {
fn default() -> Self {
Self::new()
}
}
impl<S> Diagnostics<S> {
pub fn new() -> Self {
Self {
diagnostics: Vec::new(),
}
}
pub fn retain_level(&mut self, level: Level) {
self.diagnostics.retain(|d| d.level <= level);
}
pub fn has_error(&self) -> bool {
self.diagnostics.iter().any(|d| Level::Error == d.level)
}
pub fn push(&mut self, diagnostic: Diagnostic<S>) {
self.diagnostics.push(diagnostic);
}
pub fn iter(&self) -> std::slice::Iter<'_, Diagnostic<S>> {
self.diagnostics.iter()
}
pub fn len(&self) -> usize {
self.diagnostics.len()
}
pub fn is_empty(&self) -> bool {
self.diagnostics.is_empty()
}
}
impl Diagnostics {
pub fn try_emit_all(&self) -> Result<(), TokenStream> {
if let Some(tokens) = self
.diagnostics
.iter()
.filter_map(|diag| diag.try_emit().err())
.reduce(|mut tokens, next| {
tokens.extend(next);
tokens
})
{
Err(tokens)
} else {
Ok(())
}
}
}
impl<S> Extend<Diagnostic<S>> for Diagnostics<S> {
fn extend<T: IntoIterator<Item = Diagnostic<S>>>(&mut self, iter: T) {
self.diagnostics.extend(iter);
}
}
impl<S> std::fmt::Debug for Diagnostics<S>
where
Diagnostic<S>: std::fmt::Display,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.diagnostics.is_empty() {
write!(f, "Diagnostics (empty)")?;
} else {
write!(f, "Diagnostics (")?;
let groups = self.diagnostics.iter().into_group_map_by(|d| d.level);
for (level, count) in
Level::iter().filter_map(|level| groups.get(&level).map(|vec| (level, vec.len())))
{
write!(f, "{level:?}: {count}, ")?;
}
writeln!(f, "):")?;
for diagnostic in Level::iter()
.filter_map(|level| groups.get(&level))
.flatten()
{
writeln!(f, "{diagnostic}")?;
}
}
Ok(())
}
}
impl<S> FromIterator<Diagnostic<S>> for Diagnostics<S> {
fn from_iter<T: IntoIterator<Item = Diagnostic<S>>>(iter: T) -> Self {
Self {
diagnostics: Vec::from_iter(iter),
}
}
}
impl<S> IntoIterator for Diagnostics<S> {
type Item = Diagnostic<S>;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.diagnostics.into_iter()
}
}