diff options
Diffstat (limited to 'compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs')
-rw-r--r-- | compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs | 215 |
1 files changed, 215 insertions, 0 deletions
diff --git a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs new file mode 100644 index 000000000..0fcd61d1e --- /dev/null +++ b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs @@ -0,0 +1,215 @@ +//! Emit diagnostics using the `annotate-snippets` library +//! +//! This is the equivalent of `./emitter.rs` but making use of the +//! [`annotate-snippets`][annotate_snippets] library instead of building the output ourselves. +//! +//! [annotate_snippets]: https://docs.rs/crate/annotate-snippets/ + +use crate::emitter::FileWithAnnotatedLines; +use crate::snippet::Line; +use crate::{ + CodeSuggestion, Diagnostic, DiagnosticId, DiagnosticMessage, Emitter, FluentBundle, + LazyFallbackBundle, Level, MultiSpan, Style, SubDiagnostic, +}; +use annotate_snippets::display_list::{DisplayList, FormatOptions}; +use annotate_snippets::snippet::*; +use rustc_data_structures::sync::Lrc; +use rustc_error_messages::FluentArgs; +use rustc_span::source_map::SourceMap; +use rustc_span::SourceFile; + +/// Generates diagnostics using annotate-snippet +pub struct AnnotateSnippetEmitterWriter { + source_map: Option<Lrc<SourceMap>>, + fluent_bundle: Option<Lrc<FluentBundle>>, + fallback_bundle: LazyFallbackBundle, + + /// If true, hides the longer explanation text + short_message: bool, + /// If true, will normalize line numbers with `LL` to prevent noise in UI test diffs. + ui_testing: bool, + + macro_backtrace: bool, +} + +impl Emitter for AnnotateSnippetEmitterWriter { + /// The entry point for the diagnostics generation + fn emit_diagnostic(&mut self, diag: &Diagnostic) { + let fluent_args = self.to_fluent_args(diag.args()); + + let mut children = diag.children.clone(); + let (mut primary_span, suggestions) = self.primary_span_formatted(&diag, &fluent_args); + + self.fix_multispans_in_extern_macros_and_render_macro_backtrace( + &self.source_map, + &mut primary_span, + &mut children, + &diag.level, + self.macro_backtrace, + ); + + self.emit_messages_default( + &diag.level, + &diag.message, + &fluent_args, + &diag.code, + &primary_span, + &children, + &suggestions, + ); + } + + fn source_map(&self) -> Option<&Lrc<SourceMap>> { + self.source_map.as_ref() + } + + fn fluent_bundle(&self) -> Option<&Lrc<FluentBundle>> { + self.fluent_bundle.as_ref() + } + + fn fallback_fluent_bundle(&self) -> &FluentBundle { + &**self.fallback_bundle + } + + fn should_show_explain(&self) -> bool { + !self.short_message + } +} + +/// Provides the source string for the given `line` of `file` +fn source_string(file: Lrc<SourceFile>, line: &Line) -> String { + file.get_line(line.line_index - 1).map(|a| a.to_string()).unwrap_or_default() +} + +/// Maps `Diagnostic::Level` to `snippet::AnnotationType` +fn annotation_type_for_level(level: Level) -> AnnotationType { + match level { + Level::Bug | Level::DelayedBug | Level::Fatal | Level::Error { .. } => { + AnnotationType::Error + } + Level::Warning(_) => AnnotationType::Warning, + Level::Note | Level::OnceNote => AnnotationType::Note, + Level::Help => AnnotationType::Help, + // FIXME(#59346): Not sure how to map this level + Level::FailureNote => AnnotationType::Error, + Level::Allow => panic!("Should not call with Allow"), + Level::Expect(_) => panic!("Should not call with Expect"), + } +} + +impl AnnotateSnippetEmitterWriter { + pub fn new( + source_map: Option<Lrc<SourceMap>>, + fluent_bundle: Option<Lrc<FluentBundle>>, + fallback_bundle: LazyFallbackBundle, + short_message: bool, + macro_backtrace: bool, + ) -> Self { + Self { + source_map, + fluent_bundle, + fallback_bundle, + short_message, + ui_testing: false, + macro_backtrace, + } + } + + /// Allows to modify `Self` to enable or disable the `ui_testing` flag. + /// + /// If this is set to true, line numbers will be normalized as `LL` in the output. + pub fn ui_testing(mut self, ui_testing: bool) -> Self { + self.ui_testing = ui_testing; + self + } + + fn emit_messages_default( + &mut self, + level: &Level, + messages: &[(DiagnosticMessage, Style)], + args: &FluentArgs<'_>, + code: &Option<DiagnosticId>, + msp: &MultiSpan, + _children: &[SubDiagnostic], + _suggestions: &[CodeSuggestion], + ) { + let message = self.translate_messages(messages, args); + if let Some(source_map) = &self.source_map { + // Make sure our primary file comes first + let primary_lo = if let Some(ref primary_span) = msp.primary_span().as_ref() { + if primary_span.is_dummy() { + // FIXME(#59346): Not sure when this is the case and what + // should be done if it happens + return; + } else { + source_map.lookup_char_pos(primary_span.lo()) + } + } else { + // FIXME(#59346): Not sure when this is the case and what + // should be done if it happens + return; + }; + let mut annotated_files = FileWithAnnotatedLines::collect_annotations(self, args, msp); + if let Ok(pos) = + annotated_files.binary_search_by(|x| x.file.name.cmp(&primary_lo.file.name)) + { + annotated_files.swap(0, pos); + } + // owned: line source, line index, annotations + type Owned = (String, usize, Vec<crate::snippet::Annotation>); + let filename = source_map.filename_for_diagnostics(&primary_lo.file.name); + let origin = filename.to_string_lossy(); + let annotated_files: Vec<Owned> = annotated_files + .into_iter() + .flat_map(|annotated_file| { + let file = annotated_file.file; + annotated_file + .lines + .into_iter() + .map(|line| { + (source_string(file.clone(), &line), line.line_index, line.annotations) + }) + .collect::<Vec<Owned>>() + }) + .collect(); + let snippet = Snippet { + title: Some(Annotation { + label: Some(&message), + id: code.as_ref().map(|c| match c { + DiagnosticId::Error(val) | DiagnosticId::Lint { name: val, .. } => { + val.as_str() + } + }), + annotation_type: annotation_type_for_level(*level), + }), + footer: vec![], + opt: FormatOptions { color: true, anonymized_line_numbers: self.ui_testing }, + slices: annotated_files + .iter() + .map(|(source, line_index, annotations)| { + Slice { + source, + line_start: *line_index, + origin: Some(&origin), + // FIXME(#59346): Not really sure when `fold` should be true or false + fold: false, + annotations: annotations + .iter() + .map(|annotation| SourceAnnotation { + range: (annotation.start_col, annotation.end_col), + label: annotation.label.as_deref().unwrap_or_default(), + annotation_type: annotation_type_for_level(*level), + }) + .collect(), + } + }) + .collect(), + }; + // FIXME(#59346): Figure out if we can _always_ print to stderr or not. + // `emitter.rs` has the `Destination` enum that lists various possible output + // destinations. + eprintln!("{}", DisplayList::from(snippet)) + } + // FIXME(#59346): Is it ok to return None if there's no source_map? + } +} |