1 | use crate::{abort_now, check_correctness, sealed::Sealed, SpanRange}; |
2 | use proc_macro2::Span; |
3 | use proc_macro2::TokenStream; |
4 | |
5 | use quote::{quote_spanned, ToTokens}; |
6 | |
7 | /// Represents a diagnostic level |
8 | /// |
9 | /// # Warnings |
10 | /// |
11 | /// Warnings are ignored on stable/beta |
12 | #[derive (Debug, PartialEq)] |
13 | pub enum Level { |
14 | Error, |
15 | Warning, |
16 | #[doc (hidden)] |
17 | NonExhaustive, |
18 | } |
19 | |
20 | /// Represents a single diagnostic message |
21 | #[derive (Debug)] |
22 | pub struct Diagnostic { |
23 | pub(crate) level: Level, |
24 | pub(crate) span_range: SpanRange, |
25 | pub(crate) msg: String, |
26 | pub(crate) suggestions: Vec<(SuggestionKind, String, Option<SpanRange>)>, |
27 | pub(crate) children: Vec<(SpanRange, String)>, |
28 | } |
29 | |
30 | /// A collection of methods that do not exist in `proc_macro::Diagnostic` |
31 | /// but still useful to have around. |
32 | /// |
33 | /// This trait is sealed and cannot be implemented outside of `proc_macro_error`. |
34 | pub trait DiagnosticExt: Sealed { |
35 | /// Create a new diagnostic message that points to the `span_range`. |
36 | /// |
37 | /// This function is the same as `Diagnostic::spanned` but produces considerably |
38 | /// better error messages for multi-token spans on stable. |
39 | fn spanned_range(span_range: SpanRange, level: Level, message: String) -> Self; |
40 | |
41 | /// Add another error message to self such that it will be emitted right after |
42 | /// the main message. |
43 | /// |
44 | /// This function is the same as `Diagnostic::span_error` but produces considerably |
45 | /// better error messages for multi-token spans on stable. |
46 | fn span_range_error(self, span_range: SpanRange, msg: String) -> Self; |
47 | |
48 | /// Attach a "help" note to your main message, the note will have it's own span on nightly. |
49 | /// |
50 | /// This function is the same as `Diagnostic::span_help` but produces considerably |
51 | /// better error messages for multi-token spans on stable. |
52 | /// |
53 | /// # Span |
54 | /// |
55 | /// The span is ignored on stable, the note effectively inherits its parent's (main message) span |
56 | fn span_range_help(self, span_range: SpanRange, msg: String) -> Self; |
57 | |
58 | /// Attach a note to your main message, the note will have it's own span on nightly. |
59 | /// |
60 | /// This function is the same as `Diagnostic::span_note` but produces considerably |
61 | /// better error messages for multi-token spans on stable. |
62 | /// |
63 | /// # Span |
64 | /// |
65 | /// The span is ignored on stable, the note effectively inherits its parent's (main message) span |
66 | fn span_range_note(self, span_range: SpanRange, msg: String) -> Self; |
67 | } |
68 | |
69 | impl DiagnosticExt for Diagnostic { |
70 | fn spanned_range(span_range: SpanRange, level: Level, message: String) -> Self { |
71 | Diagnostic { |
72 | level, |
73 | span_range, |
74 | msg: message, |
75 | suggestions: vec![], |
76 | children: vec![], |
77 | } |
78 | } |
79 | |
80 | fn span_range_error(mut self, span_range: SpanRange, msg: String) -> Self { |
81 | self.children.push((span_range, msg)); |
82 | self |
83 | } |
84 | |
85 | fn span_range_help(mut self, span_range: SpanRange, msg: String) -> Self { |
86 | self.suggestions |
87 | .push((SuggestionKind::Help, msg, Some(span_range))); |
88 | self |
89 | } |
90 | |
91 | fn span_range_note(mut self, span_range: SpanRange, msg: String) -> Self { |
92 | self.suggestions |
93 | .push((SuggestionKind::Note, msg, Some(span_range))); |
94 | self |
95 | } |
96 | } |
97 | |
98 | impl Diagnostic { |
99 | /// Create a new diagnostic message that points to `Span::call_site()` |
100 | pub fn new(level: Level, message: String) -> Self { |
101 | Diagnostic::spanned(Span::call_site(), level, message) |
102 | } |
103 | |
104 | /// Create a new diagnostic message that points to the `span` |
105 | pub fn spanned(span: Span, level: Level, message: String) -> Self { |
106 | Diagnostic::spanned_range( |
107 | SpanRange { |
108 | first: span, |
109 | last: span, |
110 | }, |
111 | level, |
112 | message, |
113 | ) |
114 | } |
115 | |
116 | /// Add another error message to self such that it will be emitted right after |
117 | /// the main message. |
118 | pub fn span_error(self, span: Span, msg: String) -> Self { |
119 | self.span_range_error( |
120 | SpanRange { |
121 | first: span, |
122 | last: span, |
123 | }, |
124 | msg, |
125 | ) |
126 | } |
127 | |
128 | /// Attach a "help" note to your main message, the note will have it's own span on nightly. |
129 | /// |
130 | /// # Span |
131 | /// |
132 | /// The span is ignored on stable, the note effectively inherits its parent's (main message) span |
133 | pub fn span_help(self, span: Span, msg: String) -> Self { |
134 | self.span_range_help( |
135 | SpanRange { |
136 | first: span, |
137 | last: span, |
138 | }, |
139 | msg, |
140 | ) |
141 | } |
142 | |
143 | /// Attach a "help" note to your main message. |
144 | pub fn help(mut self, msg: String) -> Self { |
145 | self.suggestions.push((SuggestionKind::Help, msg, None)); |
146 | self |
147 | } |
148 | |
149 | /// Attach a note to your main message, the note will have it's own span on nightly. |
150 | /// |
151 | /// # Span |
152 | /// |
153 | /// The span is ignored on stable, the note effectively inherits its parent's (main message) span |
154 | pub fn span_note(self, span: Span, msg: String) -> Self { |
155 | self.span_range_note( |
156 | SpanRange { |
157 | first: span, |
158 | last: span, |
159 | }, |
160 | msg, |
161 | ) |
162 | } |
163 | |
164 | /// Attach a note to your main message |
165 | pub fn note(mut self, msg: String) -> Self { |
166 | self.suggestions.push((SuggestionKind::Note, msg, None)); |
167 | self |
168 | } |
169 | |
170 | /// The message of main warning/error (no notes attached) |
171 | pub fn message(&self) -> &str { |
172 | &self.msg |
173 | } |
174 | |
175 | /// Abort the proc-macro's execution and display the diagnostic. |
176 | /// |
177 | /// # Warnings |
178 | /// |
179 | /// Warnings are not emitted on stable and beta, but this function will abort anyway. |
180 | pub fn abort(self) -> ! { |
181 | self.emit(); |
182 | abort_now() |
183 | } |
184 | |
185 | /// Display the diagnostic while not aborting macro execution. |
186 | /// |
187 | /// # Warnings |
188 | /// |
189 | /// Warnings are ignored on stable/beta |
190 | pub fn emit(self) { |
191 | check_correctness(); |
192 | crate::imp::emit_diagnostic(self); |
193 | } |
194 | } |
195 | |
196 | /// **NOT PUBLIC API! NOTHING TO SEE HERE!!!** |
197 | #[doc (hidden)] |
198 | impl Diagnostic { |
199 | pub fn span_suggestion(self, span: Span, suggestion: &str, msg: String) -> Self { |
200 | match suggestion { |
201 | "help" | "hint" => self.span_help(span, msg), |
202 | _ => self.span_note(span, msg), |
203 | } |
204 | } |
205 | |
206 | pub fn suggestion(self, suggestion: &str, msg: String) -> Self { |
207 | match suggestion { |
208 | "help" | "hint" => self.help(msg), |
209 | _ => self.note(msg), |
210 | } |
211 | } |
212 | } |
213 | |
214 | impl ToTokens for Diagnostic { |
215 | fn to_tokens(&self, ts: &mut TokenStream) { |
216 | use std::borrow::Cow; |
217 | |
218 | fn ensure_lf(buf: &mut String, s: &str) { |
219 | if s.ends_with(' \n' ) { |
220 | buf.push_str(s); |
221 | } else { |
222 | buf.push_str(s); |
223 | buf.push(' \n' ); |
224 | } |
225 | } |
226 | |
227 | fn diag_to_tokens( |
228 | span_range: SpanRange, |
229 | level: &Level, |
230 | msg: &str, |
231 | suggestions: &[(SuggestionKind, String, Option<SpanRange>)], |
232 | ) -> TokenStream { |
233 | if *level == Level::Warning { |
234 | return TokenStream::new(); |
235 | } |
236 | |
237 | let message = if suggestions.is_empty() { |
238 | Cow::Borrowed(msg) |
239 | } else { |
240 | let mut message = String::new(); |
241 | ensure_lf(&mut message, msg); |
242 | message.push(' \n' ); |
243 | |
244 | for (kind, note, _span) in suggestions { |
245 | message.push_str(" = " ); |
246 | message.push_str(kind.name()); |
247 | message.push_str(": " ); |
248 | ensure_lf(&mut message, note); |
249 | } |
250 | message.push(' \n' ); |
251 | |
252 | Cow::Owned(message) |
253 | }; |
254 | |
255 | let mut msg = proc_macro2::Literal::string(&message); |
256 | msg.set_span(span_range.last); |
257 | let group = quote_spanned!(span_range.last=> { #msg } ); |
258 | quote_spanned!(span_range.first=> compile_error!#group) |
259 | } |
260 | |
261 | ts.extend(diag_to_tokens( |
262 | self.span_range, |
263 | &self.level, |
264 | &self.msg, |
265 | &self.suggestions, |
266 | )); |
267 | ts.extend( |
268 | self.children |
269 | .iter() |
270 | .map(|(span_range, msg)| diag_to_tokens(*span_range, &Level::Error, &msg, &[])), |
271 | ); |
272 | } |
273 | } |
274 | |
275 | #[derive (Debug)] |
276 | pub(crate) enum SuggestionKind { |
277 | Help, |
278 | Note, |
279 | } |
280 | |
281 | impl SuggestionKind { |
282 | fn name(&self) -> &'static str { |
283 | match self { |
284 | SuggestionKind::Note => "note" , |
285 | SuggestionKind::Help => "help" , |
286 | } |
287 | } |
288 | } |
289 | |
290 | #[cfg (feature = "syn-error" )] |
291 | impl From<syn::Error> for Diagnostic { |
292 | fn from(err: syn::Error) -> Self { |
293 | use proc_macro2::{Delimiter, TokenTree}; |
294 | |
295 | fn gut_error(ts: &mut impl Iterator<Item = TokenTree>) -> Option<(SpanRange, String)> { |
296 | let first = match ts.next() { |
297 | // compile_error |
298 | None => return None, |
299 | Some(tt) => tt.span(), |
300 | }; |
301 | ts.next().unwrap(); // ! |
302 | |
303 | let lit = match ts.next().unwrap() { |
304 | TokenTree::Group(group) => { |
305 | // Currently `syn` builds `compile_error!` invocations |
306 | // exclusively in `ident{"..."}` (braced) form which is not |
307 | // followed by `;` (semicolon). |
308 | // |
309 | // But if it changes to `ident("...");` (parenthesized) |
310 | // or `ident["..."];` (bracketed) form, |
311 | // we will need to skip the `;` as well. |
312 | // Highly unlikely, but better safe than sorry. |
313 | |
314 | if group.delimiter() == Delimiter::Parenthesis |
315 | || group.delimiter() == Delimiter::Bracket |
316 | { |
317 | ts.next().unwrap(); // ; |
318 | } |
319 | |
320 | match group.stream().into_iter().next().unwrap() { |
321 | TokenTree::Literal(lit) => lit, |
322 | _ => unreachable!(), |
323 | } |
324 | } |
325 | _ => unreachable!(), |
326 | }; |
327 | |
328 | let last = lit.span(); |
329 | let mut msg = lit.to_string(); |
330 | |
331 | // "abc" => abc |
332 | msg.pop(); |
333 | msg.remove(0); |
334 | |
335 | Some((SpanRange { first, last }, msg)) |
336 | } |
337 | |
338 | let mut ts = err.to_compile_error().into_iter(); |
339 | |
340 | let (span_range, msg) = gut_error(&mut ts).unwrap(); |
341 | let mut res = Diagnostic::spanned_range(span_range, Level::Error, msg); |
342 | |
343 | while let Some((span_range, msg)) = gut_error(&mut ts) { |
344 | res = res.span_range_error(span_range, msg); |
345 | } |
346 | |
347 | res |
348 | } |
349 | } |
350 | |