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