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
use proc_macro2::*; use quote::{ToTokens, TokenStreamExt}; use syn::parse::Error; #[macro_export] macro_rules! err_span { ($span:expr, $($msg:tt)*) => ( $crate::Diagnostic::spanned_error(&$span, format!($($msg)*)) ) } #[macro_export] macro_rules! bail_span { ($($t:tt)*) => ( return Err(err_span!($($t)*).into()) ) } #[derive(Debug)] pub struct Diagnostic { inner: Repr, } #[derive(Debug)] enum Repr { Single { text: String, span: Option<(Span, Span)>, }, SynError(Error), Multi { diagnostics: Vec<Diagnostic>, }, } impl Diagnostic { pub fn error<T: Into<String>>(text: T) -> Diagnostic { Diagnostic { inner: Repr::Single { text: text.into(), span: None, }, } } pub fn span_error<T: Into<String>>(span: Span, text: T) -> Diagnostic { Diagnostic { inner: Repr::Single { text: text.into(), span: Some((span, span)), }, } } pub fn spanned_error<T: Into<String>>(node: &dyn ToTokens, text: T) -> Diagnostic { Diagnostic { inner: Repr::Single { text: text.into(), span: extract_spans(node), }, } } pub fn from_vec(diagnostics: Vec<Diagnostic>) -> Result<(), Diagnostic> { if diagnostics.len() == 0 { Ok(()) } else { Err(Diagnostic { inner: Repr::Multi { diagnostics }, }) } } #[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<Error> 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); } } } }