]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs
errors: introduce `DiagnosticMessage`
[rust.git] / compiler / rustc_errors / src / annotate_snippet_emitter_writer.rs
1 //! Emit diagnostics using the `annotate-snippets` library
2 //!
3 //! This is the equivalent of `./emitter.rs` but making use of the
4 //! [`annotate-snippets`][annotate_snippets] library instead of building the output ourselves.
5 //!
6 //! [annotate_snippets]: https://docs.rs/crate/annotate-snippets/
7
8 use crate::emitter::FileWithAnnotatedLines;
9 use crate::snippet::Line;
10 use crate::{CodeSuggestion, Diagnostic, DiagnosticId, Emitter, Level, SubDiagnostic};
11 use annotate_snippets::display_list::{DisplayList, FormatOptions};
12 use annotate_snippets::snippet::*;
13 use rustc_data_structures::sync::Lrc;
14 use rustc_span::source_map::SourceMap;
15 use rustc_span::{MultiSpan, SourceFile};
16
17 /// Generates diagnostics using annotate-snippet
18 pub struct AnnotateSnippetEmitterWriter {
19     source_map: Option<Lrc<SourceMap>>,
20     /// If true, hides the longer explanation text
21     short_message: bool,
22     /// If true, will normalize line numbers with `LL` to prevent noise in UI test diffs.
23     ui_testing: bool,
24
25     macro_backtrace: bool,
26 }
27
28 impl Emitter for AnnotateSnippetEmitterWriter {
29     /// The entry point for the diagnostics generation
30     fn emit_diagnostic(&mut self, diag: &Diagnostic) {
31         let mut children = diag.children.clone();
32         let (mut primary_span, suggestions) = self.primary_span_formatted(&diag);
33
34         self.fix_multispans_in_extern_macros_and_render_macro_backtrace(
35             &self.source_map,
36             &mut primary_span,
37             &mut children,
38             &diag.level,
39             self.macro_backtrace,
40         );
41
42         self.emit_messages_default(
43             &diag.level,
44             diag.message().to_string(),
45             &diag.code,
46             &primary_span,
47             &children,
48             &suggestions,
49         );
50     }
51
52     fn source_map(&self) -> Option<&Lrc<SourceMap>> {
53         self.source_map.as_ref()
54     }
55
56     fn should_show_explain(&self) -> bool {
57         !self.short_message
58     }
59 }
60
61 /// Provides the source string for the given `line` of `file`
62 fn source_string(file: Lrc<SourceFile>, line: &Line) -> String {
63     file.get_line(line.line_index - 1).map(|a| a.to_string()).unwrap_or_default()
64 }
65
66 /// Maps `Diagnostic::Level` to `snippet::AnnotationType`
67 fn annotation_type_for_level(level: Level) -> AnnotationType {
68     match level {
69         Level::Bug | Level::DelayedBug | Level::Fatal | Level::Error { .. } => {
70             AnnotationType::Error
71         }
72         Level::Warning => AnnotationType::Warning,
73         Level::Note | Level::OnceNote => AnnotationType::Note,
74         Level::Help => AnnotationType::Help,
75         // FIXME(#59346): Not sure how to map this level
76         Level::FailureNote => AnnotationType::Error,
77         Level::Allow => panic!("Should not call with Allow"),
78         Level::Expect(_) => panic!("Should not call with Expect"),
79     }
80 }
81
82 impl AnnotateSnippetEmitterWriter {
83     pub fn new(
84         source_map: Option<Lrc<SourceMap>>,
85         short_message: bool,
86         macro_backtrace: bool,
87     ) -> Self {
88         Self { source_map, short_message, ui_testing: false, macro_backtrace }
89     }
90
91     /// Allows to modify `Self` to enable or disable the `ui_testing` flag.
92     ///
93     /// If this is set to true, line numbers will be normalized as `LL` in the output.
94     pub fn ui_testing(mut self, ui_testing: bool) -> Self {
95         self.ui_testing = ui_testing;
96         self
97     }
98
99     fn emit_messages_default(
100         &mut self,
101         level: &Level,
102         message: String,
103         code: &Option<DiagnosticId>,
104         msp: &MultiSpan,
105         _children: &[SubDiagnostic],
106         _suggestions: &[CodeSuggestion],
107     ) {
108         if let Some(source_map) = &self.source_map {
109             // Make sure our primary file comes first
110             let primary_lo = if let Some(ref primary_span) = msp.primary_span().as_ref() {
111                 if primary_span.is_dummy() {
112                     // FIXME(#59346): Not sure when this is the case and what
113                     // should be done if it happens
114                     return;
115                 } else {
116                     source_map.lookup_char_pos(primary_span.lo())
117                 }
118             } else {
119                 // FIXME(#59346): Not sure when this is the case and what
120                 // should be done if it happens
121                 return;
122             };
123             let mut annotated_files =
124                 FileWithAnnotatedLines::collect_annotations(msp, &self.source_map);
125             if let Ok(pos) =
126                 annotated_files.binary_search_by(|x| x.file.name.cmp(&primary_lo.file.name))
127             {
128                 annotated_files.swap(0, pos);
129             }
130             // owned: line source, line index, annotations
131             type Owned = (String, usize, Vec<crate::snippet::Annotation>);
132             let filename = source_map.filename_for_diagnostics(&primary_lo.file.name);
133             let origin = filename.to_string_lossy();
134             let annotated_files: Vec<Owned> = annotated_files
135                 .into_iter()
136                 .flat_map(|annotated_file| {
137                     let file = annotated_file.file;
138                     annotated_file
139                         .lines
140                         .into_iter()
141                         .map(|line| {
142                             (source_string(file.clone(), &line), line.line_index, line.annotations)
143                         })
144                         .collect::<Vec<Owned>>()
145                 })
146                 .collect();
147             let snippet = Snippet {
148                 title: Some(Annotation {
149                     label: Some(&message),
150                     id: code.as_ref().map(|c| match c {
151                         DiagnosticId::Error(val) | DiagnosticId::Lint { name: val, .. } => {
152                             val.as_str()
153                         }
154                     }),
155                     annotation_type: annotation_type_for_level(*level),
156                 }),
157                 footer: vec![],
158                 opt: FormatOptions { color: true, anonymized_line_numbers: self.ui_testing },
159                 slices: annotated_files
160                     .iter()
161                     .map(|(source, line_index, annotations)| {
162                         Slice {
163                             source,
164                             line_start: *line_index,
165                             origin: Some(&origin),
166                             // FIXME(#59346): Not really sure when `fold` should be true or false
167                             fold: false,
168                             annotations: annotations
169                                 .iter()
170                                 .map(|annotation| SourceAnnotation {
171                                     range: (annotation.start_col, annotation.end_col),
172                                     label: annotation.label.as_deref().unwrap_or_default(),
173                                     annotation_type: annotation_type_for_level(*level),
174                                 })
175                                 .collect(),
176                         }
177                     })
178                     .collect(),
179             };
180             // FIXME(#59346): Figure out if we can _always_ print to stderr or not.
181             // `emitter.rs` has the `Destination` enum that lists various possible output
182             // destinations.
183             eprintln!("{}", DisplayList::from(snippet))
184         }
185         // FIXME(#59346): Is it ok to return None if there's no source_map?
186     }
187 }