proc_macro_error/
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)]
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}