1use crate::{abort_now, check_correctness, sealed::Sealed, SpanRange};
2use proc_macro2::Span;
3use proc_macro2::TokenStream;
4
5use 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)]
13pub enum Level {
14 Error,
15 Warning,
16 #[doc(hidden)]
17 NonExhaustive,
18}
19
20/// Represents a single diagnostic message
21#[derive(Debug)]
22pub 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`.
34pub 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
69impl 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
98impl 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)]
198impl 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
214impl 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)]
276pub(crate) enum SuggestionKind {
277 Help,
278 Note,
279}
280
281impl 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")]
291impl 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