Skip to main content

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