holium_backend/
error.rs

1//! Error handling for any error that can arise during our procedural macro logic.
2
3use proc_macro2::*;
4use quote::{ToTokens, TokenStreamExt};
5use syn::parse::Error;
6
7/// Provide a Diagnostic with the given span and message
8#[macro_export]
9macro_rules! err_span {
10    ($span:expr, $($msg:tt)*) => (
11        $crate::Diagnostic::spanned_error(&$span, format!($($msg)*))
12    )
13}
14
15/// Immediately fail and return an Err, with the arguments passed to err_span!
16#[macro_export]
17macro_rules! bail_span {
18    ($($t:tt)*) => (
19        return Err(err_span!($($t)*).into())
20    )
21}
22
23/// A struct representing a diagnostic to emit to the end-user as an error.
24#[derive(Debug)]
25pub struct Diagnostic {
26    inner: Repr,
27}
28
29#[derive(Debug)]
30enum Repr {
31    Single {
32        text: String,
33        span: Option<(Span, Span)>,
34    },
35    SynError(Error),
36    Multi {
37        diagnostics: Vec<Diagnostic>,
38    },
39}
40
41impl Diagnostic {
42    /// Generate a `Diagnostic` from an informational message with no Span
43    pub fn error<T: Into<String>>(text: T) -> Diagnostic {
44        Diagnostic {
45            inner: Repr::Single {
46                text: text.into(),
47                span: None,
48            },
49        }
50    }
51
52    /// Generate a `Diagnostic` from a Span and an informational message
53    pub fn span_error<T: Into<String>>(span: Span, text: T) -> Diagnostic {
54        Diagnostic {
55            inner: Repr::Single {
56                text: text.into(),
57                span: Some((span, span)),
58            },
59        }
60    }
61
62    /// Generate a `Diagnostic` from the span of any tokenizable object and a message
63    pub fn spanned_error<T: Into<String>>(node: &dyn ToTokens, text: T) -> Diagnostic {
64        Diagnostic {
65            inner: Repr::Single {
66                text: text.into(),
67                span: extract_spans(node),
68            },
69        }
70    }
71
72    /// Attempt to generate a `Diagnostic` from a vector of other `Diagnostic` instances.
73    /// If the `Vec` is empty, returns `Ok(())`, otherwise returns the new `Diagnostic`
74    pub fn from_vec(diagnostics: Vec<Diagnostic>) -> Result<(), Diagnostic> {
75        if diagnostics.len() == 0 {
76            Ok(())
77        } else {
78            Err(Diagnostic {
79                inner: Repr::Multi { diagnostics },
80            })
81        }
82    }
83
84    /// Immediately trigger a panic from this `Diagnostic`
85    #[allow(unconditional_recursion)]
86    pub fn panic(&self) -> ! {
87        match &self.inner {
88            Repr::Single { text, .. } => panic!("{}", text),
89            Repr::SynError(error) => panic!("{}", error),
90            Repr::Multi { diagnostics } => diagnostics[0].panic(),
91        }
92    }
93}
94
95impl From<Error> for Diagnostic {
96    fn from(err: Error) -> Diagnostic {
97        Diagnostic {
98            inner: Repr::SynError(err),
99        }
100    }
101}
102
103fn extract_spans(node: &dyn ToTokens) -> Option<(Span, Span)> {
104    let mut t = TokenStream::new();
105    node.to_tokens(&mut t);
106    let mut tokens = t.into_iter();
107    let start = tokens.next().map(|t| t.span());
108    let end = tokens.last().map(|t| t.span());
109    start.map(|start| (start, end.unwrap_or(start)))
110}
111
112impl ToTokens for Diagnostic {
113    fn to_tokens(&self, dst: &mut TokenStream) {
114        match &self.inner {
115            Repr::Single { text, span } => {
116                let cs2 = (Span::call_site(), Span::call_site());
117                let (start, end) = span.unwrap_or(cs2);
118                dst.append(Ident::new("compile_error", start));
119                dst.append(Punct::new('!', Spacing::Alone));
120                let mut message = TokenStream::new();
121                message.append(Literal::string(text));
122                let mut group = Group::new(Delimiter::Brace, message);
123                group.set_span(end);
124                dst.append(group);
125            }
126            Repr::Multi { diagnostics } => {
127                for diagnostic in diagnostics {
128                    diagnostic.to_tokens(dst);
129                }
130            }
131            Repr::SynError(err) => {
132                err.to_compile_error().to_tokens(dst);
133            }
134        }
135    }
136}