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