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