venial/
error.rs

1// Most of this code is copy-pasted from syn roughly as-is.
2
3use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree};
4use quote::ToTokens;
5use std::fmt::{Debug, Display};
6
7/// Convenient error type for displaying errors in your macros to users.
8///
9/// # Error reporting in proc macros
10///
11/// The correct way to report errors back to the compiler from a procedural
12/// macro is by emitting an appropriately spanned invocation of
13/// [`compile_error!`] in the generated code. This produces a better diagnostic
14/// message than simply panicking the macro.
15///
16/// This type provides a convenient [`.to_compile_error()`] method that returns
17/// `compile_error!("Your error message")` in TokenStream form.
18///
19/// [`compile_error!`]: std::compile_error!
20///
21/// ```
22/// # extern crate proc_macro;
23/// #
24/// # use proc_macro2::TokenStream;
25/// # use venial::{parse_item, Item, Struct, Error};
26/// #
27/// # const IGNORE: &str = stringify! {
28/// #[proc_macro_derive(MyDerive)]
29/// # };
30/// pub fn my_derive(input: TokenStream) -> TokenStream {
31///     let input = parse_item(input);
32///
33///     let parse_res = match input {
34///         Err(error) => Err(error),
35///         Ok(Item::Struct(struct_decl)) => parse_my_struct(struct_decl),
36///         Ok(item) => Err(Error::new_at_span(
37///             item.span(),
38///             "Error in my_derive macro: only structs are accepted"
39///         )),
40///     };
41///
42///     parse_res
43///         .unwrap_or_else(|err| err.to_compile_error())
44///         .into()
45/// }
46///
47/// pub fn parse_my_struct(input: Struct) -> Result<TokenStream, Error> {
48///     // ...
49///     # unimplemented!()
50/// }
51/// ```
52
53#[derive(Clone)]
54pub struct Error {
55    messages: Vec<ErrorMessage>,
56}
57
58#[derive(Clone)]
59struct ErrorMessage {
60    start_span: Span,
61    end_span: Span,
62    message: String,
63}
64
65impl Error {
66    /// Create a new error message with the given text.
67    ///
68    /// The span will be located at the macro's call site.
69    pub fn new<T: Display>(message: T) -> Self {
70        Error {
71            messages: vec![ErrorMessage {
72                start_span: Span::call_site(),
73                end_span: Span::call_site(),
74                message: message.to_string(),
75            }],
76        }
77    }
78
79    /// Create a new error message with the given text and span.
80    pub fn new_at_span<T: Display>(span: Span, message: T) -> Self {
81        Error {
82            messages: vec![ErrorMessage {
83                start_span: span,
84                end_span: span,
85                message: message.to_string(),
86            }],
87        }
88    }
89
90    /// Create a new error message with the given text.
91    ///
92    /// Venial will do its best to locate the message at a span encompassing
93    /// all the given tokens.
94    pub fn new_at_tokens<T: ToTokens, U: Display>(tokens: T, message: U) -> Self {
95        let mut iter = tokens.into_token_stream().into_iter();
96        let start = iter.next().map_or_else(Span::call_site, |t| t.span());
97        let end = iter.last().map_or(start, |t| t.span());
98        Error {
99            messages: vec![ErrorMessage {
100                start_span: start,
101                end_span: end,
102                message: message.to_string(),
103            }],
104        }
105    }
106
107    /// Returns a span in the first error.
108    ///
109    /// Note that, if span merging isn't enabled (currently nightly-only), the
110    /// returned span will the smalled than the span that `self.to_compile_error()`
111    /// inhabits.
112    pub fn span(&self) -> Span {
113        let start = self.messages[0].start_span;
114        let end = self.messages[0].end_span;
115        start.join(end).unwrap_or(start)
116    }
117
118    /// Render the error as an invocation of [`compile_error!`].
119    ///
120    /// [`compile_error!`]: std::compile_error!
121    pub fn to_compile_error(&self) -> TokenStream {
122        self.messages
123            .iter()
124            .map(ErrorMessage::to_compile_error)
125            .collect()
126    }
127
128    /// Add another error message to self such that when `to_compile_error()` is
129    /// called, both errors will be emitted together.
130    pub fn combine(&mut self, another: Error) {
131        self.messages.extend(another.messages);
132    }
133}
134
135impl ErrorMessage {
136    fn to_compile_error(&self) -> TokenStream {
137        // compile_error!($message)
138        TokenStream::from_iter(vec![
139            TokenTree::Ident(Ident::new("compile_error", self.start_span)),
140            TokenTree::Punct({
141                let mut punct = Punct::new('!', Spacing::Alone);
142                punct.set_span(self.start_span);
143                punct
144            }),
145            TokenTree::Group({
146                let mut group = Group::new(Delimiter::Brace, {
147                    TokenStream::from_iter(vec![TokenTree::Literal({
148                        let mut string = Literal::string(&self.message);
149                        string.set_span(self.end_span);
150                        string
151                    })])
152                });
153                group.set_span(self.end_span);
154                group
155            }),
156        ])
157    }
158}
159
160// -- Trait impls --
161
162impl std::error::Error for Error {}
163
164impl Debug for Error {
165    fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
166        if self.messages.len() == 1 {
167            formatter
168                .debug_tuple("Error")
169                .field(&self.messages[0])
170                .finish()
171        } else {
172            formatter
173                .debug_tuple("Error")
174                .field(&self.messages)
175                .finish()
176        }
177    }
178}
179
180impl Debug for ErrorMessage {
181    fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
182        Debug::fmt(&self.message, formatter)
183    }
184}
185
186impl Display for Error {
187    fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
188        formatter.write_str(&self.messages[0].message)
189    }
190}