1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
//! Compatibility for `proc_macro` diagnostics, which are missing from [`proc_macro2`].
extern crate proc_macro;
use std::borrow::Cow;
use std::hash::{Hash, Hasher};
use proc_macro2::{Ident, Literal, Span, TokenStream};
use quote::quote_spanned;
use serde::{Deserialize, Serialize};
use crate::pretty_span::PrettySpan;
/// Diagnostic reporting level.
#[non_exhaustive]
#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Level {
/// An error.
///
/// The most severe and important diagnostic. Errors will prevent compilation.
Error,
/// A warning.
///
/// The second most severe diagnostic. Will not stop compilation.
Warning,
/// A note.
///
/// The third most severe, or second least severe diagnostic.
Note,
/// A help message.
///
/// The least severe and important diagnostic.
Help,
}
impl Level {
/// If this level is [`Level::Error`].
pub fn is_error(&self) -> bool {
self <= &Self::Error
}
}
/// Diagnostic. A warning or error (or lower [`Level`]) with a message and span. Shown by IDEs
/// usually as a squiggly red or yellow underline.
///
/// Diagnostics must be emitted via [`Diagnostic::try_emit`], [`Diagnostic::to_tokens`], or
/// [`Diagnostic::try_emit_all`] for diagnostics to show up.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Diagnostic<S = Span> {
/// Span (source code location).
pub span: S,
/// Severity level.
pub level: Level,
/// Human-readable message.
pub message: String,
}
impl<S> Diagnostic<S> {
/// If this diagnostic's level is [`Level::Error`].
pub fn is_error(&self) -> bool {
self.level.is_error()
}
}
impl Diagnostic {
/// Create a new diagnostic from the given span, level, and message.
pub fn spanned(span: Span, level: Level, message: impl Into<String>) -> Self {
let message = message.into();
Self {
span,
level,
message,
}
}
/// Emit if possible, otherwise return `Err` containing a [`TokenStream`] of a
/// `compile_error!(...)` call.
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())
}
/// Emits all if possible, otherwise returns `Err` containing a [`TokenStream`] of
/// `compile_error!(...)` calls.
pub fn try_emit_all<'a>(
diagnostics: impl IntoIterator<Item = &'a Self>,
) -> Result<(), TokenStream> {
if let Some(tokens) = diagnostics
.into_iter()
.filter_map(|diag| diag.try_emit().err())
.reduce(|mut tokens, next| {
tokens.extend(next);
tokens
})
{
Err(tokens)
} else {
Ok(())
}
}
/// Used to emulate `proc_macro::Diagnostic::emit` by turning this diagnostic into a properly spanned [`TokenStream`]
/// that emits an error via `compile_error!(...)` with this diagnostic's message.
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 {
// Emit as a `#[deprecated]` warning message.
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 {};
}
}
}
}
}
/// Converts this into a serializable and deserializable Diagnostic. Span information is
/// converted into [`SerdeSpan`] which keeps the span info but cannot be plugged into or
/// emitted through the Rust compiler's diagnostic system.
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(())
}
}
/// A serializable and deserializable version of [`Span`]. Cannot be plugged into the Rust
/// compiler's diagnostic system.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerdeSpan {
/// The source file path.
// https://github.com/serde-rs/serde/issues/1852#issuecomment-904840811
#[serde(borrow)]
pub path: Cow<'static, str>,
/// Line number, one-indexed.
pub line: usize,
/// Column number, one-indexed.
pub column: usize,
}
impl From<Span> for SerdeSpan {
fn from(span: Span) -> Self {
#[cfg_attr(
not(nightly),
expect(unused_labels, reason = "conditional compilation")
)]
let path = 'a: {
#[cfg(nightly)]
if proc_macro::is_available() {
break 'a span
.unwrap()
.source_file()
.path()
.display()
.to_string()
.into();
}
"unknown".into()
};
Self {
path,
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.path, self.line, self.column)
}
}