use proc_macro2::*; use quote::{ToTokens, TokenStreamExt}; use syn::parse::Error; /// Provide a Diagnostic with the given span and message #[macro_export] macro_rules! err_span { ($span:expr, $($msg:tt)*) => ( $crate::Diagnostic::spanned_error(&$span, format!($($msg)*)) ) } /// Immediately fail and return an Err, with the arguments passed to err_span! #[macro_export] macro_rules! bail_span { ($($t:tt)*) => ( return Err(err_span!($($t)*).into()) ) } /// A struct representing a diagnostic to emit to the end-user as an error. #[derive(Debug)] pub struct Diagnostic { inner: Repr, } #[derive(Debug)] enum Repr { Single { text: String, span: Option<(Span, Span)>, }, SynError(Error), Multi { diagnostics: Vec, }, } impl Diagnostic { /// Generate a `Diagnostic` from an informational message with no Span pub fn error>(text: T) -> Diagnostic { Diagnostic { inner: Repr::Single { text: text.into(), span: None, }, } } /// Generate a `Diagnostic` from a Span and an informational message pub fn span_error>(span: Span, text: T) -> Diagnostic { Diagnostic { inner: Repr::Single { text: text.into(), span: Some((span, span)), }, } } /// Generate a `Diagnostic` from the span of any tokenizable object and a message pub fn spanned_error>(node: &dyn ToTokens, text: T) -> Diagnostic { Diagnostic { inner: Repr::Single { text: text.into(), span: extract_spans(node), }, } } /// Attempt to generate a `Diagnostic` from a vector of other `Diagnostic` instances. /// If the `Vec` is empty, returns `Ok(())`, otherwise returns the new `Diagnostic` pub fn from_vec(diagnostics: Vec) -> Result<(), Diagnostic> { if diagnostics.is_empty() { Ok(()) } else { Err(Diagnostic { inner: Repr::Multi { diagnostics }, }) } } /// Immediately trigger a panic from this `Diagnostic` #[allow(unconditional_recursion)] pub fn panic(&self) -> ! { match &self.inner { Repr::Single { text, .. } => panic!("{}", text), Repr::SynError(error) => panic!("{}", error), Repr::Multi { diagnostics } => diagnostics[0].panic(), } } } impl From for Diagnostic { fn from(err: Error) -> Diagnostic { Diagnostic { inner: Repr::SynError(err), } } } fn extract_spans(node: &dyn ToTokens) -> Option<(Span, Span)> { let mut t = TokenStream::new(); node.to_tokens(&mut t); let mut tokens = t.into_iter(); let start = tokens.next().map(|t| t.span()); let end = tokens.last().map(|t| t.span()); start.map(|start| (start, end.unwrap_or(start))) } impl ToTokens for Diagnostic { fn to_tokens(&self, dst: &mut TokenStream) { match &self.inner { Repr::Single { text, span } => { let cs2 = (Span::call_site(), Span::call_site()); let (start, end) = span.unwrap_or(cs2); dst.append(Ident::new("compile_error", start)); dst.append(Punct::new('!', Spacing::Alone)); let mut message = TokenStream::new(); message.append(Literal::string(text)); let mut group = Group::new(Delimiter::Brace, message); group.set_span(end); dst.append(group); } Repr::Multi { diagnostics } => { for diagnostic in diagnostics { diagnostic.to_tokens(dst); } } Repr::SynError(err) => { err.to_compile_error().to_tokens(dst); } } } }