| use crate::{abort_now, check_correctness}; |
| 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) start: Span, |
| pub(crate) end: Span, |
| pub(crate) msg: String, |
| pub(crate) suggestions: Vec<(SuggestionKind, String, Option<Span>)>, |
| pub(crate) children: Vec<(Span, Span, String)>, |
| } |
| |
| 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::double_spanned(span, 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.double_span_error(span, 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(mut self, span: Span, msg: String) -> Self { |
| self.suggestions |
| .push((SuggestionKind::Help, msg, Some(span))); |
| self |
| } |
| |
| /// 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(mut self, span: Span, msg: String) -> Self { |
| self.suggestions |
| .push((SuggestionKind::Note, msg, Some(span))); |
| self |
| } |
| |
| /// 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 do not get emitted on stable/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 double_spanned(start: Span, end: Span, level: Level, message: String) -> Self { |
| Diagnostic { |
| level, |
| start, |
| end, |
| msg: message, |
| suggestions: vec![], |
| children: vec![], |
| } |
| } |
| |
| pub fn double_span_error(mut self, start: Span, end: Span, msg: String) -> Self { |
| self.children.push((start, end, msg)); |
| self |
| } |
| |
| 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( |
| start: Span, |
| end: Span, |
| level: &Level, |
| msg: &str, |
| suggestions: &[(SuggestionKind, String, Option<Span>)], |
| ) -> 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 msg = syn::LitStr::new(&*message, end); |
| let group = quote_spanned!(end=> { #msg } ); |
| quote_spanned!(start=> compile_error!#group) |
| } |
| |
| ts.extend(diag_to_tokens( |
| self.start, |
| self.end, |
| &self.level, |
| &self.msg, |
| &self.suggestions, |
| )); |
| ts.extend( |
| self.children |
| .iter() |
| .map(|(start, end, msg)| diag_to_tokens(*start, *end, &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", |
| } |
| } |
| } |
| |
| 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<(Span, Span, String)> { |
| let start = 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 end = lit.span(); |
| let mut msg = lit.to_string(); |
| |
| // "abc" => abc |
| msg.pop(); |
| msg.remove(0); |
| |
| Some((start, end, msg)) |
| } |
| |
| let mut ts = err.to_compile_error().into_iter(); |
| |
| let (start, end, msg) = gut_error(&mut ts).unwrap(); |
| let mut res = Diagnostic::double_spanned(start, end, Level::Error, msg); |
| |
| while let Some((start, end, msg)) = gut_error(&mut ts) { |
| res = res.double_span_error(start, end, msg); |
| } |
| |
| res |
| } |
| } |