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