rustc_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
8use std::sync::Arc;
9
10use annotate_snippets::{Renderer, Snippet};
11use rustc_error_messages::FluentArgs;
12use rustc_span::SourceFile;
13use rustc_span::source_map::SourceMap;
14
15use crate::emitter::FileWithAnnotatedLines;
16use crate::registry::Registry;
17use crate::snippet::Line;
18use crate::translation::{Translator, to_fluent_args};
19use crate::{
20    CodeSuggestion, DiagInner, DiagMessage, Emitter, ErrCode, Level, MultiSpan, Style, Subdiag,
21};
22
23/// Generates diagnostics using annotate-snippet
24pub struct AnnotateSnippetEmitter {
25    source_map: Option<Arc<SourceMap>>,
26    translator: Translator,
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
36impl Emitter for AnnotateSnippetEmitter {
37    /// The entry point for the diagnostics generation
38    fn emit_diagnostic(&mut self, mut diag: DiagInner, _registry: &Registry) {
39        let fluent_args = to_fluent_args(diag.args.iter());
40
41        let mut suggestions = diag.suggestions.unwrap_tag();
42        self.primary_span_formatted(&mut diag.span, &mut suggestions, &fluent_args);
43
44        self.fix_multispans_in_extern_macros_and_render_macro_backtrace(
45            &mut diag.span,
46            &mut diag.children,
47            &diag.level,
48            self.macro_backtrace,
49        );
50
51        self.emit_messages_default(
52            &diag.level,
53            &diag.messages,
54            &fluent_args,
55            &diag.code,
56            &diag.span,
57            &diag.children,
58            &suggestions,
59        );
60    }
61
62    fn source_map(&self) -> Option<&SourceMap> {
63        self.source_map.as_deref()
64    }
65
66    fn should_show_explain(&self) -> bool {
67        !self.short_message
68    }
69
70    fn translator(&self) -> &Translator {
71        &self.translator
72    }
73}
74
75/// Provides the source string for the given `line` of `file`
76fn source_string(file: Arc<SourceFile>, line: &Line) -> String {
77    file.get_line(line.line_index - 1).map(|a| a.to_string()).unwrap_or_default()
78}
79
80/// Maps [`crate::Level`] to [`annotate_snippets::Level`]
81fn annotation_level_for_level(level: Level) -> annotate_snippets::Level {
82    match level {
83        Level::Bug | Level::Fatal | Level::Error | Level::DelayedBug => {
84            annotate_snippets::Level::Error
85        }
86        Level::ForceWarning | Level::Warning => annotate_snippets::Level::Warning,
87        Level::Note | Level::OnceNote => annotate_snippets::Level::Note,
88        Level::Help | Level::OnceHelp => annotate_snippets::Level::Help,
89        // FIXME(#59346): Not sure how to map this level
90        Level::FailureNote => annotate_snippets::Level::Error,
91        Level::Allow => panic!("Should not call with Allow"),
92        Level::Expect => panic!("Should not call with Expect"),
93    }
94}
95
96impl AnnotateSnippetEmitter {
97    pub fn new(
98        source_map: Option<Arc<SourceMap>>,
99        translator: Translator,
100        short_message: bool,
101        macro_backtrace: bool,
102    ) -> Self {
103        Self { source_map, translator, short_message, ui_testing: false, macro_backtrace }
104    }
105
106    /// Allows to modify `Self` to enable or disable the `ui_testing` flag.
107    ///
108    /// If this is set to true, line numbers will be normalized as `LL` in the output.
109    pub fn ui_testing(mut self, ui_testing: bool) -> Self {
110        self.ui_testing = ui_testing;
111        self
112    }
113
114    fn emit_messages_default(
115        &mut self,
116        level: &Level,
117        messages: &[(DiagMessage, Style)],
118        args: &FluentArgs<'_>,
119        code: &Option<ErrCode>,
120        msp: &MultiSpan,
121        _children: &[Subdiag],
122        _suggestions: &[CodeSuggestion],
123    ) {
124        let message = self.translator.translate_messages(messages, args);
125        if let Some(source_map) = &self.source_map {
126            // Make sure our primary file comes first
127            let primary_lo = if let Some(primary_span) = msp.primary_span().as_ref() {
128                if primary_span.is_dummy() {
129                    // FIXME(#59346): Not sure when this is the case and what
130                    // should be done if it happens
131                    return;
132                } else {
133                    source_map.lookup_char_pos(primary_span.lo())
134                }
135            } else {
136                // FIXME(#59346): Not sure when this is the case and what
137                // should be done if it happens
138                return;
139            };
140            let mut annotated_files = FileWithAnnotatedLines::collect_annotations(self, args, msp);
141            if let Ok(pos) =
142                annotated_files.binary_search_by(|x| x.file.name.cmp(&primary_lo.file.name))
143            {
144                annotated_files.swap(0, pos);
145            }
146            // owned: file name, line source, line index, annotations
147            type Owned = (String, String, usize, Vec<crate::snippet::Annotation>);
148            let annotated_files: Vec<Owned> = annotated_files
149                .into_iter()
150                .flat_map(|annotated_file| {
151                    let file = annotated_file.file;
152                    annotated_file
153                        .lines
154                        .into_iter()
155                        .map(|line| {
156                            // Ensure the source file is present before we try
157                            // to load a string from it.
158                            // FIXME(#115869): support -Z ignore-directory-in-diagnostics-source-blocks
159                            source_map.ensure_source_file_source_present(&file);
160                            (
161                                format!("{}", source_map.filename_for_diagnostics(&file.name)),
162                                source_string(Arc::clone(&file), &line),
163                                line.line_index,
164                                line.annotations,
165                            )
166                        })
167                        .collect::<Vec<Owned>>()
168                })
169                .collect();
170            let code = code.map(|code| code.to_string());
171
172            let snippets =
173                annotated_files.iter().map(|(file_name, source, line_index, annotations)| {
174                    Snippet::source(source)
175                        .line_start(*line_index)
176                        .origin(file_name)
177                        // FIXME(#59346): Not really sure when `fold` should be true or false
178                        .fold(false)
179                        .annotations(annotations.iter().map(|annotation| {
180                            annotation_level_for_level(*level)
181                                .span(annotation.start_col.display..annotation.end_col.display)
182                                .label(annotation.label.as_deref().unwrap_or_default())
183                        }))
184                });
185            let mut message = annotation_level_for_level(*level).title(&message).snippets(snippets);
186            if let Some(code) = code.as_deref() {
187                message = message.id(code)
188            }
189            // FIXME(#59346): Figure out if we can _always_ print to stderr or not.
190            // `emitter.rs` has the `Destination` enum that lists various possible output
191            // destinations.
192            let renderer = Renderer::plain().anonymized_line_numbers(self.ui_testing);
193            eprintln!("{}", renderer.render(message))
194        }
195        // FIXME(#59346): Is it ok to return None if there's no source_map?
196    }
197}