rust/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs

212 lines
8.0 KiB
Rust

//! 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 annotate_snippets::{Renderer, Snippet};
use rustc_data_structures::sync::Lrc;
use rustc_error_messages::FluentArgs;
use rustc_span::SourceFile;
use rustc_span::source_map::SourceMap;
use crate::emitter::FileWithAnnotatedLines;
use crate::snippet::Line;
use crate::translation::{Translate, to_fluent_args};
use crate::{
CodeSuggestion, DiagInner, DiagMessage, Emitter, ErrCode, FluentBundle, LazyFallbackBundle,
Level, MultiSpan, Style, Subdiag,
};
/// Generates diagnostics using annotate-snippet
pub struct AnnotateSnippetEmitter {
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 Translate for AnnotateSnippetEmitter {
fn fluent_bundle(&self) -> Option<&FluentBundle> {
self.fluent_bundle.as_deref()
}
fn fallback_fluent_bundle(&self) -> &FluentBundle {
&self.fallback_bundle
}
}
impl Emitter for AnnotateSnippetEmitter {
/// The entry point for the diagnostics generation
fn emit_diagnostic(&mut self, mut diag: DiagInner) {
let fluent_args = to_fluent_args(diag.args.iter());
let mut suggestions = diag.suggestions.unwrap_tag();
self.primary_span_formatted(&mut diag.span, &mut suggestions, &fluent_args);
self.fix_multispans_in_extern_macros_and_render_macro_backtrace(
&mut diag.span,
&mut diag.children,
&diag.level,
self.macro_backtrace,
);
self.emit_messages_default(
&diag.level,
&diag.messages,
&fluent_args,
&diag.code,
&diag.span,
&diag.children,
&suggestions,
);
}
fn source_map(&self) -> Option<&SourceMap> {
self.source_map.as_deref()
}
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 [`crate::Level`] to [`annotate_snippets::Level`]
fn annotation_level_for_level(level: Level) -> annotate_snippets::Level {
match level {
Level::Bug | Level::Fatal | Level::Error | Level::DelayedBug => {
annotate_snippets::Level::Error
}
Level::ForceWarning(_) | Level::Warning => annotate_snippets::Level::Warning,
Level::Note | Level::OnceNote => annotate_snippets::Level::Note,
Level::Help | Level::OnceHelp => annotate_snippets::Level::Help,
// FIXME(#59346): Not sure how to map this level
Level::FailureNote => annotate_snippets::Level::Error,
Level::Allow => panic!("Should not call with Allow"),
Level::Expect(_) => panic!("Should not call with Expect"),
}
}
impl AnnotateSnippetEmitter {
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: &[(DiagMessage, Style)],
args: &FluentArgs<'_>,
code: &Option<ErrCode>,
msp: &MultiSpan,
_children: &[Subdiag],
_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(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: file name, line source, line index, annotations
type Owned = (String, String, usize, Vec<crate::snippet::Annotation>);
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| {
// Ensure the source file is present before we try
// to load a string from it.
// FIXME(#115869): support -Z ignore-directory-in-diagnostics-source-blocks
source_map.ensure_source_file_source_present(&file);
(
format!("{}", source_map.filename_for_diagnostics(&file.name)),
source_string(Lrc::clone(&file), &line),
line.line_index,
line.annotations,
)
})
.collect::<Vec<Owned>>()
})
.collect();
let code = code.map(|code| code.to_string());
let snippets =
annotated_files.iter().map(|(file_name, source, line_index, annotations)| {
Snippet::source(source)
.line_start(*line_index)
.origin(file_name)
// FIXME(#59346): Not really sure when `fold` should be true or false
.fold(false)
.annotations(annotations.iter().map(|annotation| {
annotation_level_for_level(*level)
.span(annotation.start_col.display..annotation.end_col.display)
.label(annotation.label.as_deref().unwrap_or_default())
}))
});
let mut message = annotation_level_for_level(*level).title(&message).snippets(snippets);
if let Some(code) = code.as_deref() {
message = message.id(code)
}
// 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.
let renderer = Renderer::plain().anonymized_line_numbers(self.ui_testing);
eprintln!("{}", renderer.render(message))
}
// FIXME(#59346): Is it ok to return None if there's no source_map?
}
}