]> git.lizzy.rs Git - rust.git/blob - src/librustc_errors/annotate_snippet_emitter_writer.rs
Rollup merge of #66013 - nnethercote:avoid-hashing-twice-in-get_query, r=Zoxc
[rust.git] / src / librustc_errors / 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 syntax_pos::{SourceFile, MultiSpan, Loc};
9 use crate::{
10     Level, CodeSuggestion, Diagnostic, Emitter,
11     SourceMapperDyn, SubDiagnostic, DiagnosticId
12 };
13 use crate::emitter::FileWithAnnotatedLines;
14 use rustc_data_structures::sync::Lrc;
15 use crate::snippet::Line;
16 use annotate_snippets::snippet::*;
17 use annotate_snippets::display_list::DisplayList;
18 use annotate_snippets::formatter::DisplayListFormatter;
19
20
21 /// Generates diagnostics using annotate-snippet
22 pub struct AnnotateSnippetEmitterWriter {
23     source_map: Option<Lrc<SourceMapperDyn>>,
24     /// If true, hides the longer explanation text
25     short_message: bool,
26     /// If true, will normalize line numbers with `LL` to prevent noise in UI test diffs.
27     ui_testing: bool,
28
29     external_macro_backtrace: bool,
30 }
31
32 impl Emitter for AnnotateSnippetEmitterWriter {
33     /// The entry point for the diagnostics generation
34     fn emit_diagnostic(&mut self, diag: &Diagnostic) {
35         let mut children = diag.children.clone();
36         let (mut primary_span, suggestions) = self.primary_span_formatted(&diag);
37
38         self.fix_multispans_in_std_macros(&self.source_map,
39                                           &mut primary_span,
40                                           &mut children,
41                                           &diag.level,
42                                           self.external_macro_backtrace);
43
44         self.emit_messages_default(&diag.level,
45                                    diag.message(),
46                                    &diag.code,
47                                    &primary_span,
48                                    &children,
49                                    &suggestions);
50     }
51
52     fn source_map(&self) -> Option<&Lrc<SourceMapperDyn>> {
53         self.source_map.as_ref()
54     }
55
56     fn should_show_explain(&self) -> bool {
57         !self.short_message
58     }
59 }
60
61 /// Collects all the data needed to generate the data structures needed for the
62 /// `annotate-snippets` library.
63 struct DiagnosticConverter<'a> {
64     source_map: Option<Lrc<SourceMapperDyn>>,
65     level: Level,
66     message: String,
67     code: Option<DiagnosticId>,
68     msp: MultiSpan,
69     #[allow(dead_code)]
70     children: &'a [SubDiagnostic],
71     #[allow(dead_code)]
72     suggestions: &'a [CodeSuggestion]
73 }
74
75 impl<'a>  DiagnosticConverter<'a> {
76     /// Turns rustc Diagnostic information into a `annotate_snippets::snippet::Snippet`.
77     fn to_annotation_snippet(&self) -> Option<Snippet> {
78         if let Some(source_map) = &self.source_map {
79             // Make sure our primary file comes first
80             let primary_lo = if let Some(ref primary_span) =
81                 self.msp.primary_span().as_ref() {
82                 source_map.lookup_char_pos(primary_span.lo())
83             } else {
84                 // FIXME(#59346): Not sure when this is the case and what
85                 // should be done if it happens
86                 return None
87             };
88             let annotated_files = FileWithAnnotatedLines::collect_annotations(
89                 &self.msp,
90                 &self.source_map
91             );
92             let slices = self.slices_for_files(annotated_files, primary_lo);
93
94             Some(Snippet {
95                 title: Some(Annotation {
96                     label: Some(self.message.to_string()),
97                     id: self.code.clone().map(|c| {
98                         match c {
99                             DiagnosticId::Error(val) | DiagnosticId::Lint(val) => val
100                         }
101                     }),
102                     annotation_type: Self::annotation_type_for_level(self.level),
103                 }),
104                 footer: vec![],
105                 slices,
106             })
107         } else {
108             // FIXME(#59346): Is it ok to return None if there's no source_map?
109             None
110         }
111     }
112
113     fn slices_for_files(
114         &self,
115         annotated_files: Vec<FileWithAnnotatedLines>,
116         primary_lo: Loc
117     ) -> Vec<Slice> {
118         // FIXME(#64205): Provide a test case where `annotated_files` is > 1
119         annotated_files.iter().flat_map(|annotated_file| {
120             annotated_file.lines.iter().map(|line| {
121                 let line_source = Self::source_string(annotated_file.file.clone(), &line);
122                 Slice {
123                     source: line_source,
124                     line_start: line.line_index,
125                     origin: Some(primary_lo.file.name.to_string()),
126                     // FIXME(#59346): Not really sure when `fold` should be true or false
127                     fold: false,
128                     annotations: line.annotations.iter().map(|a| {
129                         self.annotation_to_source_annotation(a.clone())
130                     }).collect(),
131                 }
132             }).collect::<Vec<Slice>>()
133         }).collect::<Vec<Slice>>()
134     }
135
136     /// Turns a `crate::snippet::Annotation` into a `SourceAnnotation`
137     fn annotation_to_source_annotation(
138         &self,
139         annotation: crate::snippet::Annotation
140     ) -> SourceAnnotation {
141         SourceAnnotation {
142             range: (annotation.start_col, annotation.end_col),
143             label: annotation.label.unwrap_or("".to_string()),
144             annotation_type: Self::annotation_type_for_level(self.level)
145         }
146     }
147
148     /// Provides the source string for the given `line` of `file`
149     fn source_string(
150         file: Lrc<SourceFile>,
151         line: &Line
152     ) -> String {
153         file.get_line(line.line_index - 1).map(|a| a.to_string()).unwrap_or(String::new())
154     }
155
156     /// Maps `Diagnostic::Level` to `snippet::AnnotationType`
157     fn annotation_type_for_level(level: Level) -> AnnotationType {
158         match level {
159             Level::Bug | Level::Fatal | Level::Error => AnnotationType::Error,
160             Level::Warning => AnnotationType::Warning,
161             Level::Note => AnnotationType::Note,
162             Level::Help => AnnotationType::Help,
163             // FIXME(#59346): Not sure how to map these two levels
164             Level::Cancelled | Level::FailureNote => AnnotationType::Error
165         }
166     }
167 }
168
169 impl AnnotateSnippetEmitterWriter {
170     pub fn new(
171         source_map: Option<Lrc<SourceMapperDyn>>,
172         short_message: bool,
173         external_macro_backtrace: bool,
174     ) -> Self {
175         Self {
176             source_map,
177             short_message,
178             ui_testing: false,
179             external_macro_backtrace,
180         }
181     }
182
183     /// Allows to modify `Self` to enable or disable the `ui_testing` flag.
184     ///
185     /// If this is set to true, line numbers will be normalized as `LL` in the output.
186     pub fn ui_testing(mut self, ui_testing: bool) -> Self {
187         self.ui_testing = ui_testing;
188         self
189     }
190
191     fn emit_messages_default(
192         &mut self,
193         level: &Level,
194         message: String,
195         code: &Option<DiagnosticId>,
196         msp: &MultiSpan,
197         children: &[SubDiagnostic],
198         suggestions: &[CodeSuggestion]
199     ) {
200         let converter = DiagnosticConverter {
201             source_map: self.source_map.clone(),
202             level: level.clone(),
203             message,
204             code: code.clone(),
205             msp: msp.clone(),
206             children,
207             suggestions
208         };
209         if let Some(snippet) = converter.to_annotation_snippet() {
210             let dl = DisplayList::from(snippet);
211             let dlf = DisplayListFormatter::new(true, self.ui_testing);
212             // FIXME(#59346): Figure out if we can _always_ print to stderr or not.
213             // `emitter.rs` has the `Destination` enum that lists various possible output
214             // destinations.
215             eprintln!("{}", dlf.format(&dl));
216         };
217     }
218 }