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