1ea88969fSopenharmony_ciuse crate::{abort_now, check_correctness, sealed::Sealed, SpanRange};
2ea88969fSopenharmony_ciuse proc_macro2::Span;
3ea88969fSopenharmony_ciuse proc_macro2::TokenStream;
4ea88969fSopenharmony_ci
5ea88969fSopenharmony_ciuse quote::{quote_spanned, ToTokens};
6ea88969fSopenharmony_ci
7ea88969fSopenharmony_ci/// Represents a diagnostic level
8ea88969fSopenharmony_ci///
9ea88969fSopenharmony_ci/// # Warnings
10ea88969fSopenharmony_ci///
11ea88969fSopenharmony_ci/// Warnings are ignored on stable/beta
12ea88969fSopenharmony_ci#[derive(Debug, PartialEq)]
13ea88969fSopenharmony_cipub enum Level {
14ea88969fSopenharmony_ci    Error,
15ea88969fSopenharmony_ci    Warning,
16ea88969fSopenharmony_ci    #[doc(hidden)]
17ea88969fSopenharmony_ci    NonExhaustive,
18ea88969fSopenharmony_ci}
19ea88969fSopenharmony_ci
20ea88969fSopenharmony_ci/// Represents a single diagnostic message
21ea88969fSopenharmony_ci#[derive(Debug)]
22ea88969fSopenharmony_cipub struct Diagnostic {
23ea88969fSopenharmony_ci    pub(crate) level: Level,
24ea88969fSopenharmony_ci    pub(crate) span_range: SpanRange,
25ea88969fSopenharmony_ci    pub(crate) msg: String,
26ea88969fSopenharmony_ci    pub(crate) suggestions: Vec<(SuggestionKind, String, Option<SpanRange>)>,
27ea88969fSopenharmony_ci    pub(crate) children: Vec<(SpanRange, String)>,
28ea88969fSopenharmony_ci}
29ea88969fSopenharmony_ci
30ea88969fSopenharmony_ci/// A collection of methods that do not exist in `proc_macro::Diagnostic`
31ea88969fSopenharmony_ci/// but still useful to have around.
32ea88969fSopenharmony_ci///
33ea88969fSopenharmony_ci/// This trait is sealed and cannot be implemented outside of `proc_macro_error`.
34ea88969fSopenharmony_cipub trait DiagnosticExt: Sealed {
35ea88969fSopenharmony_ci    /// Create a new diagnostic message that points to the `span_range`.
36ea88969fSopenharmony_ci    ///
37ea88969fSopenharmony_ci    /// This function is the same as `Diagnostic::spanned` but produces considerably
38ea88969fSopenharmony_ci    /// better error messages for multi-token spans on stable.
39ea88969fSopenharmony_ci    fn spanned_range(span_range: SpanRange, level: Level, message: String) -> Self;
40ea88969fSopenharmony_ci
41ea88969fSopenharmony_ci    /// Add another error message to self such that it will be emitted right after
42ea88969fSopenharmony_ci    /// the main message.
43ea88969fSopenharmony_ci    ///
44ea88969fSopenharmony_ci    /// This function is the same as `Diagnostic::span_error` but produces considerably
45ea88969fSopenharmony_ci    /// better error messages for multi-token spans on stable.
46ea88969fSopenharmony_ci    fn span_range_error(self, span_range: SpanRange, msg: String) -> Self;
47ea88969fSopenharmony_ci
48ea88969fSopenharmony_ci    /// Attach a "help" note to your main message, the note will have it's own span on nightly.
49ea88969fSopenharmony_ci    ///
50ea88969fSopenharmony_ci    /// This function is the same as `Diagnostic::span_help` but produces considerably
51ea88969fSopenharmony_ci    /// better error messages for multi-token spans on stable.
52ea88969fSopenharmony_ci    ///
53ea88969fSopenharmony_ci    /// # Span
54ea88969fSopenharmony_ci    ///
55ea88969fSopenharmony_ci    /// The span is ignored on stable, the note effectively inherits its parent's (main message) span
56ea88969fSopenharmony_ci    fn span_range_help(self, span_range: SpanRange, msg: String) -> Self;
57ea88969fSopenharmony_ci
58ea88969fSopenharmony_ci    /// Attach a note to your main message, the note will have it's own span on nightly.
59ea88969fSopenharmony_ci    ///
60ea88969fSopenharmony_ci    /// This function is the same as `Diagnostic::span_note` but produces considerably
61ea88969fSopenharmony_ci    /// better error messages for multi-token spans on stable.
62ea88969fSopenharmony_ci    ///
63ea88969fSopenharmony_ci    /// # Span
64ea88969fSopenharmony_ci    ///
65ea88969fSopenharmony_ci    /// The span is ignored on stable, the note effectively inherits its parent's (main message) span
66ea88969fSopenharmony_ci    fn span_range_note(self, span_range: SpanRange, msg: String) -> Self;
67ea88969fSopenharmony_ci}
68ea88969fSopenharmony_ci
69ea88969fSopenharmony_ciimpl DiagnosticExt for Diagnostic {
70ea88969fSopenharmony_ci    fn spanned_range(span_range: SpanRange, level: Level, message: String) -> Self {
71ea88969fSopenharmony_ci        Diagnostic {
72ea88969fSopenharmony_ci            level,
73ea88969fSopenharmony_ci            span_range,
74ea88969fSopenharmony_ci            msg: message,
75ea88969fSopenharmony_ci            suggestions: vec![],
76ea88969fSopenharmony_ci            children: vec![],
77ea88969fSopenharmony_ci        }
78ea88969fSopenharmony_ci    }
79ea88969fSopenharmony_ci
80ea88969fSopenharmony_ci    fn span_range_error(mut self, span_range: SpanRange, msg: String) -> Self {
81ea88969fSopenharmony_ci        self.children.push((span_range, msg));
82ea88969fSopenharmony_ci        self
83ea88969fSopenharmony_ci    }
84ea88969fSopenharmony_ci
85ea88969fSopenharmony_ci    fn span_range_help(mut self, span_range: SpanRange, msg: String) -> Self {
86ea88969fSopenharmony_ci        self.suggestions
87ea88969fSopenharmony_ci            .push((SuggestionKind::Help, msg, Some(span_range)));
88ea88969fSopenharmony_ci        self
89ea88969fSopenharmony_ci    }
90ea88969fSopenharmony_ci
91ea88969fSopenharmony_ci    fn span_range_note(mut self, span_range: SpanRange, msg: String) -> Self {
92ea88969fSopenharmony_ci        self.suggestions
93ea88969fSopenharmony_ci            .push((SuggestionKind::Note, msg, Some(span_range)));
94ea88969fSopenharmony_ci        self
95ea88969fSopenharmony_ci    }
96ea88969fSopenharmony_ci}
97ea88969fSopenharmony_ci
98ea88969fSopenharmony_ciimpl Diagnostic {
99ea88969fSopenharmony_ci    /// Create a new diagnostic message that points to `Span::call_site()`
100ea88969fSopenharmony_ci    pub fn new(level: Level, message: String) -> Self {
101ea88969fSopenharmony_ci        Diagnostic::spanned(Span::call_site(), level, message)
102ea88969fSopenharmony_ci    }
103ea88969fSopenharmony_ci
104ea88969fSopenharmony_ci    /// Create a new diagnostic message that points to the `span`
105ea88969fSopenharmony_ci    pub fn spanned(span: Span, level: Level, message: String) -> Self {
106ea88969fSopenharmony_ci        Diagnostic::spanned_range(
107ea88969fSopenharmony_ci            SpanRange {
108ea88969fSopenharmony_ci                first: span,
109ea88969fSopenharmony_ci                last: span,
110ea88969fSopenharmony_ci            },
111ea88969fSopenharmony_ci            level,
112ea88969fSopenharmony_ci            message,
113ea88969fSopenharmony_ci        )
114ea88969fSopenharmony_ci    }
115ea88969fSopenharmony_ci
116ea88969fSopenharmony_ci    /// Add another error message to self such that it will be emitted right after
117ea88969fSopenharmony_ci    /// the main message.
118ea88969fSopenharmony_ci    pub fn span_error(self, span: Span, msg: String) -> Self {
119ea88969fSopenharmony_ci        self.span_range_error(
120ea88969fSopenharmony_ci            SpanRange {
121ea88969fSopenharmony_ci                first: span,
122ea88969fSopenharmony_ci                last: span,
123ea88969fSopenharmony_ci            },
124ea88969fSopenharmony_ci            msg,
125ea88969fSopenharmony_ci        )
126ea88969fSopenharmony_ci    }
127ea88969fSopenharmony_ci
128ea88969fSopenharmony_ci    /// Attach a "help" note to your main message, the note will have it's own span on nightly.
129ea88969fSopenharmony_ci    ///
130ea88969fSopenharmony_ci    /// # Span
131ea88969fSopenharmony_ci    ///
132ea88969fSopenharmony_ci    /// The span is ignored on stable, the note effectively inherits its parent's (main message) span
133ea88969fSopenharmony_ci    pub fn span_help(self, span: Span, msg: String) -> Self {
134ea88969fSopenharmony_ci        self.span_range_help(
135ea88969fSopenharmony_ci            SpanRange {
136ea88969fSopenharmony_ci                first: span,
137ea88969fSopenharmony_ci                last: span,
138ea88969fSopenharmony_ci            },
139ea88969fSopenharmony_ci            msg,
140ea88969fSopenharmony_ci        )
141ea88969fSopenharmony_ci    }
142ea88969fSopenharmony_ci
143ea88969fSopenharmony_ci    /// Attach a "help" note to your main message.
144ea88969fSopenharmony_ci    pub fn help(mut self, msg: String) -> Self {
145ea88969fSopenharmony_ci        self.suggestions.push((SuggestionKind::Help, msg, None));
146ea88969fSopenharmony_ci        self
147ea88969fSopenharmony_ci    }
148ea88969fSopenharmony_ci
149ea88969fSopenharmony_ci    /// Attach a note to your main message, the note will have it's own span on nightly.
150ea88969fSopenharmony_ci    ///
151ea88969fSopenharmony_ci    /// # Span
152ea88969fSopenharmony_ci    ///
153ea88969fSopenharmony_ci    /// The span is ignored on stable, the note effectively inherits its parent's (main message) span
154ea88969fSopenharmony_ci    pub fn span_note(self, span: Span, msg: String) -> Self {
155ea88969fSopenharmony_ci        self.span_range_note(
156ea88969fSopenharmony_ci            SpanRange {
157ea88969fSopenharmony_ci                first: span,
158ea88969fSopenharmony_ci                last: span,
159ea88969fSopenharmony_ci            },
160ea88969fSopenharmony_ci            msg,
161ea88969fSopenharmony_ci        )
162ea88969fSopenharmony_ci    }
163ea88969fSopenharmony_ci
164ea88969fSopenharmony_ci    /// Attach a note to your main message
165ea88969fSopenharmony_ci    pub fn note(mut self, msg: String) -> Self {
166ea88969fSopenharmony_ci        self.suggestions.push((SuggestionKind::Note, msg, None));
167ea88969fSopenharmony_ci        self
168ea88969fSopenharmony_ci    }
169ea88969fSopenharmony_ci
170ea88969fSopenharmony_ci    /// The message of main warning/error (no notes attached)
171ea88969fSopenharmony_ci    pub fn message(&self) -> &str {
172ea88969fSopenharmony_ci        &self.msg
173ea88969fSopenharmony_ci    }
174ea88969fSopenharmony_ci
175ea88969fSopenharmony_ci    /// Abort the proc-macro's execution and display the diagnostic.
176ea88969fSopenharmony_ci    ///
177ea88969fSopenharmony_ci    /// # Warnings
178ea88969fSopenharmony_ci    ///
179ea88969fSopenharmony_ci    /// Warnings are not emitted on stable and beta, but this function will abort anyway.
180ea88969fSopenharmony_ci    pub fn abort(self) -> ! {
181ea88969fSopenharmony_ci        self.emit();
182ea88969fSopenharmony_ci        abort_now()
183ea88969fSopenharmony_ci    }
184ea88969fSopenharmony_ci
185ea88969fSopenharmony_ci    /// Display the diagnostic while not aborting macro execution.
186ea88969fSopenharmony_ci    ///
187ea88969fSopenharmony_ci    /// # Warnings
188ea88969fSopenharmony_ci    ///
189ea88969fSopenharmony_ci    /// Warnings are ignored on stable/beta
190ea88969fSopenharmony_ci    pub fn emit(self) {
191ea88969fSopenharmony_ci        check_correctness();
192ea88969fSopenharmony_ci        crate::imp::emit_diagnostic(self);
193ea88969fSopenharmony_ci    }
194ea88969fSopenharmony_ci}
195ea88969fSopenharmony_ci
196ea88969fSopenharmony_ci/// **NOT PUBLIC API! NOTHING TO SEE HERE!!!**
197ea88969fSopenharmony_ci#[doc(hidden)]
198ea88969fSopenharmony_ciimpl Diagnostic {
199ea88969fSopenharmony_ci    pub fn span_suggestion(self, span: Span, suggestion: &str, msg: String) -> Self {
200ea88969fSopenharmony_ci        match suggestion {
201ea88969fSopenharmony_ci            "help" | "hint" => self.span_help(span, msg),
202ea88969fSopenharmony_ci            _ => self.span_note(span, msg),
203ea88969fSopenharmony_ci        }
204ea88969fSopenharmony_ci    }
205ea88969fSopenharmony_ci
206ea88969fSopenharmony_ci    pub fn suggestion(self, suggestion: &str, msg: String) -> Self {
207ea88969fSopenharmony_ci        match suggestion {
208ea88969fSopenharmony_ci            "help" | "hint" => self.help(msg),
209ea88969fSopenharmony_ci            _ => self.note(msg),
210ea88969fSopenharmony_ci        }
211ea88969fSopenharmony_ci    }
212ea88969fSopenharmony_ci}
213ea88969fSopenharmony_ci
214ea88969fSopenharmony_ciimpl ToTokens for Diagnostic {
215ea88969fSopenharmony_ci    fn to_tokens(&self, ts: &mut TokenStream) {
216ea88969fSopenharmony_ci        use std::borrow::Cow;
217ea88969fSopenharmony_ci
218ea88969fSopenharmony_ci        fn ensure_lf(buf: &mut String, s: &str) {
219ea88969fSopenharmony_ci            if s.ends_with('\n') {
220ea88969fSopenharmony_ci                buf.push_str(s);
221ea88969fSopenharmony_ci            } else {
222ea88969fSopenharmony_ci                buf.push_str(s);
223ea88969fSopenharmony_ci                buf.push('\n');
224ea88969fSopenharmony_ci            }
225ea88969fSopenharmony_ci        }
226ea88969fSopenharmony_ci
227ea88969fSopenharmony_ci        fn diag_to_tokens(
228ea88969fSopenharmony_ci            span_range: SpanRange,
229ea88969fSopenharmony_ci            level: &Level,
230ea88969fSopenharmony_ci            msg: &str,
231ea88969fSopenharmony_ci            suggestions: &[(SuggestionKind, String, Option<SpanRange>)],
232ea88969fSopenharmony_ci        ) -> TokenStream {
233ea88969fSopenharmony_ci            if *level == Level::Warning {
234ea88969fSopenharmony_ci                return TokenStream::new();
235ea88969fSopenharmony_ci            }
236ea88969fSopenharmony_ci
237ea88969fSopenharmony_ci            let message = if suggestions.is_empty() {
238ea88969fSopenharmony_ci                Cow::Borrowed(msg)
239ea88969fSopenharmony_ci            } else {
240ea88969fSopenharmony_ci                let mut message = String::new();
241ea88969fSopenharmony_ci                ensure_lf(&mut message, msg);
242ea88969fSopenharmony_ci                message.push('\n');
243ea88969fSopenharmony_ci
244ea88969fSopenharmony_ci                for (kind, note, _span) in suggestions {
245ea88969fSopenharmony_ci                    message.push_str("  = ");
246ea88969fSopenharmony_ci                    message.push_str(kind.name());
247ea88969fSopenharmony_ci                    message.push_str(": ");
248ea88969fSopenharmony_ci                    ensure_lf(&mut message, note);
249ea88969fSopenharmony_ci                }
250ea88969fSopenharmony_ci                message.push('\n');
251ea88969fSopenharmony_ci
252ea88969fSopenharmony_ci                Cow::Owned(message)
253ea88969fSopenharmony_ci            };
254ea88969fSopenharmony_ci
255ea88969fSopenharmony_ci            let mut msg = proc_macro2::Literal::string(&message);
256ea88969fSopenharmony_ci            msg.set_span(span_range.last);
257ea88969fSopenharmony_ci            let group = quote_spanned!(span_range.last=> { #msg } );
258ea88969fSopenharmony_ci            quote_spanned!(span_range.first=> compile_error!#group)
259ea88969fSopenharmony_ci        }
260ea88969fSopenharmony_ci
261ea88969fSopenharmony_ci        ts.extend(diag_to_tokens(
262ea88969fSopenharmony_ci            self.span_range,
263ea88969fSopenharmony_ci            &self.level,
264ea88969fSopenharmony_ci            &self.msg,
265ea88969fSopenharmony_ci            &self.suggestions,
266ea88969fSopenharmony_ci        ));
267ea88969fSopenharmony_ci        ts.extend(
268ea88969fSopenharmony_ci            self.children
269ea88969fSopenharmony_ci                .iter()
270ea88969fSopenharmony_ci                .map(|(span_range, msg)| diag_to_tokens(*span_range, &Level::Error, &msg, &[])),
271ea88969fSopenharmony_ci        );
272ea88969fSopenharmony_ci    }
273ea88969fSopenharmony_ci}
274ea88969fSopenharmony_ci
275ea88969fSopenharmony_ci#[derive(Debug)]
276ea88969fSopenharmony_cipub(crate) enum SuggestionKind {
277ea88969fSopenharmony_ci    Help,
278ea88969fSopenharmony_ci    Note,
279ea88969fSopenharmony_ci}
280ea88969fSopenharmony_ci
281ea88969fSopenharmony_ciimpl SuggestionKind {
282ea88969fSopenharmony_ci    fn name(&self) -> &'static str {
283ea88969fSopenharmony_ci        match self {
284ea88969fSopenharmony_ci            SuggestionKind::Note => "note",
285ea88969fSopenharmony_ci            SuggestionKind::Help => "help",
286ea88969fSopenharmony_ci        }
287ea88969fSopenharmony_ci    }
288ea88969fSopenharmony_ci}
289ea88969fSopenharmony_ci
290ea88969fSopenharmony_ci#[cfg(feature = "syn-error")]
291ea88969fSopenharmony_ciimpl From<syn::Error> for Diagnostic {
292ea88969fSopenharmony_ci    fn from(err: syn::Error) -> Self {
293ea88969fSopenharmony_ci        use proc_macro2::{Delimiter, TokenTree};
294ea88969fSopenharmony_ci
295ea88969fSopenharmony_ci        fn gut_error(ts: &mut impl Iterator<Item = TokenTree>) -> Option<(SpanRange, String)> {
296ea88969fSopenharmony_ci            let first = match ts.next() {
297ea88969fSopenharmony_ci                // compile_error
298ea88969fSopenharmony_ci                None => return None,
299ea88969fSopenharmony_ci                Some(tt) => tt.span(),
300ea88969fSopenharmony_ci            };
301ea88969fSopenharmony_ci            ts.next().unwrap(); // !
302ea88969fSopenharmony_ci
303ea88969fSopenharmony_ci            let lit = match ts.next().unwrap() {
304ea88969fSopenharmony_ci                TokenTree::Group(group) => {
305ea88969fSopenharmony_ci                    // Currently `syn` builds `compile_error!` invocations
306ea88969fSopenharmony_ci                    // exclusively in `ident{"..."}` (braced) form which is not
307ea88969fSopenharmony_ci                    // followed by `;` (semicolon).
308ea88969fSopenharmony_ci                    //
309ea88969fSopenharmony_ci                    // But if it changes to `ident("...");` (parenthesized)
310ea88969fSopenharmony_ci                    // or `ident["..."];` (bracketed) form,
311ea88969fSopenharmony_ci                    // we will need to skip the `;` as well.
312ea88969fSopenharmony_ci                    // Highly unlikely, but better safe than sorry.
313ea88969fSopenharmony_ci
314ea88969fSopenharmony_ci                    if group.delimiter() == Delimiter::Parenthesis
315ea88969fSopenharmony_ci                        || group.delimiter() == Delimiter::Bracket
316ea88969fSopenharmony_ci                    {
317ea88969fSopenharmony_ci                        ts.next().unwrap(); // ;
318ea88969fSopenharmony_ci                    }
319ea88969fSopenharmony_ci
320ea88969fSopenharmony_ci                    match group.stream().into_iter().next().unwrap() {
321ea88969fSopenharmony_ci                        TokenTree::Literal(lit) => lit,
322ea88969fSopenharmony_ci                        _ => unreachable!(),
323ea88969fSopenharmony_ci                    }
324ea88969fSopenharmony_ci                }
325ea88969fSopenharmony_ci                _ => unreachable!(),
326ea88969fSopenharmony_ci            };
327ea88969fSopenharmony_ci
328ea88969fSopenharmony_ci            let last = lit.span();
329ea88969fSopenharmony_ci            let mut msg = lit.to_string();
330ea88969fSopenharmony_ci
331ea88969fSopenharmony_ci            // "abc" => abc
332ea88969fSopenharmony_ci            msg.pop();
333ea88969fSopenharmony_ci            msg.remove(0);
334ea88969fSopenharmony_ci
335ea88969fSopenharmony_ci            Some((SpanRange { first, last }, msg))
336ea88969fSopenharmony_ci        }
337ea88969fSopenharmony_ci
338ea88969fSopenharmony_ci        let mut ts = err.to_compile_error().into_iter();
339ea88969fSopenharmony_ci
340ea88969fSopenharmony_ci        let (span_range, msg) = gut_error(&mut ts).unwrap();
341ea88969fSopenharmony_ci        let mut res = Diagnostic::spanned_range(span_range, Level::Error, msg);
342ea88969fSopenharmony_ci
343ea88969fSopenharmony_ci        while let Some((span_range, msg)) = gut_error(&mut ts) {
344ea88969fSopenharmony_ci            res = res.span_range_error(span_range, msg);
345ea88969fSopenharmony_ci        }
346ea88969fSopenharmony_ci
347ea88969fSopenharmony_ci        res
348ea88969fSopenharmony_ci    }
349ea88969fSopenharmony_ci}
350