proc_macro2_diagnostics/
diagnostic.rs

1use proc_macro2::{Span, TokenStream};
2
3use crate::SpanDiagnosticExt;
4use crate::line::Line;
5
6/// Trait implemented by types that can be converted into a set of `Span`s.
7pub trait MultiSpan {
8    /// Converts `self` into a `Vec<Span>`.
9    fn into_spans(self) -> Vec<Span>;
10}
11
12impl MultiSpan for Span {
13    fn into_spans(self) -> Vec<Span> { vec![self] }
14}
15
16impl MultiSpan for Vec<Span> {
17    fn into_spans(self) -> Vec<Span> { self }
18}
19
20impl<'a> MultiSpan for &'a [Span] {
21    fn into_spans(self) -> Vec<Span> {
22        self.to_vec()
23    }
24}
25
26/// An enum representing a diagnostic level.
27#[non_exhaustive]
28#[derive(Copy, Clone, Debug, PartialEq, Eq)]
29pub enum Level {
30    /// An error.
31    Error,
32    /// A warning.
33    Warning,
34    /// A note.
35    Note,
36    /// A help message.
37    Help,
38}
39
40impl std::str::FromStr for Level {
41    type Err = ();
42    fn from_str(s: &str) -> Result<Self, Self::Err> {
43        if s.contains(Level::Error.as_str()) {
44            Ok(Level::Error)
45        } else if s.contains(Level::Warning.as_str()) {
46            Ok(Level::Warning)
47        } else if s.contains(Level::Note.as_str()) {
48            Ok(Level::Note)
49        } else if s.contains(Level::Help.as_str()) {
50            Ok(Level::Help)
51        } else {
52            Err(())
53        }
54    }
55}
56
57impl Level {
58    fn as_str(self) -> &'static str {
59        match self {
60            Level::Error => "error",
61            Level::Warning => "warning",
62            Level::Note => "note",
63            Level::Help => "help",
64        }
65    }
66}
67
68impl std::fmt::Display for Level {
69    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70        self.as_str().fmt(f)
71    }
72}
73
74/// A structure representing a diagnostic message and associated children
75/// messages.
76#[derive(Clone, Debug)]
77pub struct Diagnostic {
78    level: Level,
79    message: String,
80    spans: Vec<Span>,
81    children: Vec<Diagnostic>
82}
83
84macro_rules! diagnostic_child_methods {
85    ($spanned:ident, $regular:ident, $level:expr) => (
86        /// Adds a new child diagnostic message to `self` with the level
87        /// identified by this method's name with the given `spans` and
88        /// `message`.
89        pub fn $spanned<S, T>(self, spans: S, message: T) -> Diagnostic
90            where S: MultiSpan, T: Into<String>
91        {
92            self.spanned_child(spans, $level, message)
93        }
94
95        /// Adds a new child diagnostic message to `self` with the level
96        /// identified by this method's name with the given `message`.
97        pub fn $regular<T: Into<String>>(self, message: T) -> Diagnostic {
98            self.child($level, message)
99        }
100    )
101}
102
103impl Diagnostic {
104    /// Creates a new diagnostic with the given `level` and `message`.
105    pub fn new<T: Into<String>>(level: Level, message: T) -> Diagnostic {
106        Diagnostic {
107            level,
108            message: message.into(),
109            spans: vec![],
110            children: vec![]
111        }
112    }
113
114    /// Creates a new diagnostic with the given `level` and `message` pointing
115    /// to the given set of `spans`.
116    pub fn spanned<S, T>(spans: S, level: Level, message: T) -> Diagnostic
117        where S: MultiSpan, T: Into<String>
118    {
119        Diagnostic {
120            level,
121            message: message.into(),
122            spans: spans.into_spans(),
123            children: vec![]
124        }
125    }
126
127    /// Adds a new child diagnostic message to `self` with the `level` and the
128    /// given `spans` and `message`.
129    pub fn spanned_child<S, T>(mut self, spans: S, level: Level, message: T) -> Diagnostic
130        where S: MultiSpan, T: Into<String>
131    {
132        self.children.push(Diagnostic::spanned(spans, level, message));
133        self
134    }
135
136    /// Adds a new child diagnostic message to `self` with `level` and the given
137    /// `message`.
138    pub fn child<T: Into<String>>(mut self, level: Level, message: T) -> Diagnostic {
139        self.children.push(Diagnostic::new(level, message));
140        self
141    }
142
143    diagnostic_child_methods!(span_error, error, Level::Error);
144    diagnostic_child_methods!(span_warning, warning, Level::Warning);
145    diagnostic_child_methods!(span_note, note, Level::Note);
146    diagnostic_child_methods!(span_help, help, Level::Help);
147
148    /// Return the children diagnostics of `self`.
149    pub fn children(&self) -> impl Iterator<Item=&Diagnostic> {
150        self.children.iter()
151    }
152
153    /// Return the `level` of `self`.
154    pub fn level(&self) -> Level {
155        self.level
156    }
157
158    fn stable_emit_as_tokens(self, item: bool) -> TokenStream {
159        let error: syn::parse::Error = self.into();
160        if item {
161            error.to_compile_error()
162        } else {
163            let compile_error_calls = error.into_iter().map(|e| {
164                let compile_error = e.to_compile_error();
165                quote::quote_spanned!(e.span() => #compile_error;)
166            });
167
168            quote::quote!({ #(#compile_error_calls)* })
169        }
170    }
171
172    /// Emit the diagnostic as tokens.
173    #[cfg(not(nightly_diagnostics))]
174    fn emit_as_tokens(self, item: bool, _: TokenStream) -> TokenStream {
175        self.stable_emit_as_tokens(item)
176    }
177
178    /// Emit the diagnostic as tokens.
179    #[cfg(nightly_diagnostics)]
180    fn emit_as_tokens(self, item: bool, default: TokenStream) -> TokenStream {
181        if !crate::nightly_works() {
182            return self.stable_emit_as_tokens(item);
183        }
184
185        proc_macro::Diagnostic::from(self).emit();
186        default
187    }
188
189    /// Emit tokens, suitable for item contexts, to generate a comple-time
190    /// diagnostic corresponding to `self`. On nightly, this directly emits the
191    /// error and returns an empty token stream.
192    pub fn emit_as_item_tokens(self) -> TokenStream {
193        self.emit_as_tokens(true, TokenStream::new())
194    }
195
196    /// Emit tokens, suitable for item contexts, to generate a comple-time
197    /// diagnostic corresponding to `self`. On nightly, this directly emits the
198    /// error and returns `default`.
199    pub fn emit_as_item_tokens_or(self, default: TokenStream) -> TokenStream {
200        self.emit_as_tokens(true, default)
201    }
202
203    /// Emit tokens, suitable for expression contexts, to generate a comple-time
204    /// diagnostic corresponding to `self`. On nightly, this directly emits the
205    /// error and returns a `()` token stream.
206    pub fn emit_as_expr_tokens(self) -> TokenStream {
207        self.emit_as_tokens(false, quote::quote!({}))
208    }
209
210    /// Emit tokens, suitable for expressioon contexts, to generate a
211    /// comple-time diagnostic corresponding to `self`. On nightly, this
212    /// directly emits the error and returns `default`.
213    pub fn emit_as_expr_tokens_or(self, default: TokenStream) -> TokenStream {
214        self.emit_as_tokens(false, default)
215    }
216}
217
218impl From<Diagnostic> for syn::parse::Error {
219    fn from(diag: Diagnostic) -> syn::parse::Error {
220        fn diag_to_msg(diag: &Diagnostic) -> String {
221            let (spans, level, msg) = (&diag.spans, diag.level, &diag.message);
222            if spans.is_empty() {
223                Line::joined(level, msg).to_string()
224            } else {
225                if level == Level::Error {
226                    return msg.into();
227                }
228
229                Line::new(level, msg).to_string()
230            }
231        }
232
233        fn diag_to_span(diag: &Diagnostic) -> Span {
234            diag.spans.get(0).cloned().unwrap_or_else(|| Span::call_site())
235        }
236
237        let mut msg = diag_to_msg(&diag);
238        let mut span = diag_to_span(&diag);
239        let mut error: Option<syn::Error> = None;
240        for child in diag.children {
241            if child.spans.is_empty() {
242                // Join to the current error we're building up.
243                msg.push_str(&format!("\n{}", diag_to_msg(&child)));
244            } else {
245                // This creates a new error with all of the diagnostic messages
246                // that have been joined thus far in `msg`.
247                let new_error = syn::parse::Error::new(span, &msg);
248                if let Some(ref mut error) = error {
249                    error.combine(new_error);
250                } else {
251                    error = Some(new_error);
252                }
253
254                // Start a new error to be built from `child`.
255                span = diag_to_span(&child);
256                msg = diag_to_msg(&child);
257            }
258        }
259
260        if let Some(mut error) = error {
261            error.combine(syn::parse::Error::new(span, &msg));
262            error
263        } else {
264            syn::parse::Error::new(span, &msg)
265        }
266    }
267}
268
269impl From<syn::parse::Error> for Diagnostic {
270    fn from(error: syn::parse::Error) -> Diagnostic {
271        let mut diag: Option<Diagnostic> = None;
272        for e in &error {
273            for line in e.to_string().lines() {
274                if let Some(line) = Line::parse(line) {
275                    if line.is_new() {
276                        diag = diag.map(|d| d.spanned_child(e.span(), line.level, line.msg))
277                            .or_else(|| Some(Diagnostic::spanned(e.span(), line.level, line.msg)));
278                    } else {
279                        diag = diag.map(|d| d.child(line.level, line.msg));
280                    }
281                } else {
282                    diag = diag.map(|d| d.span_error(e.span(), line))
283                        .or_else(|| Some(e.span().error(line)));
284                }
285            }
286        }
287
288        diag.unwrap_or_else(|| error.span().error(error.to_string()))
289    }
290}
291
292#[cfg(nightly_diagnostics)]
293impl From<Diagnostic> for proc_macro::Diagnostic {
294    fn from(diag: Diagnostic) -> proc_macro::Diagnostic {
295        fn spans_to_proc_macro_spans(spans: Vec<Span>) -> Vec<proc_macro::Span> {
296            spans.into_iter()
297                .map(|s| s.unstable())
298                .collect::<Vec<proc_macro::Span>>()
299        }
300
301        let spans = spans_to_proc_macro_spans(diag.spans);
302
303        let level = match diag.level {
304            Level::Error => proc_macro::Level::Error,
305            Level::Warning => proc_macro::Level::Warning,
306            Level::Note => proc_macro::Level::Note,
307            Level::Help => proc_macro::Level::Help,
308        };
309
310        let mut proc_diag = proc_macro::Diagnostic::spanned(spans, level, diag.message);
311        for child in diag.children {
312            // FIXME: proc_macro::Diagnostic needs a `push` method.
313            let spans = spans_to_proc_macro_spans(child.spans);
314            proc_diag = match child.level {
315                Level::Error => proc_diag.span_error(spans, child.message),
316                Level::Warning => proc_diag.span_warning(spans, child.message),
317                Level::Note => proc_diag.span_note(spans, child.message),
318                Level::Help => proc_diag.span_help(spans, child.message),
319            };
320        }
321
322        proc_diag
323    }
324}