1 use crate::{abort_now, check_correctness};
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) start: Span,
25     pub(crate) end: Span,
26     pub(crate) msg: String,
27     pub(crate) suggestions: Vec<(SuggestionKind, String, Option<Span>)>,
28     pub(crate) children: Vec<(Span, Span, String)>,
29 }
30 
31 impl Diagnostic {
32     /// Create a new diagnostic message that points to `Span::call_site()`
new(level: Level, message: String) -> Self33     pub fn new(level: Level, message: String) -> Self {
34         Diagnostic::spanned(Span::call_site(), level, message)
35     }
36 
37     /// Create a new diagnostic message that points to the `span`
spanned(span: Span, level: Level, message: String) -> Self38     pub fn spanned(span: Span, level: Level, message: String) -> Self {
39         Diagnostic::double_spanned(span, span, level, message)
40     }
41 
42     /// Add another error message to self such that it will be emitted right after
43     /// the main message.
span_error(self, span: Span, msg: String) -> Self44     pub fn span_error(self, span: Span, msg: String) -> Self {
45         self.double_span_error(span, span, msg)
46     }
47 
48     /// Attach a "help" note to your main message, the note will have it's own span on nightly.
49     ///
50     /// # Span
51     ///
52     /// The span is ignored on stable, the note effectively inherits its parent's (main message) span
span_help(mut self, span: Span, msg: String) -> Self53     pub fn span_help(mut self, span: Span, msg: String) -> Self {
54         self.suggestions
55             .push((SuggestionKind::Help, msg, Some(span)));
56         self
57     }
58 
59     /// Attach a "help" note to your main message.
help(mut self, msg: String) -> Self60     pub fn help(mut self, msg: String) -> Self {
61         self.suggestions.push((SuggestionKind::Help, msg, None));
62         self
63     }
64 
65     /// Attach a note to your main message, the note will have it's own span on nightly.
66     ///
67     /// # Span
68     ///
69     /// The span is ignored on stable, the note effectively inherits its parent's (main message) span
span_note(mut self, span: Span, msg: String) -> Self70     pub fn span_note(mut self, span: Span, msg: String) -> Self {
71         self.suggestions
72             .push((SuggestionKind::Note, msg, Some(span)));
73         self
74     }
75 
76     /// Attach a note to your main message
note(mut self, msg: String) -> Self77     pub fn note(mut self, msg: String) -> Self {
78         self.suggestions.push((SuggestionKind::Note, msg, None));
79         self
80     }
81 
82     /// The message of main warning/error (no notes attached)
message(&self) -> &str83     pub fn message(&self) -> &str {
84         &self.msg
85     }
86 
87     /// Abort the proc-macro's execution and display the diagnostic.
88     ///
89     /// # Warnings
90     ///
91     /// Warnings do not get emitted on stable/beta but this function will abort anyway.
abort(self) -> !92     pub fn abort(self) -> ! {
93         self.emit();
94         abort_now()
95     }
96 
97     /// Display the diagnostic while not aborting macro execution.
98     ///
99     /// # Warnings
100     ///
101     /// Warnings are ignored on stable/beta
emit(self)102     pub fn emit(self) {
103         check_correctness();
104         crate::imp::emit_diagnostic(self);
105     }
106 }
107 
108 /// **NOT PUBLIC API! NOTHING TO SEE HERE!!!**
109 #[doc(hidden)]
110 impl Diagnostic {
double_spanned(start: Span, end: Span, level: Level, message: String) -> Self111     pub fn double_spanned(start: Span, end: Span, level: Level, message: String) -> Self {
112         Diagnostic {
113             level,
114             start,
115             end,
116             msg: message,
117             suggestions: vec![],
118             children: vec![],
119         }
120     }
121 
double_span_error(mut self, start: Span, end: Span, msg: String) -> Self122     pub fn double_span_error(mut self, start: Span, end: Span, msg: String) -> Self {
123         self.children.push((start, end, msg));
124         self
125     }
126 
span_suggestion(self, span: Span, suggestion: &str, msg: String) -> Self127     pub fn span_suggestion(self, span: Span, suggestion: &str, msg: String) -> Self {
128         match suggestion {
129             "help" | "hint" => self.span_help(span, msg),
130             _ => self.span_note(span, msg),
131         }
132     }
133 
suggestion(self, suggestion: &str, msg: String) -> Self134     pub fn suggestion(self, suggestion: &str, msg: String) -> Self {
135         match suggestion {
136             "help" | "hint" => self.help(msg),
137             _ => self.note(msg),
138         }
139     }
140 }
141 
142 impl ToTokens for Diagnostic {
to_tokens(&self, ts: &mut TokenStream)143     fn to_tokens(&self, ts: &mut TokenStream) {
144         use std::borrow::Cow;
145 
146         fn ensure_lf(buf: &mut String, s: &str) {
147             if s.ends_with('\n') {
148                 buf.push_str(s);
149             } else {
150                 buf.push_str(s);
151                 buf.push('\n');
152             }
153         }
154 
155         fn diag_to_tokens(
156             start: Span,
157             end: Span,
158             level: &Level,
159             msg: &str,
160             suggestions: &[(SuggestionKind, String, Option<Span>)],
161         ) -> TokenStream {
162             if *level == Level::Warning {
163                 return TokenStream::new();
164             }
165 
166             let message = if suggestions.is_empty() {
167                 Cow::Borrowed(msg)
168             } else {
169                 let mut message = String::new();
170                 ensure_lf(&mut message, msg);
171                 message.push('\n');
172 
173                 for (kind, note, _span) in suggestions {
174                     message.push_str("  = ");
175                     message.push_str(kind.name());
176                     message.push_str(": ");
177                     ensure_lf(&mut message, note);
178                 }
179                 message.push('\n');
180 
181                 Cow::Owned(message)
182             };
183 
184             let msg = syn::LitStr::new(&*message, end);
185             let group = quote_spanned!(end=> { #msg } );
186             quote_spanned!(start=> compile_error!#group)
187         }
188 
189         ts.extend(diag_to_tokens(
190             self.start,
191             self.end,
192             &self.level,
193             &self.msg,
194             &self.suggestions,
195         ));
196         ts.extend(
197             self.children
198                 .iter()
199                 .map(|(start, end, msg)| diag_to_tokens(*start, *end, &Level::Error, &msg, &[])),
200         );
201     }
202 }
203 
204 #[derive(Debug)]
205 pub(crate) enum SuggestionKind {
206     Help,
207     Note,
208 }
209 
210 impl SuggestionKind {
name(&self) -> &'static str211     fn name(&self) -> &'static str {
212         match self {
213             SuggestionKind::Note => "note",
214             SuggestionKind::Help => "help",
215         }
216     }
217 }
218 
219 impl From<syn::Error> for Diagnostic {
from(err: syn::Error) -> Self220     fn from(err: syn::Error) -> Self {
221         use proc_macro2::{Delimiter, TokenTree};
222 
223         fn gut_error(ts: &mut impl Iterator<Item = TokenTree>) -> Option<(Span, Span, String)> {
224             let start = match ts.next() {
225                 // compile_error
226                 None => return None,
227                 Some(tt) => tt.span(),
228             };
229             ts.next().unwrap(); // !
230 
231             let lit = match ts.next().unwrap() {
232                 TokenTree::Group(group) => {
233                     // Currently `syn` builds `compile_error!` invocations
234                     // exclusively in `ident{"..."}` (braced) form which is not
235                     // followed by `;` (semicolon).
236                     //
237                     // But if it changes to `ident("...");` (parenthesized)
238                     // or `ident["..."];` (bracketed) form,
239                     // we will need to skip the `;` as well.
240                     // Highly unlikely, but better safe than sorry.
241 
242                     if group.delimiter() == Delimiter::Parenthesis
243                         || group.delimiter() == Delimiter::Bracket
244                     {
245                         ts.next().unwrap(); // ;
246                     }
247 
248                     match group.stream().into_iter().next().unwrap() {
249                         TokenTree::Literal(lit) => lit,
250                         _ => unreachable!(),
251                     }
252                 }
253                 _ => unreachable!(),
254             };
255 
256             let end = lit.span();
257             let mut msg = lit.to_string();
258 
259             // "abc" => abc
260             msg.pop();
261             msg.remove(0);
262 
263             Some((start, end, msg))
264         }
265 
266         let mut ts = err.to_compile_error().into_iter();
267 
268         let (start, end, msg) = gut_error(&mut ts).unwrap();
269         let mut res = Diagnostic::double_spanned(start, end, Level::Error, msg);
270 
271         while let Some((start, end, msg)) = gut_error(&mut ts) {
272             res = res.double_span_error(start, end, msg);
273         }
274 
275         res
276     }
277 }
278