proc_macro_error2/
diagnostic.rs

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)]
13#[non_exhaustive]
14pub 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"]
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    #[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
72impl 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
101impl 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)]
202impl 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
218impl 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)]
280pub(crate) enum SuggestionKind {
281    Help,
282    Note,
283}
284
285impl 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")]
295impl 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}