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}