350 lines
11 KiB
Rust
350 lines
11 KiB
Rust
use crate::{abort_now, check_correctness, sealed::Sealed, SpanRange};
|
|
use proc_macro2::Span;
|
|
use proc_macro2::TokenStream;
|
|
|
|
use quote::{quote_spanned, ToTokens};
|
|
|
|
/// Represents a diagnostic level
|
|
///
|
|
/// # Warnings
|
|
///
|
|
/// Warnings are ignored on stable/beta
|
|
#[derive(Debug, PartialEq)]
|
|
pub enum Level {
|
|
Error,
|
|
Warning,
|
|
#[doc(hidden)]
|
|
NonExhaustive,
|
|
}
|
|
|
|
/// Represents a single diagnostic message
|
|
#[derive(Debug)]
|
|
pub struct Diagnostic {
|
|
pub(crate) level: Level,
|
|
pub(crate) span_range: SpanRange,
|
|
pub(crate) msg: String,
|
|
pub(crate) suggestions: Vec<(SuggestionKind, String, Option<SpanRange>)>,
|
|
pub(crate) children: Vec<(SpanRange, String)>,
|
|
}
|
|
|
|
/// A collection of methods that do not exist in `proc_macro::Diagnostic`
|
|
/// but still useful to have around.
|
|
///
|
|
/// This trait is sealed and cannot be implemented outside of `proc_macro_error`.
|
|
pub trait DiagnosticExt: Sealed {
|
|
/// Create a new diagnostic message that points to the `span_range`.
|
|
///
|
|
/// This function is the same as `Diagnostic::spanned` but produces considerably
|
|
/// better error messages for multi-token spans on stable.
|
|
fn spanned_range(span_range: SpanRange, level: Level, message: String) -> Self;
|
|
|
|
/// Add another error message to self such that it will be emitted right after
|
|
/// the main message.
|
|
///
|
|
/// This function is the same as `Diagnostic::span_error` but produces considerably
|
|
/// better error messages for multi-token spans on stable.
|
|
fn span_range_error(self, span_range: SpanRange, msg: String) -> Self;
|
|
|
|
/// Attach a "help" note to your main message, the note will have it's own span on nightly.
|
|
///
|
|
/// This function is the same as `Diagnostic::span_help` but produces considerably
|
|
/// better error messages for multi-token spans on stable.
|
|
///
|
|
/// # Span
|
|
///
|
|
/// The span is ignored on stable, the note effectively inherits its parent's (main message) span
|
|
fn span_range_help(self, span_range: SpanRange, msg: String) -> Self;
|
|
|
|
/// Attach a note to your main message, the note will have it's own span on nightly.
|
|
///
|
|
/// This function is the same as `Diagnostic::span_note` but produces considerably
|
|
/// better error messages for multi-token spans on stable.
|
|
///
|
|
/// # Span
|
|
///
|
|
/// The span is ignored on stable, the note effectively inherits its parent's (main message) span
|
|
fn span_range_note(self, span_range: SpanRange, msg: String) -> Self;
|
|
}
|
|
|
|
impl DiagnosticExt for Diagnostic {
|
|
fn spanned_range(span_range: SpanRange, level: Level, message: String) -> Self {
|
|
Diagnostic {
|
|
level,
|
|
span_range,
|
|
msg: message,
|
|
suggestions: vec![],
|
|
children: vec![],
|
|
}
|
|
}
|
|
|
|
fn span_range_error(mut self, span_range: SpanRange, msg: String) -> Self {
|
|
self.children.push((span_range, msg));
|
|
self
|
|
}
|
|
|
|
fn span_range_help(mut self, span_range: SpanRange, msg: String) -> Self {
|
|
self.suggestions
|
|
.push((SuggestionKind::Help, msg, Some(span_range)));
|
|
self
|
|
}
|
|
|
|
fn span_range_note(mut self, span_range: SpanRange, msg: String) -> Self {
|
|
self.suggestions
|
|
.push((SuggestionKind::Note, msg, Some(span_range)));
|
|
self
|
|
}
|
|
}
|
|
|
|
impl Diagnostic {
|
|
/// Create a new diagnostic message that points to `Span::call_site()`
|
|
pub fn new(level: Level, message: String) -> Self {
|
|
Diagnostic::spanned(Span::call_site(), level, message)
|
|
}
|
|
|
|
/// Create a new diagnostic message that points to the `span`
|
|
pub fn spanned(span: Span, level: Level, message: String) -> Self {
|
|
Diagnostic::spanned_range(
|
|
SpanRange {
|
|
first: span,
|
|
last: span,
|
|
},
|
|
level,
|
|
message,
|
|
)
|
|
}
|
|
|
|
/// Add another error message to self such that it will be emitted right after
|
|
/// the main message.
|
|
pub fn span_error(self, span: Span, msg: String) -> Self {
|
|
self.span_range_error(
|
|
SpanRange {
|
|
first: span,
|
|
last: span,
|
|
},
|
|
msg,
|
|
)
|
|
}
|
|
|
|
/// Attach a "help" note to your main message, the note will have it's own span on nightly.
|
|
///
|
|
/// # Span
|
|
///
|
|
/// The span is ignored on stable, the note effectively inherits its parent's (main message) span
|
|
pub fn span_help(self, span: Span, msg: String) -> Self {
|
|
self.span_range_help(
|
|
SpanRange {
|
|
first: span,
|
|
last: span,
|
|
},
|
|
msg,
|
|
)
|
|
}
|
|
|
|
/// Attach a "help" note to your main message.
|
|
pub fn help(mut self, msg: String) -> Self {
|
|
self.suggestions.push((SuggestionKind::Help, msg, None));
|
|
self
|
|
}
|
|
|
|
/// Attach a note to your main message, the note will have it's own span on nightly.
|
|
///
|
|
/// # Span
|
|
///
|
|
/// The span is ignored on stable, the note effectively inherits its parent's (main message) span
|
|
pub fn span_note(self, span: Span, msg: String) -> Self {
|
|
self.span_range_note(
|
|
SpanRange {
|
|
first: span,
|
|
last: span,
|
|
},
|
|
msg,
|
|
)
|
|
}
|
|
|
|
/// Attach a note to your main message
|
|
pub fn note(mut self, msg: String) -> Self {
|
|
self.suggestions.push((SuggestionKind::Note, msg, None));
|
|
self
|
|
}
|
|
|
|
/// The message of main warning/error (no notes attached)
|
|
pub fn message(&self) -> &str {
|
|
&self.msg
|
|
}
|
|
|
|
/// Abort the proc-macro's execution and display the diagnostic.
|
|
///
|
|
/// # Warnings
|
|
///
|
|
/// Warnings are not emitted on stable and beta, but this function will abort anyway.
|
|
pub fn abort(self) -> ! {
|
|
self.emit();
|
|
abort_now()
|
|
}
|
|
|
|
/// Display the diagnostic while not aborting macro execution.
|
|
///
|
|
/// # Warnings
|
|
///
|
|
/// Warnings are ignored on stable/beta
|
|
pub fn emit(self) {
|
|
check_correctness();
|
|
crate::imp::emit_diagnostic(self);
|
|
}
|
|
}
|
|
|
|
/// **NOT PUBLIC API! NOTHING TO SEE HERE!!!**
|
|
#[doc(hidden)]
|
|
impl Diagnostic {
|
|
pub fn span_suggestion(self, span: Span, suggestion: &str, msg: String) -> Self {
|
|
match suggestion {
|
|
"help" | "hint" => self.span_help(span, msg),
|
|
_ => self.span_note(span, msg),
|
|
}
|
|
}
|
|
|
|
pub fn suggestion(self, suggestion: &str, msg: String) -> Self {
|
|
match suggestion {
|
|
"help" | "hint" => self.help(msg),
|
|
_ => self.note(msg),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ToTokens for Diagnostic {
|
|
fn to_tokens(&self, ts: &mut TokenStream) {
|
|
use std::borrow::Cow;
|
|
|
|
fn ensure_lf(buf: &mut String, s: &str) {
|
|
if s.ends_with('\n') {
|
|
buf.push_str(s);
|
|
} else {
|
|
buf.push_str(s);
|
|
buf.push('\n');
|
|
}
|
|
}
|
|
|
|
fn diag_to_tokens(
|
|
span_range: SpanRange,
|
|
level: &Level,
|
|
msg: &str,
|
|
suggestions: &[(SuggestionKind, String, Option<SpanRange>)],
|
|
) -> TokenStream {
|
|
if *level == Level::Warning {
|
|
return TokenStream::new();
|
|
}
|
|
|
|
let message = if suggestions.is_empty() {
|
|
Cow::Borrowed(msg)
|
|
} else {
|
|
let mut message = String::new();
|
|
ensure_lf(&mut message, msg);
|
|
message.push('\n');
|
|
|
|
for (kind, note, _span) in suggestions {
|
|
message.push_str(" = ");
|
|
message.push_str(kind.name());
|
|
message.push_str(": ");
|
|
ensure_lf(&mut message, note);
|
|
}
|
|
message.push('\n');
|
|
|
|
Cow::Owned(message)
|
|
};
|
|
|
|
let mut msg = proc_macro2::Literal::string(&message);
|
|
msg.set_span(span_range.last);
|
|
let group = quote_spanned!(span_range.last=> { #msg } );
|
|
quote_spanned!(span_range.first=> compile_error!#group)
|
|
}
|
|
|
|
ts.extend(diag_to_tokens(
|
|
self.span_range,
|
|
&self.level,
|
|
&self.msg,
|
|
&self.suggestions,
|
|
));
|
|
ts.extend(
|
|
self.children
|
|
.iter()
|
|
.map(|(span_range, msg)| diag_to_tokens(*span_range, &Level::Error, &msg, &[])),
|
|
);
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) enum SuggestionKind {
|
|
Help,
|
|
Note,
|
|
}
|
|
|
|
impl SuggestionKind {
|
|
fn name(&self) -> &'static str {
|
|
match self {
|
|
SuggestionKind::Note => "note",
|
|
SuggestionKind::Help => "help",
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "syn-error")]
|
|
impl From<syn::Error> for Diagnostic {
|
|
fn from(err: syn::Error) -> Self {
|
|
use proc_macro2::{Delimiter, TokenTree};
|
|
|
|
fn gut_error(ts: &mut impl Iterator<Item = TokenTree>) -> Option<(SpanRange, String)> {
|
|
let first = match ts.next() {
|
|
// compile_error
|
|
None => return None,
|
|
Some(tt) => tt.span(),
|
|
};
|
|
ts.next().unwrap(); // !
|
|
|
|
let lit = match ts.next().unwrap() {
|
|
TokenTree::Group(group) => {
|
|
// Currently `syn` builds `compile_error!` invocations
|
|
// exclusively in `ident{"..."}` (braced) form which is not
|
|
// followed by `;` (semicolon).
|
|
//
|
|
// But if it changes to `ident("...");` (parenthesized)
|
|
// or `ident["..."];` (bracketed) form,
|
|
// we will need to skip the `;` as well.
|
|
// Highly unlikely, but better safe than sorry.
|
|
|
|
if group.delimiter() == Delimiter::Parenthesis
|
|
|| group.delimiter() == Delimiter::Bracket
|
|
{
|
|
ts.next().unwrap(); // ;
|
|
}
|
|
|
|
match group.stream().into_iter().next().unwrap() {
|
|
TokenTree::Literal(lit) => lit,
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
let last = lit.span();
|
|
let mut msg = lit.to_string();
|
|
|
|
// "abc" => abc
|
|
msg.pop();
|
|
msg.remove(0);
|
|
|
|
Some((SpanRange { first, last }, msg))
|
|
}
|
|
|
|
let mut ts = err.to_compile_error().into_iter();
|
|
|
|
let (span_range, msg) = gut_error(&mut ts).unwrap();
|
|
let mut res = Diagnostic::spanned_range(span_range, Level::Error, msg);
|
|
|
|
while let Some((span_range, msg)) = gut_error(&mut ts) {
|
|
res = res.span_range_error(span_range, msg);
|
|
}
|
|
|
|
res
|
|
}
|
|
}
|