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.
spanned_range(span_range: SpanRange, level: Level, message: String) -> Self39     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.
span_range_error(self, span_range: SpanRange, msg: String) -> Self46     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
span_range_help(self, span_range: SpanRange, msg: String) -> Self56     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
span_range_note(self, span_range: SpanRange, msg: String) -> Self66     fn span_range_note(self, span_range: SpanRange, msg: String) -> Self;
67 }
68 
69 impl DiagnosticExt for Diagnostic {
spanned_range(span_range: SpanRange, level: Level, message: String) -> Self70     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 
span_range_error(mut self, span_range: SpanRange, msg: String) -> Self80     fn span_range_error(mut self, span_range: SpanRange, msg: String) -> Self {
81         self.children.push((span_range, msg));
82         self
83     }
84 
span_range_help(mut self, span_range: SpanRange, msg: String) -> Self85     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 
span_range_note(mut self, span_range: SpanRange, msg: String) -> Self91     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()`
new(level: Level, message: String) -> Self100     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`
spanned(span: Span, level: Level, message: String) -> Self105     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.
span_error(self, span: Span, msg: String) -> Self118     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
span_help(self, span: Span, msg: String) -> Self133     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.
help(mut self, msg: String) -> Self144     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
span_note(self, span: Span, msg: String) -> Self154     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
note(mut self, msg: String) -> Self165     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)
message(&self) -> &str171     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.
abort(self) -> !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
emit(self)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 {
span_suggestion(self, span: Span, suggestion: &str, msg: String) -> Self199     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 
suggestion(self, suggestion: &str, msg: String) -> Self206     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 {
to_tokens(&self, ts: &mut TokenStream)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 {
name(&self) -> &'static str282     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 {
from(err: syn::Error) -> Self292     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