swc_common/errors/
diagnostic.rs

1// Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT
2// file at the top-level directory of this distribution and at
3// http://rust-lang.org/COPYRIGHT.
4//
5// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8// option. This file may not be copied, modified, or distributed
9// except according to those terms.
10
11use std::fmt;
12
13use super::{snippet::Style, Applicability, CodeSuggestion, Level, Substitution, SubstitutionPart};
14use crate::syntax_pos::{MultiSpan, Span};
15
16#[derive(Clone, Debug, PartialEq, Eq, Hash)]
17#[cfg_attr(
18    feature = "diagnostic-serde",
19    derive(serde::Serialize, serde::Deserialize)
20)]
21#[cfg_attr(
22    any(feature = "rkyv-impl"),
23    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
24)]
25#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
26#[cfg_attr(feature = "rkyv-impl", repr(C))]
27pub struct Message(pub String, pub Style);
28
29#[must_use]
30#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)]
31#[cfg_attr(
32    feature = "diagnostic-serde",
33    derive(serde::Serialize, serde::Deserialize)
34)]
35#[cfg_attr(
36    any(feature = "rkyv-impl"),
37    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
38)]
39#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
40#[cfg_attr(feature = "rkyv-impl", repr(C))]
41/// Represents a diagnostic message with its level, message, unique identifier,
42/// span, children, and suggestions.
43pub struct Diagnostic {
44    /// The level of the diagnostic (e.g., error, warning, help)
45    pub level: Level,
46    /// The message(s) associated with the diagnostic
47    pub message: Vec<Message>,
48    /// A unique identifier for the diagnostic, which can be used to look up
49    /// more information
50    pub code: Option<DiagnosticId>,
51    /// The span of the source code where the diagnostic is located
52    pub span: MultiSpan,
53    /// Child diagnostics that are related to this diagnostic
54    pub children: Vec<SubDiagnostic>,
55    /// Suggestions for how to fix the issue identified by the diagnostic
56    pub suggestions: Vec<CodeSuggestion>,
57}
58
59#[derive(Clone, Debug, PartialEq, Eq, Hash)]
60#[cfg_attr(
61    feature = "diagnostic-serde",
62    derive(serde::Serialize, serde::Deserialize)
63)]
64#[cfg_attr(
65    any(feature = "rkyv-impl"),
66    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
67)]
68#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
69#[cfg_attr(feature = "rkyv-impl", repr(u32))]
70pub enum DiagnosticId {
71    Error(String),
72    Lint(String),
73}
74
75/// For example a note attached to an error.
76#[derive(Clone, Debug, PartialEq, Eq, Hash)]
77#[cfg_attr(
78    feature = "diagnostic-serde",
79    derive(serde::Serialize, serde::Deserialize)
80)]
81#[cfg_attr(
82    any(feature = "rkyv-impl"),
83    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
84)]
85#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
86#[cfg_attr(feature = "rkyv-impl", repr(C))]
87pub struct SubDiagnostic {
88    pub level: Level,
89    pub message: Vec<Message>,
90    pub span: MultiSpan,
91    pub render_span: Option<MultiSpan>,
92}
93
94#[derive(PartialEq, Eq, Default)]
95pub struct DiagnosticStyledString(pub Vec<StringPart>);
96
97impl DiagnosticStyledString {
98    pub fn new() -> DiagnosticStyledString {
99        Default::default()
100    }
101
102    pub fn push_normal<S: Into<String>>(&mut self, t: S) {
103        self.0.push(StringPart::Normal(t.into()));
104    }
105
106    pub fn push_highlighted<S: Into<String>>(&mut self, t: S) {
107        self.0.push(StringPart::Highlighted(t.into()));
108    }
109
110    pub fn normal<S: Into<String>>(t: S) -> DiagnosticStyledString {
111        DiagnosticStyledString(vec![StringPart::Normal(t.into())])
112    }
113
114    pub fn highlighted<S: Into<String>>(t: S) -> DiagnosticStyledString {
115        DiagnosticStyledString(vec![StringPart::Highlighted(t.into())])
116    }
117
118    pub fn content(&self) -> String {
119        self.0.iter().map(|x| x.content()).collect::<String>()
120    }
121}
122
123#[derive(PartialEq, Eq)]
124pub enum StringPart {
125    Normal(String),
126    Highlighted(String),
127}
128
129impl StringPart {
130    pub fn content(&self) -> &str {
131        match self {
132            &StringPart::Normal(ref s) | &StringPart::Highlighted(ref s) => s,
133        }
134    }
135}
136
137impl Diagnostic {
138    pub fn new(level: Level, message: &str) -> Self {
139        Diagnostic::new_with_code(level, None, message)
140    }
141
142    pub fn new_with_code(level: Level, code: Option<DiagnosticId>, message: &str) -> Self {
143        Diagnostic {
144            level,
145            message: vec![Message(message.to_owned(), Style::NoStyle)],
146            code,
147            span: MultiSpan::new(),
148            children: Vec::new(),
149            suggestions: Vec::new(),
150        }
151    }
152
153    pub fn is_error(&self) -> bool {
154        match self.level {
155            Level::Bug | Level::Fatal | Level::PhaseFatal | Level::Error | Level::FailureNote => {
156                true
157            }
158
159            Level::Warning | Level::Note | Level::Help | Level::Cancelled => false,
160        }
161    }
162
163    /// Cancel the diagnostic (a structured diagnostic must either be emitted or
164    /// canceled or it will panic when dropped).
165    pub fn cancel(&mut self) {
166        self.level = Level::Cancelled;
167    }
168
169    pub fn cancelled(&self) -> bool {
170        self.level == Level::Cancelled
171    }
172
173    /// Add a span/label to be included in the resulting snippet.
174    /// This is pushed onto the `MultiSpan` that was created when the
175    /// diagnostic was first built. If you don't call this function at
176    /// all, and you just supplied a `Span` to create the diagnostic,
177    /// then the snippet will just include that `Span`, which is
178    /// called the primary span.
179    pub fn span_label<T: Into<String>>(&mut self, span: Span, label: T) -> &mut Self {
180        self.span.push_span_label(span, label.into());
181        self
182    }
183
184    pub fn replace_span_with(&mut self, after: Span) -> &mut Self {
185        let before = self.span.clone();
186        self.set_span(after);
187        for span_label in before.span_labels() {
188            if let Some(label) = span_label.label {
189                self.span_label(after, label);
190            }
191        }
192        self
193    }
194
195    pub fn note_expected_found(
196        &mut self,
197        label: &dyn fmt::Display,
198        expected: DiagnosticStyledString,
199        found: DiagnosticStyledString,
200    ) -> &mut Self {
201        self.note_expected_found_extra(label, expected, found, &"", &"")
202    }
203
204    pub fn note_expected_found_extra(
205        &mut self,
206        label: &dyn fmt::Display,
207        expected: DiagnosticStyledString,
208        found: DiagnosticStyledString,
209        expected_extra: &dyn fmt::Display,
210        found_extra: &dyn fmt::Display,
211    ) -> &mut Self {
212        let mut msg: Vec<_> = vec![Message(format!("expected {label} `"), Style::NoStyle)];
213        msg.extend(expected.0.iter().map(|x| match *x {
214            StringPart::Normal(ref s) => Message(s.to_owned(), Style::NoStyle),
215            StringPart::Highlighted(ref s) => Message(s.to_owned(), Style::Highlight),
216        }));
217        msg.push(Message(format!("`{expected_extra}\n"), Style::NoStyle));
218        msg.push(Message(format!("   found {label} `"), Style::NoStyle));
219        msg.extend(found.0.iter().map(|x| match *x {
220            StringPart::Normal(ref s) => Message(s.to_owned(), Style::NoStyle),
221            StringPart::Highlighted(ref s) => Message(s.to_owned(), Style::Highlight),
222        }));
223        msg.push(Message(format!("`{found_extra}"), Style::NoStyle));
224
225        // For now, just attach these as notes
226        self.highlighted_note(msg);
227        self
228    }
229
230    pub fn note_trait_signature(&mut self, name: String, signature: String) -> &mut Self {
231        self.highlighted_note(vec![
232            Message(format!("`{name}` from trait: `"), Style::NoStyle),
233            Message(signature, Style::Highlight),
234            Message("`".to_string(), Style::NoStyle),
235        ]);
236        self
237    }
238
239    pub fn note(&mut self, msg: &str) -> &mut Self {
240        self.sub(Level::Note, msg, MultiSpan::new(), None);
241        self
242    }
243
244    pub fn highlighted_note(&mut self, msg: Vec<Message>) -> &mut Self {
245        self.sub_with_highlights(Level::Note, msg, MultiSpan::new(), None);
246        self
247    }
248
249    pub fn span_note<S: Into<MultiSpan>>(&mut self, sp: S, msg: &str) -> &mut Self {
250        self.sub(Level::Note, msg, sp.into(), None);
251        self
252    }
253
254    pub fn warn(&mut self, msg: &str) -> &mut Self {
255        self.sub(Level::Warning, msg, MultiSpan::new(), None);
256        self
257    }
258
259    pub fn span_warn<S: Into<MultiSpan>>(&mut self, sp: S, msg: &str) -> &mut Self {
260        self.sub(Level::Warning, msg, sp.into(), None);
261        self
262    }
263
264    pub fn help(&mut self, msg: &str) -> &mut Self {
265        self.sub(Level::Help, msg, MultiSpan::new(), None);
266        self
267    }
268
269    pub fn span_help<S: Into<MultiSpan>>(&mut self, sp: S, msg: &str) -> &mut Self {
270        self.sub(Level::Help, msg, sp.into(), None);
271        self
272    }
273
274    /// Prints out a message with a suggested edit of the code. If the
275    /// suggestion is presented inline it will only show the text message
276    /// and not the text.
277    ///
278    /// See `CodeSuggestion` for more information.
279    #[deprecated(note = "Use `span_suggestion_short_with_applicability`")]
280    pub fn span_suggestion_short(&mut self, sp: Span, msg: &str, suggestion: String) -> &mut Self {
281        self.suggestions.push(CodeSuggestion {
282            substitutions: vec![Substitution {
283                parts: vec![SubstitutionPart {
284                    snippet: suggestion,
285                    span: sp,
286                }],
287            }],
288            msg: msg.to_owned(),
289            show_code_when_inline: false,
290            applicability: Applicability::Unspecified,
291        });
292        self
293    }
294
295    /// Prints out a message with a suggested edit of the code.
296    ///
297    /// In case of short messages and a simple suggestion,
298    /// rustc displays it as a label like
299    ///
300    /// "try adding parentheses: `(tup.0).1`"
301    ///
302    /// The message
303    ///
304    /// * should not end in any punctuation (a `:` is added automatically)
305    /// * should not be a question
306    /// * should not contain any parts like "the following", "as shown"
307    /// * may look like "to do xyz, use" or "to do xyz, use abc"
308    /// * may contain a name of a function, variable or type, but not whole
309    ///   expressions
310    ///
311    /// See `CodeSuggestion` for more information.
312    #[deprecated(note = "Use `span_suggestion_with_applicability`")]
313    pub fn span_suggestion(&mut self, sp: Span, msg: &str, suggestion: String) -> &mut Self {
314        self.suggestions.push(CodeSuggestion {
315            substitutions: vec![Substitution {
316                parts: vec![SubstitutionPart {
317                    snippet: suggestion,
318                    span: sp,
319                }],
320            }],
321            msg: msg.to_owned(),
322            show_code_when_inline: true,
323            applicability: Applicability::Unspecified,
324        });
325        self
326    }
327
328    pub fn multipart_suggestion_with_applicability(
329        &mut self,
330        msg: &str,
331        suggestion: Vec<(Span, String)>,
332        applicability: Applicability,
333    ) -> &mut Self {
334        self.suggestions.push(CodeSuggestion {
335            substitutions: vec![Substitution {
336                parts: suggestion
337                    .into_iter()
338                    .map(|(span, snippet)| SubstitutionPart { snippet, span })
339                    .collect(),
340            }],
341            msg: msg.to_owned(),
342            show_code_when_inline: true,
343            applicability,
344        });
345        self
346    }
347
348    #[deprecated(note = "Use `multipart_suggestion_with_applicability`")]
349    pub fn multipart_suggestion(
350        &mut self,
351        msg: &str,
352        suggestion: Vec<(Span, String)>,
353    ) -> &mut Self {
354        self.multipart_suggestion_with_applicability(msg, suggestion, Applicability::Unspecified)
355    }
356
357    /// Prints out a message with multiple suggested edits of the code.
358    #[deprecated(note = "Use `span_suggestions_with_applicability`")]
359    pub fn span_suggestions(&mut self, sp: Span, msg: &str, suggestions: Vec<String>) -> &mut Self {
360        self.suggestions.push(CodeSuggestion {
361            substitutions: suggestions
362                .into_iter()
363                .map(|snippet| Substitution {
364                    parts: vec![SubstitutionPart { snippet, span: sp }],
365                })
366                .collect(),
367            msg: msg.to_owned(),
368            show_code_when_inline: true,
369            applicability: Applicability::Unspecified,
370        });
371        self
372    }
373
374    /// This is a suggestion that may contain mistakes or fillers and should
375    /// be read and understood by a human.
376    pub fn span_suggestion_with_applicability(
377        &mut self,
378        sp: Span,
379        msg: &str,
380        suggestion: String,
381        applicability: Applicability,
382    ) -> &mut Self {
383        self.suggestions.push(CodeSuggestion {
384            substitutions: vec![Substitution {
385                parts: vec![SubstitutionPart {
386                    snippet: suggestion,
387                    span: sp,
388                }],
389            }],
390            msg: msg.to_owned(),
391            show_code_when_inline: true,
392            applicability,
393        });
394        self
395    }
396
397    pub fn span_suggestions_with_applicability(
398        &mut self,
399        sp: Span,
400        msg: &str,
401        suggestions: impl Iterator<Item = String>,
402        applicability: Applicability,
403    ) -> &mut Self {
404        self.suggestions.push(CodeSuggestion {
405            substitutions: suggestions
406                .map(|snippet| Substitution {
407                    parts: vec![SubstitutionPart { snippet, span: sp }],
408                })
409                .collect(),
410            msg: msg.to_owned(),
411            show_code_when_inline: true,
412            applicability,
413        });
414        self
415    }
416
417    pub fn span_suggestion_short_with_applicability(
418        &mut self,
419        sp: Span,
420        msg: &str,
421        suggestion: String,
422        applicability: Applicability,
423    ) -> &mut Self {
424        self.suggestions.push(CodeSuggestion {
425            substitutions: vec![Substitution {
426                parts: vec![SubstitutionPart {
427                    snippet: suggestion,
428                    span: sp,
429                }],
430            }],
431            msg: msg.to_owned(),
432            show_code_when_inline: false,
433            applicability,
434        });
435        self
436    }
437
438    pub fn set_span<S: Into<MultiSpan>>(&mut self, sp: S) -> &mut Self {
439        self.span = sp.into();
440        self
441    }
442
443    pub fn code(&mut self, s: DiagnosticId) -> &mut Self {
444        self.code = Some(s);
445        self
446    }
447
448    pub fn get_code(&self) -> Option<DiagnosticId> {
449        self.code.clone()
450    }
451
452    pub fn message(&self) -> String {
453        self.message
454            .iter()
455            .map(|i| i.0.as_str())
456            .collect::<String>()
457    }
458
459    pub fn styled_message(&self) -> &Vec<Message> {
460        &self.message
461    }
462
463    /// Used by a lint. Copies over all details *but* the "main
464    /// message".
465    pub fn copy_details_not_message(&mut self, from: &Diagnostic) {
466        self.span = from.span.clone();
467        self.code.clone_from(&from.code);
468        self.children.extend(from.children.iter().cloned())
469    }
470
471    /// Convenience function for internal use, clients should use one of the
472    /// public methods above.
473    pub fn sub(
474        &mut self,
475        level: Level,
476        message: &str,
477        span: MultiSpan,
478        render_span: Option<MultiSpan>,
479    ) {
480        let sub = SubDiagnostic {
481            level,
482            message: vec![Message(message.to_owned(), Style::NoStyle)],
483            span,
484            render_span,
485        };
486        self.children.push(sub);
487    }
488
489    /// Convenience function for internal use, clients should use one of the
490    /// public methods above.
491    fn sub_with_highlights(
492        &mut self,
493        level: Level,
494        message: Vec<Message>,
495        span: MultiSpan,
496        render_span: Option<MultiSpan>,
497    ) {
498        let sub = SubDiagnostic {
499            level,
500            message,
501            span,
502            render_span,
503        };
504        self.children.push(sub);
505    }
506}
507
508impl SubDiagnostic {
509    pub fn message(&self) -> String {
510        self.message
511            .iter()
512            .map(|i| i.0.as_str())
513            .collect::<String>()
514    }
515
516    pub fn styled_message(&self) -> &Vec<Message> {
517        &self.message
518    }
519}