diff --git a/Cargo.lock b/Cargo.lock index 6d823c5b5a596..58a2acb2b3beb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,6 +68,14 @@ dependencies = [ "yansi-term", ] +[[package]] +name = "annotate-snippets" +version = "0.11.5" +dependencies = [ + "anstyle", + "unicode-width 0.2.1", +] + [[package]] name = "annotate-snippets" version = "0.11.5" @@ -3751,6 +3759,8 @@ name = "rustc_errors" version = "0.0.0" dependencies = [ "annotate-snippets 0.11.5", + "anstream", + "anstyle", "derive_setters", "rustc_abi", "rustc_ast", @@ -3820,7 +3830,7 @@ dependencies = [ name = "rustc_fluent_macro" version = "0.0.0" dependencies = [ - "annotate-snippets 0.11.5", + "annotate-snippets 0.11.5 (registry+https://github.com/rust-lang/crates.io-index)", "fluent-bundle", "fluent-syntax", "proc-macro2", @@ -4324,7 +4334,6 @@ dependencies = [ "rustc_macros", "rustc_session", "rustc_span", - "termcolor", "thin-vec", "tracing", "unicode-normalization", @@ -5709,7 +5718,7 @@ version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1211b1111c752c73b33073d2958072be08825fd97c9ab4d83444da361a06634b" dependencies = [ - "annotate-snippets 0.11.5", + "annotate-snippets 0.11.5 (registry+https://github.com/rust-lang/crates.io-index)", "anyhow", "bstr", "cargo-platform", diff --git a/compiler/rustc_errors/Cargo.toml b/compiler/rustc_errors/Cargo.toml index c4181a62a35d0..63a76fffb3717 100644 --- a/compiler/rustc_errors/Cargo.toml +++ b/compiler/rustc_errors/Cargo.toml @@ -5,7 +5,9 @@ edition = "2024" [dependencies] # tidy-alphabetical-start -annotate-snippets = "0.11" +annotate-snippets = { path = "../../../annotate-snippets-rs" } +anstream = "0.6.19" +anstyle = "1.0.11" derive_setters = "0.1.6" rustc_abi = { path = "../rustc_abi" } rustc_ast = { path = "../rustc_ast" } @@ -25,7 +27,7 @@ rustc_serialize = { path = "../rustc_serialize" } rustc_span = { path = "../rustc_span" } rustc_target = { path = "../rustc_target" } rustc_type_ir = { path = "../rustc_type_ir" } -serde = { version = "1.0.125", features = [ "derive" ] } +serde = { version = "1.0.125", features = ["derive"] } serde_json = "1.0.59" termcolor = "1.2.0" termize = "0.2" diff --git a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs index 2eb3c23259ffa..454b20494d1ee 100644 --- a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs +++ b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs @@ -5,32 +5,71 @@ //! //! [annotate_snippets]: https://docs.rs/crate/annotate-snippets/ +use std::borrow::Cow; +use std::error::Report; +use std::fmt::Debug; +use std::io; use std::sync::Arc; -use annotate_snippets::{Renderer, Snippet}; -use rustc_error_messages::FluentArgs; -use rustc_span::SourceFile; +use annotate_snippets::{AnnotationKind, Group, Padding, Patch, Renderer, Snippet, debug_groups}; +use derive_setters::Setters; +use rustc_data_structures::sync::IntoDynSyncSend; +use rustc_error_messages::{FluentArgs, SpanLabel}; +use rustc_lint_defs::pluralize; use rustc_span::source_map::SourceMap; +use rustc_span::{Pos, SourceFile, Span}; +use tracing::{debug, info}; -use crate::emitter::FileWithAnnotatedLines; +use crate::emitter::{ + Destination, MAX_SUGGESTIONS, OutputTheme, is_case_difference, is_different, + normalize_whitespace, should_show_source_code, +}; use crate::registry::Registry; -use crate::snippet::Line; use crate::translation::{Translator, to_fluent_args}; use crate::{ CodeSuggestion, DiagInner, DiagMessage, Emitter, ErrCode, Level, MultiSpan, Style, Subdiag, + SuggestionStyle, TerminalUrl, }; +/// Default column width, used in tests and when terminal dimensions cannot be determined. +const DEFAULT_COLUMN_WIDTH: usize = 140; + /// Generates diagnostics using annotate-snippet +#[derive(Setters)] pub struct AnnotateSnippetEmitter { - source_map: Option>, - translator: Translator, - /// If true, hides the longer explanation text + #[setters(skip)] + dst: IntoDynSyncSend, + sm: Option>, + #[setters(skip)] + translator: Translator, short_message: bool, - /// If true, will normalize line numbers with `LL` to prevent noise in UI test diffs. ui_testing: bool, + ignored_directories_in_source_blocks: Vec, + diagnostic_width: Option, macro_backtrace: bool, + track_diagnostics: bool, + terminal_url: TerminalUrl, + theme: OutputTheme, +} + +impl Debug for AnnotateSnippetEmitter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AnnotateSnippetEmitter") + .field("short_message", &self.short_message) + .field("ui_testing", &self.ui_testing) + .field( + "ignored_directories_in_source_blocks", + &self.ignored_directories_in_source_blocks, + ) + .field("diagnostic_width", &self.diagnostic_width) + .field("macro_backtrace", &self.macro_backtrace) + .field("track_diagnostics", &self.track_diagnostics) + .field("terminal_url", &self.terminal_url) + .field("theme", &self.theme) + .finish() + } } impl Emitter for AnnotateSnippetEmitter { @@ -38,6 +77,10 @@ impl Emitter for AnnotateSnippetEmitter { fn emit_diagnostic(&mut self, mut diag: DiagInner, _registry: &Registry) { let fluent_args = to_fluent_args(diag.args.iter()); + if self.track_diagnostics && diag.span.has_primary_spans() && !diag.span.is_dummy() { + diag.children.insert(0, diag.emitted_at_sub_diag()); + } + let mut suggestions = diag.suggestions.unwrap_tag(); self.primary_span_formatted(&mut diag.span, &mut suggestions, &fluent_args); @@ -60,7 +103,7 @@ impl Emitter for AnnotateSnippetEmitter { } fn source_map(&self) -> Option<&SourceMap> { - self.source_map.as_deref() + self.sm.as_deref() } fn should_show_explain(&self) -> bool { @@ -70,128 +113,631 @@ impl Emitter for AnnotateSnippetEmitter { fn translator(&self) -> &Translator { &self.translator } -} -/// Provides the source string for the given `line` of `file` -fn source_string(file: Arc, line: &Line) -> String { - file.get_line(line.line_index - 1).map(|a| a.to_string()).unwrap_or_default() + fn supports_color(&self) -> bool { + self.dst.supports_color() + } } -/// Maps [`crate::Level`] to [`annotate_snippets::Level`] -fn annotation_level_for_level(level: Level) -> annotate_snippets::Level { +fn annotation_level_for_level(level: Level) -> annotate_snippets::level::Level<'static> { match level { - Level::Bug | Level::Fatal | Level::Error | Level::DelayedBug => { - annotate_snippets::Level::Error + Level::Bug | Level::DelayedBug => { + annotate_snippets::Level::ERROR.with_name("error: internal compiler 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, + Level::Fatal | Level::Error => 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::FailureNote => annotate_snippets::Level::NOTE.no_name(), Level::Allow => panic!("Should not call with Allow"), Level::Expect => panic!("Should not call with Expect"), } } impl AnnotateSnippetEmitter { - pub fn new( - source_map: Option>, - translator: Translator, - short_message: bool, - macro_backtrace: bool, - ) -> Self { - Self { source_map, translator, 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 + pub fn new(dst: Destination, translator: Translator) -> Self { + Self { + dst: IntoDynSyncSend(dst), + sm: None, + translator, + short_message: false, + ui_testing: false, + ignored_directories_in_source_blocks: Vec::new(), + diagnostic_width: None, + macro_backtrace: false, + track_diagnostics: false, + terminal_url: TerminalUrl::No, + theme: OutputTheme::Ascii, + } } fn emit_messages_default( &mut self, level: &Level, - messages: &[(DiagMessage, Style)], + msgs: &[(DiagMessage, Style)], args: &FluentArgs<'_>, code: &Option, msp: &MultiSpan, - _children: &[Subdiag], - _suggestions: &[CodeSuggestion], + children: &[Subdiag], + suggestions: &[CodeSuggestion], ) { - let message = self.translator.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()) + let width = if let Some(width) = self.diagnostic_width { + width + } else if self.ui_testing { + DEFAULT_COLUMN_WIDTH + } else { + termize::dimensions().map(|(w, _)| w).unwrap_or(DEFAULT_COLUMN_WIDTH) + }; + let theme = match self.theme { + OutputTheme::Ascii => annotate_snippets::renderer::OutputTheme::Ascii, + OutputTheme::Unicode => annotate_snippets::renderer::OutputTheme::Unicode, + }; + + let anonymized_line_numbers = self.ui_testing; + let renderer = + if self.dst.supports_color() { Renderer::styled() } else { Renderer::plain() } + .term_width(width) + .anonymized_line_numbers(anonymized_line_numbers) + .theme(theme) + .short_message(self.short_message); + + let as_level = annotation_level_for_level(*level); + + // If the destination supports color and at least one portion + // of the message is styled, we need to "pre-style" the message + let (message, is_pre_styled) = if self.dst.supports_color() + && msgs.iter().any(|(_, style)| style != &crate::Style::NoStyle) + { + ( + Cow::Owned( + msgs.iter() + .filter_map(|(m, style)| { + let text: String = self + .translator + .translate_message(m, args) + .map_err(Report::new) + .unwrap() + .to_string(); + let style = style.anstyle(*level); + if text.is_empty() { + None + } else { + Some(format!("{style}{text}{style:#}")) + } + }) + .collect::(), + ), + true, + ) + } else { + (self.translator.translate_messages(msgs, args), false) + }; + + let code = code.map(|c| { + if let TerminalUrl::Yes = self.terminal_url { + let path = "https://doc.rust-lang.org/error_codes"; + (c.to_string(), Some(format!("{path}/{c}.html"))) + } else { + (c.to_string(), None) + } + }); + let mut message = if is_pre_styled { + as_level.pre_styled_title(message) + } else { + as_level.title(message) + }; + + if let Some((code, url)) = &code { + message = message.id(code); + if let Some(url) = url { + message = message.id_url(url); + } + } + let mut groups = vec![]; + let mut group = Group::with_title(message); + + let mut file_ann = collect_annotations(self, args, msp); + // Make sure our primary file comes first + let primary_span = msp.primary_span().unwrap_or_default(); + let Some(sm) = self.sm.as_ref() else { + let children = children + .iter() + .map(|c| { + let msg = self.translator.translate_messages(&c.messages, args).to_string(); + let level = annotation_level_for_level(c.level); + (level, msg) + }) + .collect::>(); + if !children.is_empty() { + for (level, msg) in &children { + group = group.element(level.clone().title(msg)); } + } + groups.push(group); + if let Err(e) = emit_to_destination( + renderer.render(&groups), + level, + &mut self.dst, + self.short_message, + ) { + panic!("failed to emit error: {e}"); + } + return; + }; + + if !primary_span.is_dummy() { + let primary_lo = sm.lookup_char_pos(primary_span.lo()); + if let Ok(pos) = file_ann.binary_search_by(|x| x.file.name.cmp(&primary_lo.file.name)) { + file_ann.swap(0, pos); + } + + for file in &file_ann { + let filename = Cow::Owned( + sm.filename_for_diagnostics(&file.file.name).to_string_lossy().to_string(), + ); + + let bounding_span = + Span::with_root_ctxt(file.file.start_pos, file.file.end_position()); + let source = sm.span_to_snippet(bounding_span).unwrap_or_default(); + + if should_show_source_code( + &self.ignored_directories_in_source_blocks, + sm, + &file.file, + ) { + let offset_line = sm.doctest_offset_line(&file.file.name, 1); + let snippet = Snippet::source(Cow::Owned(source)) + .fold(true) + .line_start(offset_line) + .path(filename) + .annotations(file.annotations.iter().map(|h| { + let lo = sm.lookup_byte_offset(h.span.lo()); + let hi = sm.lookup_byte_offset(h.span.hi()); + let range = lo.pos.to_usize()..hi.pos.to_usize(); + let ann = h.kind.span(range); + if let Some(label) = &h.label { ann.label(label) } else { ann } + })); + group = group.element(snippet); + } else if !self.short_message { + for (i, h) in file.annotations.iter().enumerate() { + if i == 0 || h.label.is_some() { + let lo = sm.lookup_char_pos(h.span.lo()); + + let origin = annotate_snippets::Origin::path(filename.clone()) + .line(sm.doctest_offset_line(&file.file.name, lo.line)) + .char_column(lo.col_display) + .primary(i == 0); + group = group.element(origin); + if let Some(label) = h.label.as_ref() { + if !label.is_empty() { + group = group.element(Padding); + group = + group.element(annotate_snippets::Level::NOTE.title(label)); + if i == file.annotations.len() - 1 + && (!children.is_empty() || !suggestions.is_empty()) + { + group = group.element(Padding); + } + } + } + } + } + } + } + } + + for c in children { + info!("{c:?}"); + let level = annotation_level_for_level(c.level); + + // If the destination supports color and at least one portion + // of the message is styled, we need to "pre-style" the message + let (msg, is_pre_styled) = if self.dst.supports_color() + && c.messages.iter().any(|(_, style)| style != &crate::Style::NoStyle) + { + ( + Cow::Owned( + c.messages + .iter() + .filter_map(|(m, style)| { + let text: String = self + .translator + .translate_message(m, args) + .map_err(Report::new) + .unwrap() + .to_string(); + let style = style.anstyle(c.level); + if text.is_empty() { + None + } else { + Some(format!("{style}{text}{style:#}")) + } + }) + .collect::(), + ), + true, + ) } else { - // FIXME(#59346): Not sure when this is the case and what - // should be done if it happens - return; + (self.translator.translate_messages(&c.messages, args), false) }; - 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); + + if !c.span.has_primary_spans() && !c.span.has_span_labels() { + group = group.element(level.clone().message(msg)); + continue; + } + + let title = if is_pre_styled { + level.clone().pre_styled_title(msg) + } else { + level.clone().title(msg) + }; + let mut temp_group = Group::with_title(title); + std::mem::swap(&mut group, &mut temp_group); + groups.push(temp_group); + + let mut file_ann = collect_annotations(self, args, &c.span); + let primary_span = c.span.primary_span().unwrap_or_default(); + if !primary_span.is_dummy() { + let primary_lo = sm.lookup_char_pos(primary_span.lo()); + if let Ok(pos) = + file_ann.binary_search_by(|x| x.file.name.cmp(&primary_lo.file.name)) + { + file_ann.swap(0, pos); + } + } + + for file in file_ann { + let filename = Cow::Owned( + sm.filename_for_diagnostics(&file.file.name).to_string_lossy().to_string(), + ); + let bounding_span = + Span::with_root_ctxt(file.file.start_pos, file.file.end_position()); + let source = sm.span_to_snippet(bounding_span).unwrap_or_default(); + + if should_show_source_code( + &self.ignored_directories_in_source_blocks, + sm, + &file.file, + ) { + let offset_line = sm.doctest_offset_line(&file.file.name, 1); + + group = group.element( + Snippet::source(Cow::Owned(source)) + .fold(true) + .line_start(offset_line) + .path(filename) + .annotations(file.annotations.into_iter().map(|h| { + let lo = sm.lookup_byte_offset(h.span.lo()); + let hi = sm.lookup_byte_offset(h.span.hi()); + + let range = lo.pos.to_usize()..hi.pos.to_usize(); + let ann = h.kind.span(range); + if let Some(label) = h.label { ann.label(label) } else { ann } + })), + ); + } else if !self.short_message { + let mut line_tracker = vec![]; + for (i, h) in file.annotations.into_iter().enumerate() { + let lo = sm.lookup_char_pos(h.span.lo()); + let hi = sm.lookup_char_pos(h.span.hi()); + if i == 0 + || (h.label.is_some() + && (!line_tracker.contains(&lo.line) + || !line_tracker.contains(&hi.line))) + { + if !line_tracker.contains(&lo.line) { + line_tracker.push(lo.line); + let origin = annotate_snippets::Origin::path(filename.clone()) + .line(sm.doctest_offset_line(&file.file.name, lo.line)) + .char_column(lo.col_display) + .primary(i == 0); + group = group.element(origin); + if let Some(label) = h.label.clone() { + if !label.is_empty() && lo.line == hi.line { + group = group.element(Padding); + group = group + .element(annotate_snippets::Level::NOTE.title(label)); + } + } + } + + if let Some(label) = h.label { + if !label.is_empty() { + if !line_tracker.contains(&hi.line) { + line_tracker.push(hi.line); + let origin = + annotate_snippets::Origin::path(filename.clone()) + .line( + sm.doctest_offset_line( + &file.file.name, + hi.line, + ), + ) + .char_column(hi.col_display) + .primary(false); + + group = group.element(origin); + group = group.element(Padding); + group = group + .element(annotate_snippets::Level::NOTE.title(label)); + } else if lo.line != hi.line { + group = group.element(Padding); + group = group + .element(annotate_snippets::Level::NOTE.title(label)); + } + } + } + } else if let Some(label) = h.label { + if !label.is_empty() { + group = group.element(Padding); + group = group.element(annotate_snippets::Level::NOTE.title(label)); + } + } + } + } } - // owned: file name, line source, line index, annotations - type Owned = (String, String, usize, Vec); - let annotated_files: Vec = annotated_files - .into_iter() - .flat_map(|annotated_file| { - let file = annotated_file.file; - annotated_file - .lines + } + + for suggestion in suggestions { + match suggestion.style { + SuggestionStyle::CompletelyHidden => { + // do not display this suggestion, it is meant only for tools + } + SuggestionStyle::HideCodeAlways => { + let msg = self + .translator + .translate_messages(&[(suggestion.msg.to_owned(), Style::HeaderMsg)], args); + group = group.element(annotate_snippets::Level::HELP.title(msg)); + } + SuggestionStyle::HideCodeInline + | SuggestionStyle::ShowCode + | SuggestionStyle::ShowAlways => { + let substitutions = suggestion + .substitutions + .iter() + .filter(|subst| { + // Suggestions coming from macros can have malformed spans. This is a heavy + // handed approach to avoid ICEs by ignoring the suggestion outright. + let invalid = + subst.parts.iter().any(|item| sm.is_valid_span(item.span).is_err()); + if invalid { + debug!("suggestion contains an invalid span: {:?}", subst); + } + + let Some(item_span) = subst.parts.first() else { + return false; + }; + let file = sm.lookup_source_file(item_span.span.lo()); + !invalid + && should_show_source_code( + &self.ignored_directories_in_source_blocks, + sm, + &file, + ) + }) + .cloned() + .collect::>(); + if substitutions.is_empty() { + continue; + } + let mut msg = self + .translator + .translate_message(&suggestion.msg, args) + .map_err(Report::new) + .unwrap() + .to_string(); + let lo = substitutions + .iter() + .find_map(|sub| sub.parts.first().map(|p| p.span.lo())) + .unwrap(); + let file = sm.lookup_source_file(lo); + if !sm.ensure_source_file_source_present(&file) { + continue; + } + + let filename = + sm.filename_for_diagnostics(&file.name).to_string_lossy().to_string(); + + let other_suggestions = substitutions.len().saturating_sub(MAX_SUGGESTIONS); + let subs = substitutions + .clone() .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(Arc::clone(&file), &line), - line.line_index, - line.annotations, - ) + .take(MAX_SUGGESTIONS) + .filter_map(|sub| { + if sub.parts.iter().any(|p| is_case_difference(sm, &p.snippet, p.span)) + { + msg.push_str(" (notice the capitalization difference)"); + } + + let parts = sub + .parts + .into_iter() + .filter_map(|p| { + if is_different(sm, &p.snippet, p.span) { + Some((p.span, p.snippet)) + } else { + None + } + }) + .collect::>(); + if parts.is_empty() { + None + } else { + let lo = parts.iter().map(|(span, _)| span.lo()).min()?; + let lo_line = file.lookup_line(file.relative_position(lo))?; + let lo = file.line_bounds(lo_line).start; + + let hi = parts.iter().map(|(span, _)| span.hi()).max()?; + let hi_line = file.lookup_line(file.relative_position(hi))?; + let hi = file.line_bounds(hi_line).end; + + Some((Span::with_root_ctxt(lo, hi), lo_line, parts)) + } }) - .collect::>() - }) - .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) + .collect::>(); + + if !subs.is_empty() { + let mut temp_group = + Group::with_title(annotate_snippets::Level::HELP.title(msg)); + std::mem::swap(&mut group, &mut temp_group); + groups.push(temp_group); + + group = group.elements(subs.into_iter().filter_map( + |(bounding_span, lo_line, parts)| { + // We can't splice anything if the source is unavailable. + if let Ok(snippet) = sm.span_to_snippet(bounding_span) { + let adj_lo = bounding_span.lo().to_usize(); + Some( + Snippet::source(snippet) + .fold(true) + .line_start(lo_line + 1) + .path(filename.clone()) + .patches(parts.into_iter().map( + |(span, replacement)| { + let lo = + span.lo().to_usize().saturating_sub(adj_lo); + let hi = + span.hi().to_usize().saturating_sub(adj_lo); + + Patch::new(lo..hi, replacement) + }, + )), + ) + } else { + None + } + }, + )); + if other_suggestions > 0 { + group = group.element( + annotate_snippets::Level::NOTE.no_name().message(format!( + "and {} other candidate{}", + other_suggestions, + pluralize!(other_suggestions) + )), + ); + } + } + } + } + } + + // TODO: This hack should be removed once annotate_snippets is the + // default emitter. + let suggestions_expected = suggestions + .iter() + .filter(|s| { + matches!( + s.style, + SuggestionStyle::HideCodeInline + | SuggestionStyle::ShowCode + | SuggestionStyle::ShowAlways + ) + }) + .count(); + if suggestions_expected > 0 && groups.is_empty() { + group = group.element(Padding); + } + + if !group.is_empty() { + groups.push(group); + } + //info!("{groups:#?}"); + info!("{}", debug_groups(&groups, &renderer).unwrap()); + if let Err(e) = + emit_to_destination(renderer.render(&groups), level, &mut self.dst, self.short_message) + { + panic!("failed to emit error: {e}"); + } + } +} + +#[derive(Debug)] +struct FileWithAnnotations { + file: Arc, + annotations: Vec, +} + +#[derive(Debug)] +struct Annotation { + kind: AnnotationKind, + span: Span, + label: Option, +} + +fn collect_annotations( + emitter: &dyn Emitter, + args: &FluentArgs<'_>, + msp: &MultiSpan, +) -> Vec { + fn add_to_file( + kind: AnnotationKind, + span: Span, + label: Option, + file: Arc, + file_vec: &mut Vec, + ) { + for slot in file_vec.iter_mut() { + // Look through each of our files for the one we're adding to + if slot.file.name == file.name { + slot.annotations.push(Annotation { kind, span, label }); + return; + } + } + + file_vec.push(FileWithAnnotations { + file, + annotations: vec![Annotation { kind, span, label }], + }); + } + + let mut output = vec![]; + + if let Some(sm) = emitter.source_map() { + for SpanLabel { span, is_primary, label } in msp.span_labels() { + // If we don't have a useful span, pick the primary span if that exists. + // Worst case we'll just print an error at the top of the main file. + let span = match (span.is_dummy(), msp.primary_span()) { + (_, None) | (false, _) => span, + (true, Some(span)) => span, + }; + let file = sm.lookup_source_file(span.lo()); + + let kind = if is_primary { AnnotationKind::Primary } else { AnnotationKind::Context }; + + let label = label.as_ref().map(|m| { + normalize_whitespace( + &emitter.translator().translate_message(m, args).map_err(Report::new).unwrap(), + ) + }); + + add_to_file(kind, span, label, file, &mut output); + } + } + output +} + +fn emit_to_destination( + rendered: String, + lvl: &Level, + dst: &mut Destination, + short_message: bool, +) -> io::Result<()> { + use crate::lock; + let _buffer_lock = lock::acquire_global_lock("rustc_errors"); + write!(dst, "{rendered}")?; + if !short_message && !lvl.is_failure_note() { + writeln!(dst)?; + } + dst.flush()?; + match writeln!(dst) { + Err(e) => panic!("failed to emit error: {e}"), + _ => { + if let Err(e) = dst.flush() { + panic!("failed to emit error: {e}") } - // 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? } + Ok(()) } diff --git a/compiler/rustc_errors/src/emitter.rs b/compiler/rustc_errors/src/emitter.rs index 2f398cea926cd..e3869ed69a8c2 100644 --- a/compiler/rustc_errors/src/emitter.rs +++ b/compiler/rustc_errors/src/emitter.rs @@ -16,6 +16,7 @@ use std::iter; use std::path::Path; use std::sync::Arc; +use anstream::stream::{AsLockedWrite, RawStream}; use derive_setters::Setters; use rustc_data_structures::fx::{FxHashMap, FxIndexMap, FxIndexSet}; use rustc_data_structures::sync::{DynSend, IntoDynSyncSend}; @@ -25,9 +26,9 @@ use rustc_lint_defs::pluralize; use rustc_span::hygiene::{ExpnKind, MacroKind}; use rustc_span::source_map::SourceMap; use rustc_span::{FileLines, FileName, SourceFile, Span, char_width, str_width}; -use termcolor::{Buffer, BufferWriter, Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; -use tracing::{debug, instrument, trace, warn}; +use tracing::{debug, info, instrument, trace, warn}; +use crate::annotate_snippet_emitter_writer::AnnotateSnippetEmitter; use crate::registry::Registry; use crate::snippet::{ Annotation, AnnotationColumn, AnnotationType, Line, MultilineAnnotation, Style, StyledString, @@ -598,18 +599,18 @@ pub enum ColorConfig { } impl ColorConfig { - pub fn to_color_choice(self) -> ColorChoice { + pub fn to_color_choice(self) -> anstream::ColorChoice { match self { ColorConfig::Always => { if io::stderr().is_terminal() { - ColorChoice::Always + anstream::ColorChoice::Always } else { - ColorChoice::AlwaysAnsi + anstream::ColorChoice::AlwaysAnsi } } - ColorConfig::Never => ColorChoice::Never, - ColorConfig::Auto if io::stderr().is_terminal() => ColorChoice::Auto, - ColorConfig::Auto => ColorChoice::Never, + ColorConfig::Never => anstream::ColorChoice::Never, + ColorConfig::Auto if io::stderr().is_terminal() => anstream::ColorChoice::Auto, + ColorConfig::Auto => anstream::ColorChoice::Never, } } } @@ -647,20 +648,8 @@ pub(crate) struct FileWithAnnotatedLines { } impl HumanEmitter { - pub fn new(dst: Destination, translator: Translator) -> HumanEmitter { - HumanEmitter { - dst: IntoDynSyncSend(dst), - sm: None, - translator, - short_message: false, - ui_testing: false, - ignored_directories_in_source_blocks: Vec::new(), - diagnostic_width: None, - macro_backtrace: false, - track_diagnostics: false, - terminal_url: TerminalUrl::No, - theme: OutputTheme::Ascii, - } + pub fn new(dst: Destination, translator: Translator) -> AnnotateSnippetEmitter { + AnnotateSnippetEmitter::new(dst, translator) } fn maybe_anonymized(&self, line_num: usize) -> Cow<'static, str> { @@ -713,7 +702,13 @@ impl HumanEmitter { Style::LineNumber, ); } - buffer.puts(line_offset, 0, &self.maybe_anonymized(line_index), Style::LineNumber); + let line_num = self.maybe_anonymized(line_index); + buffer.puts( + line_offset, + (width_offset - 3).saturating_sub(str_width(&line_num)), + &line_num, + Style::LineNumber, + ); self.draw_col_separator_no_space(buffer, line_offset, width_offset - 2); left @@ -744,13 +739,7 @@ impl HumanEmitter { // | vertical divider between the column number and the code // column number - if line.line_index == 0 { - return Vec::new(); - } - - let Some(source_string) = file.get_line(line.line_index - 1) else { - return Vec::new(); - }; + let source_string = file.get_line(line.line_index - 1).unwrap_or_default(); trace!(?source_string); let line_offset = buffer.num_lines(); @@ -1583,13 +1572,18 @@ impl HumanEmitter { } } let mut annotated_files = FileWithAnnotatedLines::collect_annotations(self, args, msp); - trace!("{annotated_files:#?}"); + info!("{annotated_files:#?}"); // Make sure our primary file comes first let primary_span = msp.primary_span().unwrap_or_default(); let (Some(sm), false) = (self.sm.as_ref(), primary_span.is_dummy()) else { // If we don't have span information, emit and exit - return emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message); + return emit_to_destination( + &buffer.render(level), + level, + &mut self.dst, + self.short_message, + ); }; let primary_lo = sm.lookup_char_pos(primary_span.lo()); if let Ok(pos) = @@ -1597,6 +1591,9 @@ impl HumanEmitter { { annotated_files.swap(0, pos); } + // An end column separator should be emitted when a file with with a + // source, is followed by one without a source + let mut col_sep_before_no_show_source = false; // Print out the annotate source lines that correspond with the error for annotated_file in annotated_files { @@ -1607,6 +1604,26 @@ impl HumanEmitter { &annotated_file.file, ) { if !self.short_message { + // Add an end column separator when a file without a source + // comes after one with a source + // ╭▸ $DIR/deriving-meta-unknown-trait.rs:1:10 + // │ + // LL │ #[derive(Eqr)] + // │ ━━━ + // ╰╴ (<- It prints *this* line) + // ╭▸ $SRC_DIR/core/src/cmp.rs:356:0 + // │ + // ╰╴note: similarly named derive macro `Eq` defined here + if col_sep_before_no_show_source { + let buffer_msg_line_offset = buffer.num_lines(); + self.draw_col_separator_end( + &mut buffer, + buffer_msg_line_offset, + max_line_num_len + 1, + ); + } + col_sep_before_no_show_source = false; + // We'll just print an unannotated message. for (annotation_id, line) in annotated_file.lines.iter().enumerate() { let mut annotations = line.annotations.clone(); @@ -1670,8 +1687,9 @@ impl HumanEmitter { } } continue; + } else { + col_sep_before_no_show_source = true; } - // print out the span location and spacer before we print the annotated source // to do this, we need to know if this span will be primary let is_primary = primary_lo.file.name == annotated_file.file.name; @@ -1681,13 +1699,17 @@ impl HumanEmitter { // remember where we are in the output buffer for easy reference let buffer_msg_line_offset = buffer.num_lines(); + let mut line = sm.doctest_offset_line(&loc.file.name, loc.line); + if line == 0 { + line += 1; + } buffer.prepend(buffer_msg_line_offset, self.file_start(), Style::LineNumber); buffer.append( buffer_msg_line_offset, &format!( "{}:{}:{}", sm.filename_for_diagnostics(&loc.file.name), - sm.doctest_offset_line(&loc.file.name, loc.line), + line, loc.col.0 + 1, ), Style::LineAndColumn, @@ -1976,11 +1998,11 @@ impl HumanEmitter { multilines.extend(&to_add); } } - trace!("buffer: {:#?}", buffer.render()); + trace!("buffer: {:#?}", buffer.render(level)); } // final step: take our styled buffer, render it, then output it - emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?; + emit_to_destination(&buffer.render(level), level, &mut self.dst, self.short_message)?; Ok(()) } @@ -1988,7 +2010,9 @@ impl HumanEmitter { fn column_width(&self, code_offset: usize) -> usize { if let Some(width) = self.diagnostic_width { width.saturating_sub(code_offset) - } else if self.ui_testing || cfg!(miri) { + } else if self.ui_testing { + DEFAULT_COLUMN_WIDTH.saturating_sub(code_offset) + } else if cfg!(miri) { DEFAULT_COLUMN_WIDTH } else { termize::dimensions() @@ -2451,15 +2475,16 @@ impl HumanEmitter { } } if other_suggestions > 0 { + self.draw_note_separator(&mut buffer, row_num, max_line_num_len + 1, false); let msg = format!( "and {} other candidate{}", other_suggestions, pluralize!(other_suggestions) ); - buffer.puts(row_num, max_line_num_len + 3, &msg, Style::NoStyle); + buffer.append(row_num, &msg, Style::NoStyle); } - emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?; + emit_to_destination(&buffer.render(level), level, &mut self.dst, self.short_message)?; Ok(()) } @@ -2509,7 +2534,7 @@ impl HumanEmitter { } } if let Err(e) = emit_to_destination( - &buffer.render(), + &buffer.render(level), level, &mut self.dst, self.short_message, @@ -3077,7 +3102,7 @@ impl FileWithAnnotatedLines { multiline_depth: 0, }); } - + info!("{msp:?}"); let mut output = vec![]; let mut multiline_annotations = vec![]; @@ -3311,7 +3336,7 @@ const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[ ('\u{2069}', "�"), ]; -fn normalize_whitespace(s: &str) -> String { +pub(crate) fn normalize_whitespace(s: &str) -> String { const { let mut i = 1; while i < OUTPUT_REPLACEMENTS.len() { @@ -3356,7 +3381,28 @@ fn overlaps(a1: &Annotation, a2: &Annotation, padding: usize) -> bool { ) } -fn emit_to_destination( +pub trait WriteColor: io::Write { + fn supports_color(&self) -> bool; +} + +impl WriteColor for anstream::AutoStream { + fn supports_color(&self) -> bool { + match self.current_choice() { + anstream::ColorChoice::Always + | anstream::ColorChoice::AlwaysAnsi + | anstream::ColorChoice::Auto => true, + anstream::ColorChoice::Never => false, + } + } +} + +impl WriteColor for std::io::Sink { + fn supports_color(&self) -> bool { + false + } +} + +pub(crate) fn emit_to_destination( rendered_buffer: &[Vec], lvl: &Level, dst: &mut Destination, @@ -3379,10 +3425,8 @@ fn emit_to_destination( let _buffer_lock = lock::acquire_global_lock("rustc_errors"); for (pos, line) in rendered_buffer.iter().enumerate() { for part in line { - let style = part.style.color_spec(*lvl); - dst.set_color(&style)?; - write!(dst, "{}", part.text)?; - dst.reset()?; + let style = part.style; + write!(dst, "{style}{}{style:#}", part.text)?; } if !short_message && (!lvl.is_failure_note() || pos != rendered_buffer.len() - 1) { writeln!(dst)?; @@ -3395,8 +3439,8 @@ fn emit_to_destination( pub type Destination = Box; struct Buffy { - buffer_writer: BufferWriter, - buffer: Buffer, + buffer_writer: anstream::Stderr, + buffer: Vec, } impl Write for Buffy { @@ -3405,7 +3449,7 @@ impl Write for Buffy { } fn flush(&mut self) -> io::Result<()> { - self.buffer_writer.print(&self.buffer)?; + self.buffer_writer.write_all(&self.buffer)?; self.buffer.clear(); Ok(()) } @@ -3422,15 +3466,12 @@ impl Drop for Buffy { impl WriteColor for Buffy { fn supports_color(&self) -> bool { - self.buffer.supports_color() - } - - fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { - self.buffer.set_color(spec) - } - - fn reset(&mut self) -> io::Result<()> { - self.buffer.reset() + match self.buffer_writer.current_choice() { + anstream::ColorChoice::Always + | anstream::ColorChoice::AlwaysAnsi + | anstream::ColorChoice::Auto => true, + anstream::ColorChoice::Never => false, + } } } @@ -3442,11 +3483,11 @@ pub fn stderr_destination(color: ColorConfig) -> Destination { // // On non-Windows we rely on the atomicity of `write` to ensure errors // don't get all jumbled up. + let buffer_writer = anstream::Stderr::new(std::io::stderr(), choice); if cfg!(windows) { - Box::new(StandardStream::stderr(choice)) + Box::new(buffer_writer) } else { - let buffer_writer = BufferWriter::stderr(choice); - let buffer = buffer_writer.buffer(); + let buffer = Vec::new(); Box::new(Buffy { buffer_writer, buffer }) } } @@ -3454,49 +3495,38 @@ pub fn stderr_destination(color: ColorConfig) -> Destination { /// On Windows, BRIGHT_BLUE is hard to read on black. Use cyan instead. /// /// See #36178. -const BRIGHT_BLUE: Color = if cfg!(windows) { Color::Cyan } else { Color::Blue }; +const BRIGHT_BLUE: anstyle::Style = if cfg!(windows) { + anstyle::AnsiColor::BrightCyan.on_default() +} else { + anstyle::AnsiColor::BrightBlue.on_default() +}; impl Style { - fn color_spec(&self, lvl: Level) -> ColorSpec { - let mut spec = ColorSpec::new(); + pub(crate) fn anstyle(&self, lvl: Level) -> anstyle::Style { match self { - Style::Addition => { - spec.set_fg(Some(Color::Green)).set_intense(true); - } - Style::Removal => { - spec.set_fg(Some(Color::Red)).set_intense(true); - } - Style::LineAndColumn => {} - Style::LineNumber => { - spec.set_bold(true); - spec.set_intense(true); - spec.set_fg(Some(BRIGHT_BLUE)); - } - Style::Quotation => {} - Style::MainHeaderMsg => { - spec.set_bold(true); - if cfg!(windows) { - spec.set_intense(true).set_fg(Some(Color::White)); - } + Style::Addition => anstyle::AnsiColor::BrightGreen.on_default(), + Style::Removal => anstyle::AnsiColor::BrightRed.on_default(), + Style::LineAndColumn => anstyle::Style::new(), + Style::LineNumber => BRIGHT_BLUE.effects(anstyle::Effects::BOLD), + Style::Quotation => anstyle::Style::new(), + Style::MainHeaderMsg => if cfg!(windows) { + anstyle::AnsiColor::BrightWhite.on_default() + } else { + anstyle::Style::new() } + .effects(anstyle::Effects::BOLD), Style::UnderlinePrimary | Style::LabelPrimary => { - spec = lvl.color(); - spec.set_bold(true); + lvl.color().effects(anstyle::Effects::BOLD) } Style::UnderlineSecondary | Style::LabelSecondary => { - spec.set_bold(true).set_intense(true); - spec.set_fg(Some(BRIGHT_BLUE)); - } - Style::HeaderMsg | Style::NoStyle => {} - Style::Level(lvl) => { - spec = lvl.color(); - spec.set_bold(true); + BRIGHT_BLUE.effects(anstyle::Effects::BOLD) } + Style::HeaderMsg | Style::NoStyle => anstyle::Style::new(), + Style::Level(lvl) => lvl.color().effects(anstyle::Effects::BOLD), Style::Highlight => { - spec.set_bold(true).set_fg(Some(Color::Magenta)); + anstyle::AnsiColor::Magenta.on_default().effects(anstyle::Effects::BOLD) } } - spec } } diff --git a/compiler/rustc_errors/src/json.rs b/compiler/rustc_errors/src/json.rs index 719d4ca625ae7..e52e998013120 100644 --- a/compiler/rustc_errors/src/json.rs +++ b/compiler/rustc_errors/src/json.rs @@ -15,6 +15,7 @@ use std::path::Path; use std::sync::{Arc, Mutex}; use std::vec; +use anstream::AutoStream; use derive_setters::Setters; use rustc_data_structures::sync::IntoDynSyncSend; use rustc_error_messages::FluentArgs; @@ -23,12 +24,11 @@ use rustc_span::Span; use rustc_span::hygiene::ExpnData; use rustc_span::source_map::{FilePathMapping, SourceMap}; use serde::Serialize; -use termcolor::{ColorSpec, WriteColor}; use crate::diagnostic::IsLint; use crate::emitter::{ ColorConfig, Destination, Emitter, HumanEmitter, HumanReadableErrorType, OutputTheme, - TimingEvent, should_show_source_code, + TimingEvent, WriteColor, should_show_source_code, }; use crate::registry::Registry; use crate::timings::{TimingRecord, TimingSection}; @@ -333,8 +333,8 @@ impl Diagnostic { // generate regular command line output and store it in the json // A threadsafe buffer for writing. - #[derive(Default, Clone)] - struct BufWriter(Arc>>); + #[derive(Clone)] + struct BufWriter(Arc>>>); impl Write for BufWriter { fn write(&mut self, buf: &[u8]) -> io::Result { @@ -344,17 +344,15 @@ impl Diagnostic { self.0.lock().unwrap().flush() } } + impl WriteColor for BufWriter { fn supports_color(&self) -> bool { - false - } - - fn set_color(&mut self, _spec: &ColorSpec) -> io::Result<()> { - Ok(()) - } - - fn reset(&mut self) -> io::Result<()> { - Ok(()) + match self.0.lock().unwrap().current_choice() { + anstream::ColorChoice::Always + | anstream::ColorChoice::AlwaysAnsi + | anstream::ColorChoice::Auto => true, + anstream::ColorChoice::Never => false, + } } } @@ -382,13 +380,12 @@ impl Diagnostic { children .insert(0, Diagnostic::from_sub_diagnostic(&diag.emitted_at_sub_diag(), &args, je)); } - let buf = BufWriter::default(); - let mut dst: Destination = Box::new(buf.clone()); + let buf = BufWriter(Arc::new(Mutex::new(AutoStream::new( + Vec::new(), + je.color_config.to_color_choice(), + )))); let short = je.json_rendered.short(); - match je.color_config { - ColorConfig::Always | ColorConfig::Auto => dst = Box::new(termcolor::Ansi::new(dst)), - ColorConfig::Never => {} - } + let dst: Destination = Box::new(buf.clone()); HumanEmitter::new(dst, je.translator.clone()) .short_message(short) .sm(je.sm.clone()) @@ -405,7 +402,7 @@ impl Diagnostic { }) .emit_diagnostic(diag, registry); let buf = Arc::try_unwrap(buf.0).unwrap().into_inner().unwrap(); - let buf = String::from_utf8(buf).unwrap(); + let buf = String::from_utf8(buf.into_inner()).unwrap(); Diagnostic { message: translated_message.to_string(), diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index 381d780077d19..cb5da52c8029e 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -39,6 +39,8 @@ use std::path::{Path, PathBuf}; use std::{fmt, panic}; use Level::*; +pub use anstream::{AutoStream, ColorChoice}; +pub use anstyle::*; pub use codes::*; pub use diagnostic::{ BugAbort, Diag, DiagArg, DiagArgMap, DiagArgName, DiagArgValue, DiagInner, DiagStyledString, @@ -1951,25 +1953,21 @@ impl fmt::Display for Level { } impl Level { - fn color(self) -> ColorSpec { - let mut spec = ColorSpec::new(); + fn color(self) -> anstyle::Style { match self { - Bug | Fatal | Error | DelayedBug => { - spec.set_fg(Some(Color::Red)).set_intense(true); - } + Bug | Fatal | Error | DelayedBug => anstyle::AnsiColor::BrightRed.on_default(), ForceWarning | Warning => { - spec.set_fg(Some(Color::Yellow)).set_intense(cfg!(windows)); - } - Note | OnceNote => { - spec.set_fg(Some(Color::Green)).set_intense(true); - } - Help | OnceHelp => { - spec.set_fg(Some(Color::Cyan)).set_intense(true); + if cfg!(windows) { + anstyle::AnsiColor::BrightYellow.on_default() + } else { + anstyle::AnsiColor::Yellow.on_default() + } } - FailureNote => {} + Note | OnceNote => anstyle::AnsiColor::BrightGreen.on_default(), + Help | OnceHelp => anstyle::AnsiColor::BrightCyan.on_default(), + FailureNote => anstyle::Style::new(), Allow | Expect => unreachable!(), } - spec } pub fn to_str(self) -> &'static str { diff --git a/compiler/rustc_errors/src/snippet.rs b/compiler/rustc_errors/src/snippet.rs index f09c2ed5534a5..21c3132d4f98e 100644 --- a/compiler/rustc_errors/src/snippet.rs +++ b/compiler/rustc_errors/src/snippet.rs @@ -192,7 +192,7 @@ impl Annotation { #[derive(Debug)] pub(crate) struct StyledString { pub text: String, - pub style: Style, + pub style: anstyle::Style, } #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Encodable, Decodable)] diff --git a/compiler/rustc_errors/src/styled_buffer.rs b/compiler/rustc_errors/src/styled_buffer.rs index 790efd0286e9d..a85a11cee24f7 100644 --- a/compiler/rustc_errors/src/styled_buffer.rs +++ b/compiler/rustc_errors/src/styled_buffer.rs @@ -1,5 +1,6 @@ // Code for creating styled buffers +use crate::Level; use crate::snippet::{Style, StyledString}; #[derive(Debug)] @@ -27,7 +28,7 @@ impl StyledBuffer { } /// Returns content of `StyledBuffer` split by lines and line styles - pub(crate) fn render(&self) -> Vec> { + pub(crate) fn render(&self, lvl: &Level) -> Vec> { // Tabs are assumed to have been replaced by spaces in calling code. debug_assert!(self.lines.iter().all(|r| !r.iter().any(|sc| sc.chr == '\t'))); @@ -35,15 +36,16 @@ impl StyledBuffer { let mut styled_vec: Vec = vec![]; for styled_line in &self.lines { - let mut current_style = Style::NoStyle; + let mut current_style = anstyle::Style::new(); let mut current_text = String::new(); for sc in styled_line { - if sc.style != current_style { + let style = sc.style.anstyle(*lvl); + if style != current_style { if !current_text.is_empty() { styled_vec.push(StyledString { text: current_text, style: current_style }); } - current_style = sc.style; + current_style = style; current_text = String::new(); } current_text.push(sc.chr); diff --git a/compiler/rustc_parse/Cargo.toml b/compiler/rustc_parse/Cargo.toml index c4a0ae2ce9dd9..e1f3f9f76b245 100644 --- a/compiler/rustc_parse/Cargo.toml +++ b/compiler/rustc_parse/Cargo.toml @@ -25,5 +25,4 @@ unicode-width = "0.2.0" # tidy-alphabetical-end [dev-dependencies] -termcolor = "1.2" diff --git a/compiler/rustc_parse/src/parser/tests.rs b/compiler/rustc_parse/src/parser/tests.rs index 15679d23bc565..81c52ecaa4363 100644 --- a/compiler/rustc_parse/src/parser/tests.rs +++ b/compiler/rustc_parse/src/parser/tests.rs @@ -13,7 +13,7 @@ use rustc_ast::token::{self, Delimiter, Token}; use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree}; use rustc_ast::{self as ast, PatKind, visit}; use rustc_ast_pretty::pprust::item_to_string; -use rustc_errors::emitter::{HumanEmitter, OutputTheme}; +use rustc_errors::emitter::{HumanEmitter, OutputTheme, WriteColor}; use rustc_errors::translation::Translator; use rustc_errors::{DiagCtxt, MultiSpan, PResult}; use rustc_session::parse::ParseSess; @@ -21,7 +21,6 @@ use rustc_span::source_map::{FilePathMapping, SourceMap}; use rustc_span::{ BytePos, FileName, Pos, Span, Symbol, create_default_session_globals_then, kw, sym, }; -use termcolor::WriteColor; use crate::parser::{ForceCollect, Parser}; use crate::{new_parser_from_source_str, source_str_to_stream, unwrap_or_emit_fatal}; @@ -163,14 +162,6 @@ impl WriteColor for Shared { fn supports_color(&self) -> bool { false } - - fn set_color(&mut self, _spec: &termcolor::ColorSpec) -> io::Result<()> { - Ok(()) - } - - fn reset(&mut self) -> io::Result<()> { - Ok(()) - } } impl Write for Shared { @@ -2114,15 +2105,15 @@ fn foo() { error: foo --> test.rs:3:6 | -3 | X0 Y0 Z0 + 3 | X0 Y0 Z0 | _______^ -4 | | X1 Y1 Z1 + 4 | | X1 Y1 Z1 | | ____^____- | ||____| | | `X` is a good letter -5 | | 1 -6 | | 2 -7 | | 3 + 5 | | 1 + 6 | | 2 + 7 | | 3 ... | 15 | | X2 Y2 Z2 16 | | X3 Y3 Z3 @@ -2133,15 +2124,15 @@ error: foo error: foo ╭▸ test.rs:3:6 │ -3 │ X0 Y0 Z0 + 3 │ X0 Y0 Z0 │ ┏━━━━━━━┛ -4 │ ┃ X1 Y1 Z1 + 4 │ ┃ X1 Y1 Z1 │ ┃┌────╿────┘ │ ┗│━━━━┥ │ │ `X` is a good letter -5 │ │ 1 -6 │ │ 2 -7 │ │ 3 + 5 │ │ 1 + 6 │ │ 2 + 7 │ │ 3 ‡ │ 15 │ │ X2 Y2 Z2 16 │ │ X3 Y3 Z3 @@ -2189,15 +2180,15 @@ fn foo() { error: foo --> test.rs:3:6 | -3 | X0 Y0 Z0 + 3 | X0 Y0 Z0 | _______^ -4 | | 1 -5 | | 2 -6 | | 3 -7 | | X1 Y1 Z1 + 4 | | 1 + 5 | | 2 + 6 | | 3 + 7 | | X1 Y1 Z1 | | _________- -8 | || 4 -9 | || 5 + 8 | || 4 + 9 | || 5 10 | || 6 11 | || X2 Y2 Z2 | ||__________- `Z` is a good letter too @@ -2211,15 +2202,15 @@ error: foo error: foo ╭▸ test.rs:3:6 │ -3 │ X0 Y0 Z0 + 3 │ X0 Y0 Z0 │ ┏━━━━━━━┛ -4 │ ┃ 1 -5 │ ┃ 2 -6 │ ┃ 3 -7 │ ┃ X1 Y1 Z1 + 4 │ ┃ 1 + 5 │ ┃ 2 + 6 │ ┃ 3 + 7 │ ┃ X1 Y1 Z1 │ ┃┌─────────┘ -8 │ ┃│ 4 -9 │ ┃│ 5 + 8 │ ┃│ 4 + 9 │ ┃│ 5 10 │ ┃│ 6 11 │ ┃│ X2 Y2 Z2 │ ┃└──────────┘ `Z` is a good letter too diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index 85bd8340c3cce..25b9d82fc7f6b 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -981,28 +981,22 @@ fn default_emitter( config::ErrorOutputType::HumanReadable { kind, color_config } => { let short = kind.short(); - if let HumanReadableErrorType::AnnotateSnippet = kind { - let emitter = - AnnotateSnippetEmitter::new(source_map, translator, short, macro_backtrace); - Box::new(emitter.ui_testing(sopts.unstable_opts.ui_testing)) - } else { - let emitter = HumanEmitter::new(stderr_destination(color_config), translator) - .sm(source_map) - .short_message(short) - .diagnostic_width(sopts.diagnostic_width) - .macro_backtrace(macro_backtrace) - .track_diagnostics(track_diagnostics) - .terminal_url(terminal_url) - .theme(if let HumanReadableErrorType::Unicode = kind { - OutputTheme::Unicode - } else { - OutputTheme::Ascii - }) - .ignored_directories_in_source_blocks( - sopts.unstable_opts.ignore_directory_in_diagnostics_source_blocks.clone(), - ); - Box::new(emitter.ui_testing(sopts.unstable_opts.ui_testing)) - } + let emitter = AnnotateSnippetEmitter::new(stderr_destination(color_config), translator) + .sm(source_map) + .short_message(short) + .diagnostic_width(sopts.diagnostic_width) + .macro_backtrace(macro_backtrace) + .track_diagnostics(track_diagnostics) + .terminal_url(terminal_url) + .theme(if let HumanReadableErrorType::Unicode = kind { + OutputTheme::Unicode + } else { + OutputTheme::Ascii + }) + .ignored_directories_in_source_blocks( + sopts.unstable_opts.ignore_directory_in_diagnostics_source_blocks.clone(), + ); + Box::new(emitter.ui_testing(sopts.unstable_opts.ui_testing)) } config::ErrorOutputType::Json { pretty, json_rendered, color_config } => Box::new( JsonEmitter::new( diff --git a/src/tools/compiletest/Cargo.toml b/src/tools/compiletest/Cargo.toml index cdada5a223062..0c7883a59c816 100644 --- a/src/tools/compiletest/Cargo.toml +++ b/src/tools/compiletest/Cargo.toml @@ -12,7 +12,7 @@ path = "src/bin/main.rs" [dependencies] # tidy-alphabetical-start -anstyle-svg = "0.1.3" +anstyle-svg = "0.1.8" build_helper = { path = "../../build_helper" } camino = "1" colored = "2" diff --git a/src/tools/tidy/src/deps.rs b/src/tools/tidy/src/deps.rs index bf813d2131e87..91729db307871 100644 --- a/src/tools/tidy/src/deps.rs +++ b/src/tools/tidy/src/deps.rs @@ -251,7 +251,11 @@ const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[ "aho-corasick", "allocator-api2", // FIXME: only appears in Cargo.lock due to https://github.com/rust-lang/cargo/issues/10801 "annotate-snippets", + "anstream", "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", "ar_archive_writer", "arrayref", "arrayvec", @@ -263,6 +267,7 @@ const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[ "cc", "cfg-if", "cfg_aliases", + "colorchoice", "constant_time_eq", "cpufeatures", "crc32fast", @@ -312,6 +317,7 @@ const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[ "indexmap", "intl-memoizer", "intl_pluralrules", + "is_terminal_polyfill", "itertools", "itoa", "jiff", @@ -336,6 +342,7 @@ const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[ "object", "odht", "once_cell", + "once_cell_polyfill", "overload", "parking_lot", "parking_lot_core", @@ -422,6 +429,7 @@ const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[ "unicode-security", "unicode-width", "unicode-xid", + "utf8parse", "valuable", "version_check", "wasi", diff --git a/tests/run-make/crate-loading-crate-depends-on-itself/foo.stderr b/tests/run-make/crate-loading-crate-depends-on-itself/foo.stderr index 7f131153540bb..9433a0ba00e80 100644 --- a/tests/run-make/crate-loading-crate-depends-on-itself/foo.stderr +++ b/tests/run-make/crate-loading-crate-depends-on-itself/foo.stderr @@ -7,19 +7,19 @@ error[E0277]: the trait bound `foo::Struct: Trait` is not satisfied note: there are multiple different versions of crate `foo` in the dependency graph --> foo-current.rs:7:1 | -4 | extern crate foo; + 4 | extern crate foo; | ----------------- one version of crate `foo` used here, as a direct dependency of the current crate -5 | -6 | pub struct Struct; + 5 | + 6 | pub struct Struct; | ----------------- this type implements the required trait -7 | pub trait Trait {} + 7 | pub trait Trait {} | ^^^^^^^^^^^^^^^ this is the required trait | ::: foo-prev.rs:X:Y | -4 | pub struct Struct; + 4 | pub struct Struct; | ----------------- this type doesn't implement the required trait -5 | pub trait Trait {} + 5 | pub trait Trait {} | --------------- this is the found trait = note: two types coming from two different versions of the same crate are different types even if they look the same = help: you can use `cargo tree` to explore your dependency tree diff --git a/tests/rustdoc-ui/doctest/standalone-warning-2024.stderr b/tests/rustdoc-ui/doctest/standalone-warning-2024.stderr index bfc1e91940453..ce65557c2c4a4 100644 --- a/tests/rustdoc-ui/doctest/standalone-warning-2024.stderr +++ b/tests/rustdoc-ui/doctest/standalone-warning-2024.stderr @@ -15,7 +15,7 @@ error: unknown attribute `standalone` note: the lint level is defined here --> $DIR/standalone-warning-2024.rs:9:9 | -9 | #![deny(warnings)] + 9 | #![deny(warnings)] | ^^^^^^^^ = note: `#[deny(rustdoc::invalid_codeblock_attributes)]` implied by `#[deny(warnings)]` diff --git a/tests/ui/annotate-snippet/missing-type.stderr b/tests/ui/annotate-snippet/missing-type.stderr index c16f022a77fa3..5cc9cc9529f40 100644 --- a/tests/ui/annotate-snippet/missing-type.stderr +++ b/tests/ui/annotate-snippet/missing-type.stderr @@ -4,3 +4,18 @@ error[E0412]: cannot find type `Iter` in this scope LL | let x: Iter; | ^^^^ not found in this scope | +help: consider importing one of these structs + | +LL + use std::collections::binary_heap::Iter; + | +LL + use std::collections::btree_map::Iter; + | +LL + use std::collections::btree_set::Iter; + | +LL + use std::collections::hash_map::Iter; + | + = and 9 other candidates + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0412`. diff --git a/tests/ui/annotate-snippet/multiple-files.stderr b/tests/ui/annotate-snippet/multiple-files.stderr index 4236ec811d04b..ffdc9482bbf24 100644 --- a/tests/ui/annotate-snippet/multiple-files.stderr +++ b/tests/ui/annotate-snippet/multiple-files.stderr @@ -7,5 +7,8 @@ LL | other_file::WithPrivateMethod.private_method(); ::: $DIR/auxiliary/other_file.rs:5:5 | LL | fn private_method(&self) {} - | ^^^^^^^^^^^^^^^^^^^^^^^^ private method defined here - | + | ------------------------ private method defined here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0624`. diff --git a/tests/ui/annotate-snippet/multispan.stderr b/tests/ui/annotate-snippet/multispan.stderr index baed54c59a4e9..8acb60e27a19b 100644 --- a/tests/ui/annotate-snippet/multispan.stderr +++ b/tests/ui/annotate-snippet/multispan.stderr @@ -4,39 +4,90 @@ error: hello to you, too! LL | hello!(hi); | ^^^^^^^^^^ | +note: found these 'hi's + --> $DIR/multispan.rs:15:12 + | +LL | hello!(hi); + | ^^ + = note: this error originates in the macro `hello` (in Nightly builds, run with -Z macro-backtrace for more info) + error: hello to you, too! --> $DIR/multispan.rs:18:5 | LL | hello!(hi hi); | ^^^^^^^^^^^^^ | +note: found these 'hi's + --> $DIR/multispan.rs:18:12 + | +LL | hello!(hi hi); + | ^^ ^^ + = note: this error originates in the macro `hello` (in Nightly builds, run with -Z macro-backtrace for more info) + error: hello to you, too! --> $DIR/multispan.rs:21:5 | LL | hello!(hi hi hi); | ^^^^^^^^^^^^^^^^ | +note: found these 'hi's + --> $DIR/multispan.rs:21:12 + | +LL | hello!(hi hi hi); + | ^^ ^^ ^^ + = note: this error originates in the macro `hello` (in Nightly builds, run with -Z macro-backtrace for more info) + error: hello to you, too! --> $DIR/multispan.rs:24:5 | LL | hello!(hi hey hi yo hi beep beep hi hi); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | +note: found these 'hi's + --> $DIR/multispan.rs:24:12 + | +LL | hello!(hi hey hi yo hi beep beep hi hi); + | ^^ ^^ ^^ ^^ ^^ + = note: this error originates in the macro `hello` (in Nightly builds, run with -Z macro-backtrace for more info) + error: hello to you, too! --> $DIR/multispan.rs:25:5 | LL | hello!(hi there, hi how are you? hi... hi.); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | +note: found these 'hi's + --> $DIR/multispan.rs:25:12 + | +LL | hello!(hi there, hi how are you? hi... hi.); + | ^^ ^^ ^^ ^^ + = note: this error originates in the macro `hello` (in Nightly builds, run with -Z macro-backtrace for more info) + error: hello to you, too! --> $DIR/multispan.rs:26:5 | LL | hello!(whoah. hi di hi di ho); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | +note: found these 'hi's + --> $DIR/multispan.rs:26:19 + | +LL | hello!(whoah. hi di hi di ho); + | ^^ ^^ + = note: this error originates in the macro `hello` (in Nightly builds, run with -Z macro-backtrace for more info) + error: hello to you, too! --> $DIR/multispan.rs:27:5 | LL | hello!(hi good hi and good bye); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | +note: found these 'hi's + --> $DIR/multispan.rs:27:12 + | +LL | hello!(hi good hi and good bye); + | ^^ ^^ + = note: this error originates in the macro `hello` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 7 previous errors + diff --git a/tests/ui/associated-types/associated-types-in-ambiguous-context.stderr b/tests/ui/associated-types/associated-types-in-ambiguous-context.stderr index a7647cf26aadf..71a1360cb5a22 100644 --- a/tests/ui/associated-types/associated-types-in-ambiguous-context.stderr +++ b/tests/ui/associated-types/associated-types-in-ambiguous-context.stderr @@ -42,7 +42,7 @@ LL + type X = ::Target; LL - type X = std::ops::Deref::Target; LL + type X = as Deref>::Target; | - and N other candidates + = and N other candidates error[E0223]: ambiguous associated type --> $DIR/associated-types-in-ambiguous-context.rs:13:23 diff --git a/tests/ui/async-await/async-drop/ex-ice1.stderr b/tests/ui/async-await/async-drop/ex-ice1.stderr index 68533edeb086c..a1fa89f2c4001 100644 --- a/tests/ui/async-await/async-drop/ex-ice1.stderr +++ b/tests/ui/async-await/async-drop/ex-ice1.stderr @@ -1,14 +1,14 @@ error[E0423]: expected function, found module `core::future::async_drop` --> $DIR/ex-ice1.rs:9:35 | -LL | let async_drop_fut = pin!(core::future::async_drop(async {})); - | ^^^^^^^^^^^^^^^^^^^^^^^^ not a function +LL | ... let async_drop_fut = pin!(core::future::async_drop(async {})); + | ^^^^^^^^^^^^^^^^^^^^^^^^ not a function error[E0603]: module `async_drop` is private --> $DIR/ex-ice1.rs:9:49 | -LL | let async_drop_fut = pin!(core::future::async_drop(async {})); - | ^^^^^^^^^^ private module +LL | ... let async_drop_fut = pin!(core::future::async_drop(async {})); + | ^^^^^^^^^^ private module | note: the module `async_drop` is defined here --> $SRC_DIR/core/src/future/mod.rs:LL:COL diff --git a/tests/ui/async-await/in-trait/dont-project-to-specializable-projection.stderr b/tests/ui/async-await/in-trait/dont-project-to-specializable-projection.stderr index 62cca41f6cfd8..fe8a8d38a9e1e 100644 --- a/tests/ui/async-await/in-trait/dont-project-to-specializable-projection.stderr +++ b/tests/ui/async-await/in-trait/dont-project-to-specializable-projection.stderr @@ -25,6 +25,7 @@ error[E0599]: no method named `poll` found for struct `Pin<&mut impl Future>` + | --> $SRC_DIR/core/src/future/future.rs:LL:COL | = note: the method is available for `Pin<&mut impl Future>` here diff --git a/tests/ui/c-variadic/issue-86053-1.stderr b/tests/ui/c-variadic/issue-86053-1.stderr index dc323f9a234d6..4dcfc90e9288d 100644 --- a/tests/ui/c-variadic/issue-86053-1.stderr +++ b/tests/ui/c-variadic/issue-86053-1.stderr @@ -57,6 +57,7 @@ error[E0412]: cannot find type `F` in this scope | LL | self , ... , self , self , ... ) where F : FnOnce ( & 'a & 'b usize ) { | ^ + | --> $SRC_DIR/core/src/ops/function.rs:LL:COL | = note: similarly named trait `Fn` defined here diff --git a/tests/ui/cast/func-pointer-issue-140491.stderr b/tests/ui/cast/func-pointer-issue-140491.stderr index e1c07010e691c..0b777905da1f9 100644 --- a/tests/ui/cast/func-pointer-issue-140491.stderr +++ b/tests/ui/cast/func-pointer-issue-140491.stderr @@ -1,8 +1,8 @@ error[E0605]: non-primitive cast: `&for<'a, 'b> fn(&'a Event<'b>) {my_fn}` as `&for<'a, 'b> fn(&'a Event<'b>)` --> $DIR/func-pointer-issue-140491.rs:6:34 | -LL | ..._>) = &my_fn as _; - | ^^^^^^^^^^^ an `as` expression can only be used to convert between primitive types or to coerce to a specific trait object +LL | ... = &my_fn as _; + | ^^^^^^^^^^^ an `as` expression can only be used to convert between primitive types or to coerce to a specific trait object | = note: casting reference expression `&my_fn` because `&` binds tighter than `as` diff --git a/tests/ui/closures/issue-78720.stderr b/tests/ui/closures/issue-78720.stderr index 3e95fab441ade..da742bd4f3429 100644 --- a/tests/ui/closures/issue-78720.stderr +++ b/tests/ui/closures/issue-78720.stderr @@ -9,6 +9,7 @@ error[E0412]: cannot find type `F` in this scope | LL | _func: F, | ^ + | --> $SRC_DIR/core/src/ops/function.rs:LL:COL | = note: similarly named trait `Fn` defined here diff --git a/tests/ui/closures/issue-90871.stderr b/tests/ui/closures/issue-90871.stderr index ef1cb213f73be..12020226680b2 100644 --- a/tests/ui/closures/issue-90871.stderr +++ b/tests/ui/closures/issue-90871.stderr @@ -3,6 +3,7 @@ error[E0412]: cannot find type `n` in this scope | LL | type_ascribe!(2, n([u8; || 1])) | ^ help: a trait with a similar name exists: `Fn` + | --> $SRC_DIR/core/src/ops/function.rs:LL:COL | = note: similarly named trait `Fn` defined here diff --git a/tests/ui/codemap_tests/huge_multispan_highlight.ascii.svg b/tests/ui/codemap_tests/huge_multispan_highlight.ascii.svg index 1cedbf75e4bf6..17f3a25be9f32 100644 --- a/tests/ui/codemap_tests/huge_multispan_highlight.ascii.svg +++ b/tests/ui/codemap_tests/huge_multispan_highlight.ascii.svg @@ -1,9 +1,9 @@ - +