From 4547b622d8d29df964fa2914213088b148c498fc Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 14:18:32 +0200 Subject: Merging upstream version 1.67.1+dfsg1. Signed-off-by: Daniel Baumann --- compiler/rustc_errors/Cargo.toml | 4 +- .../src/annotate_snippet_emitter_writer.rs | 7 +- compiler/rustc_errors/src/diagnostic.rs | 82 ++++++++- compiler/rustc_errors/src/diagnostic_builder.rs | 16 +- compiler/rustc_errors/src/diagnostic_impls.rs | 34 +++- compiler/rustc_errors/src/emitter.rs | 196 ++++++++++++++------- compiler/rustc_errors/src/json.rs | 10 +- compiler/rustc_errors/src/json/tests.rs | 1 + compiler/rustc_errors/src/lib.rs | 71 +++++++- compiler/rustc_errors/src/translation.rs | 44 +++-- 10 files changed, 364 insertions(+), 101 deletions(-) (limited to 'compiler/rustc_errors') diff --git a/compiler/rustc_errors/Cargo.toml b/compiler/rustc_errors/Cargo.toml index 7803a0792..dee7a31ec 100644 --- a/compiler/rustc_errors/Cargo.toml +++ b/compiler/rustc_errors/Cargo.toml @@ -18,7 +18,6 @@ rustc_target = { path = "../rustc_target" } rustc_hir = { path = "../rustc_hir" } rustc_lint_defs = { path = "../rustc_lint_defs" } unicode-width = "0.1.4" -atty = "0.2" termcolor = "1.0" annotate-snippets = "0.9" termize = "0.1.1" @@ -27,3 +26,6 @@ serde_json = "1.0.59" [target.'cfg(windows)'.dependencies] winapi = { version = "0.3", features = [ "handleapi", "synchapi", "winbase" ] } + +[features] +rustc_use_parallel_compiler = ['rustc_error_messages/rustc_use_parallel_compiler'] diff --git a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs index f14b8ee32..d8879bf70 100644 --- a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs +++ b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs @@ -39,7 +39,7 @@ impl Translate for AnnotateSnippetEmitterWriter { } fn fallback_fluent_bundle(&self) -> &FluentBundle { - &**self.fallback_bundle + &self.fallback_bundle } } @@ -49,10 +49,9 @@ impl Emitter for AnnotateSnippetEmitterWriter { let fluent_args = to_fluent_args(diag.args()); let mut children = diag.children.clone(); - let (mut primary_span, suggestions) = self.primary_span_formatted(&diag, &fluent_args); + 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, @@ -66,7 +65,7 @@ impl Emitter for AnnotateSnippetEmitterWriter { &diag.code, &primary_span, &children, - &suggestions, + suggestions, ); } diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs index 23f29a24f..06bb5edc0 100644 --- a/compiler/rustc_errors/src/diagnostic.rs +++ b/compiler/rustc_errors/src/diagnostic.rs @@ -4,6 +4,7 @@ use crate::{ SubdiagnosticMessage, Substitution, SubstitutionPart, SuggestionStyle, }; use rustc_data_structures::fx::FxHashMap; +use rustc_error_messages::fluent_value_from_str_list_sep_by_and; use rustc_error_messages::FluentValue; use rustc_lint_defs::{Applicability, LintExpectationId}; use rustc_span::edition::LATEST_STABLE_EDITION; @@ -12,6 +13,7 @@ use rustc_span::{Span, DUMMY_SP}; use std::borrow::Cow; use std::fmt; use std::hash::{Hash, Hasher}; +use std::panic::Location; /// Error type for `Diagnostic`'s `suggestions` field, indicating that /// `.disable_suggestions()` was called on the `Diagnostic`. @@ -33,6 +35,7 @@ pub type DiagnosticArgName<'source> = Cow<'source, str>; pub enum DiagnosticArgValue<'source> { Str(Cow<'source, str>), Number(usize), + StrListSepByAnd(Vec>), } /// Converts a value of a type into a `DiagnosticArg` (typically a field of an `IntoDiagnostic` @@ -43,19 +46,31 @@ pub trait IntoDiagnosticArg { fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static>; } +impl<'source> IntoDiagnosticArg for DiagnosticArgValue<'source> { + fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { + match self { + DiagnosticArgValue::Str(s) => DiagnosticArgValue::Str(Cow::Owned(s.into_owned())), + DiagnosticArgValue::Number(n) => DiagnosticArgValue::Number(n), + DiagnosticArgValue::StrListSepByAnd(l) => DiagnosticArgValue::StrListSepByAnd( + l.into_iter().map(|s| Cow::Owned(s.into_owned())).collect(), + ), + } + } +} + impl<'source> Into> for DiagnosticArgValue<'source> { fn into(self) -> FluentValue<'source> { match self { DiagnosticArgValue::Str(s) => From::from(s), DiagnosticArgValue::Number(n) => From::from(n), + DiagnosticArgValue::StrListSepByAnd(l) => fluent_value_from_str_list_sep_by_and(l), } } } /// Trait implemented by error types. This should not be implemented manually. Instead, use /// `#[derive(Subdiagnostic)]` -- see [rustc_macros::Subdiagnostic]. -#[cfg_attr(bootstrap, rustc_diagnostic_item = "AddSubdiagnostic")] -#[cfg_attr(not(bootstrap), rustc_diagnostic_item = "AddToDiagnostic")] +#[rustc_diagnostic_item = "AddToDiagnostic"] pub trait AddToDiagnostic where Self: Sized, @@ -107,6 +122,31 @@ pub struct Diagnostic { /// If diagnostic is from Lint, custom hash function ignores notes /// otherwise hash is based on the all the fields pub is_lint: bool, + + /// With `-Ztrack_diagnostics` enabled, + /// we print where in rustc this error was emitted. + pub emitted_at: DiagnosticLocation, +} + +#[derive(Clone, Debug, Encodable, Decodable)] +pub struct DiagnosticLocation { + file: Cow<'static, str>, + line: u32, + col: u32, +} + +impl DiagnosticLocation { + #[track_caller] + fn caller() -> Self { + let loc = Location::caller(); + DiagnosticLocation { file: loc.file().into(), line: loc.line(), col: loc.column() } + } +} + +impl fmt::Display for DiagnosticLocation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:{}:{}", self.file, self.line, self.col) + } } #[derive(Clone, Debug, PartialEq, Eq, Hash, Encodable, Decodable)] @@ -173,10 +213,28 @@ impl StringPart { } impl Diagnostic { + #[track_caller] pub fn new>(level: Level, message: M) -> Self { Diagnostic::new_with_code(level, None, message) } + #[track_caller] + pub fn new_with_messages(level: Level, messages: Vec<(DiagnosticMessage, Style)>) -> Self { + Diagnostic { + level, + message: messages, + code: None, + span: MultiSpan::new(), + children: vec![], + suggestions: Ok(vec![]), + args: Default::default(), + sort_span: DUMMY_SP, + is_lint: false, + emitted_at: DiagnosticLocation::caller(), + } + } + + #[track_caller] pub fn new_with_code>( level: Level, code: Option, @@ -192,6 +250,7 @@ impl Diagnostic { args: Default::default(), sort_span: DUMMY_SP, is_lint: false, + emitted_at: DiagnosticLocation::caller(), } } @@ -233,7 +292,7 @@ impl Diagnostic { let lint_index = expectation_id.get_lint_index(); expectation_id.set_lint_index(None); let mut stable_id = unstable_to_stable - .get(&expectation_id) + .get(expectation_id) .expect("each unstable `LintExpectationId` must have a matching stable id") .normalize(); @@ -688,7 +747,7 @@ impl Diagnostic { &mut self, sp: Span, msg: impl Into, - suggestions: impl Iterator, + suggestions: impl IntoIterator, applicability: Applicability, ) -> &mut Self { self.span_suggestions_with_style( @@ -705,11 +764,11 @@ impl Diagnostic { &mut self, sp: Span, msg: impl Into, - suggestions: impl Iterator, + suggestions: impl IntoIterator, applicability: Applicability, style: SuggestionStyle, ) -> &mut Self { - let mut suggestions: Vec<_> = suggestions.collect(); + let mut suggestions: Vec<_> = suggestions.into_iter().collect(); suggestions.sort(); debug_assert!( @@ -736,10 +795,10 @@ impl Diagnostic { pub fn multipart_suggestions( &mut self, msg: impl Into, - suggestions: impl Iterator>, + suggestions: impl IntoIterator>, applicability: Applicability, ) -> &mut Self { - let suggestions: Vec<_> = suggestions.collect(); + let suggestions: Vec<_> = suggestions.into_iter().collect(); debug_assert!( !(suggestions .iter() @@ -902,6 +961,13 @@ impl Diagnostic { self } + pub fn replace_args( + &mut self, + args: FxHashMap, DiagnosticArgValue<'static>>, + ) { + self.args = args; + } + pub fn styled_message(&self) -> &[(DiagnosticMessage, Style)] { &self.message } diff --git a/compiler/rustc_errors/src/diagnostic_builder.rs b/compiler/rustc_errors/src/diagnostic_builder.rs index 9b41234dc..a2ed98864 100644 --- a/compiler/rustc_errors/src/diagnostic_builder.rs +++ b/compiler/rustc_errors/src/diagnostic_builder.rs @@ -16,8 +16,7 @@ use std::thread::panicking; /// Trait implemented by error types. This should not be implemented manually. Instead, use /// `#[derive(Diagnostic)]` -- see [rustc_macros::Diagnostic]. -#[cfg_attr(bootstrap, rustc_diagnostic_item = "SessionDiagnostic")] -#[cfg_attr(not(bootstrap), rustc_diagnostic_item = "IntoDiagnostic")] +#[rustc_diagnostic_item = "IntoDiagnostic"] pub trait IntoDiagnostic<'a, T: EmissionGuarantee = ErrorGuaranteed> { /// Write out as a diagnostic out of `Handler`. #[must_use] @@ -133,6 +132,7 @@ mod sealed_level_is_error { impl<'a> DiagnosticBuilder<'a, ErrorGuaranteed> { /// Convenience function for internal use, clients should use one of the /// `struct_*` methods on [`Handler`]. + #[track_caller] pub(crate) fn new_guaranteeing_error, const L: Level>( handler: &'a Handler, message: M, @@ -196,6 +196,7 @@ impl EmissionGuarantee for ErrorGuaranteed { } } + #[track_caller] fn make_diagnostic_builder( handler: &Handler, msg: impl Into, @@ -209,6 +210,7 @@ impl EmissionGuarantee for ErrorGuaranteed { impl<'a> DiagnosticBuilder<'a, ()> { /// Convenience function for internal use, clients should use one of the /// `struct_*` methods on [`Handler`]. + #[track_caller] pub(crate) fn new>( handler: &'a Handler, level: Level, @@ -220,6 +222,7 @@ impl<'a> DiagnosticBuilder<'a, ()> { /// Creates a new `DiagnosticBuilder` with an already constructed /// diagnostic. + #[track_caller] pub(crate) fn new_diagnostic(handler: &'a Handler, diagnostic: Diagnostic) -> Self { debug!("Created new diagnostic"); Self { @@ -308,6 +311,7 @@ impl EmissionGuarantee for Noted { impl<'a> DiagnosticBuilder<'a, !> { /// Convenience function for internal use, clients should use one of the /// `struct_*` methods on [`Handler`]. + #[track_caller] pub(crate) fn new_fatal(handler: &'a Handler, message: impl Into) -> Self { let diagnostic = Diagnostic::new_with_code(Level::Fatal, None, message); Self::new_diagnostic_fatal(handler, diagnostic) @@ -477,9 +481,9 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> { /// In the meantime, though, callsites are required to deal with the "bug" /// locally in whichever way makes the most sense. #[track_caller] - pub fn delay_as_bug(&mut self) { + pub fn delay_as_bug(&mut self) -> G { self.downgrade_to_delayed_bug(); - self.emit(); + self.emit() } forward!( @@ -594,13 +598,13 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> { &mut self, sp: Span, msg: impl Into, - suggestions: impl Iterator, + suggestions: impl IntoIterator, applicability: Applicability, ) -> &mut Self); forward!(pub fn multipart_suggestions( &mut self, msg: impl Into, - suggestions: impl Iterator>, + suggestions: impl IntoIterator>, applicability: Applicability, ) -> &mut Self); forward!(pub fn span_suggestion_short( diff --git a/compiler/rustc_errors/src/diagnostic_impls.rs b/compiler/rustc_errors/src/diagnostic_impls.rs index 7640b2919..7155db32e 100644 --- a/compiler/rustc_errors/src/diagnostic_impls.rs +++ b/compiler/rustc_errors/src/diagnostic_impls.rs @@ -13,6 +13,7 @@ use std::borrow::Cow; use std::fmt; use std::num::ParseIntError; use std::path::{Path, PathBuf}; +use std::process::ExitStatus; pub struct DiagnosticArgFromDisplay<'a>(pub &'a dyn fmt::Display); @@ -58,6 +59,7 @@ into_diagnostic_arg_using_display!( i128, u128, std::io::Error, + std::boxed::Box, std::num::NonZeroU32, hir::Target, Edition, @@ -66,7 +68,8 @@ into_diagnostic_arg_using_display!( ParseIntError, StackProtector, &TargetTriple, - SplitDebuginfo + SplitDebuginfo, + ExitStatus, ); impl IntoDiagnosticArg for bool { @@ -103,6 +106,12 @@ impl IntoDiagnosticArg for String { } } +impl<'a> IntoDiagnosticArg for Cow<'a, str> { + fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { + DiagnosticArgValue::Str(Cow::Owned(self.into_owned())) + } +} + impl<'a> IntoDiagnosticArg for &'a Path { fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { DiagnosticArgValue::Str(Cow::Owned(self.display().to_string())) @@ -170,6 +179,29 @@ impl IntoDiagnosticArg for Level { } } +#[derive(Clone)] +pub struct DiagnosticSymbolList(Vec); + +impl From> for DiagnosticSymbolList { + fn from(v: Vec) -> Self { + DiagnosticSymbolList(v) + } +} + +impl IntoDiagnosticArg for DiagnosticSymbolList { + fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { + DiagnosticArgValue::StrListSepByAnd( + self.0.into_iter().map(|sym| Cow::Owned(format!("`{sym}`"))).collect(), + ) + } +} + +impl IntoDiagnosticArg for hir::def::Res { + fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { + DiagnosticArgValue::Str(Cow::Borrowed(self.descr())) + } +} + impl IntoDiagnostic<'_, !> for TargetDataLayoutErrors<'_> { fn into_diagnostic(self, handler: &Handler) -> DiagnosticBuilder<'_, !> { let mut diag; diff --git a/compiler/rustc_errors/src/emitter.rs b/compiler/rustc_errors/src/emitter.rs index cd6413bc3..4df2198fb 100644 --- a/compiler/rustc_errors/src/emitter.rs +++ b/compiler/rustc_errors/src/emitter.rs @@ -16,20 +16,20 @@ use crate::snippet::{Annotation, AnnotationType, Line, MultilineAnnotation, Styl use crate::styled_buffer::StyledBuffer; use crate::translation::{to_fluent_args, Translate}; use crate::{ - CodeSuggestion, Diagnostic, DiagnosticId, DiagnosticMessage, FluentBundle, Handler, - LazyFallbackBundle, Level, MultiSpan, SubDiagnostic, SubstitutionHighlight, SuggestionStyle, + diagnostic::DiagnosticLocation, CodeSuggestion, Diagnostic, DiagnosticId, DiagnosticMessage, + FluentBundle, Handler, LazyFallbackBundle, Level, MultiSpan, SubDiagnostic, + SubstitutionHighlight, SuggestionStyle, }; - use rustc_lint_defs::pluralize; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_data_structures::sync::Lrc; -use rustc_error_messages::FluentArgs; +use rustc_error_messages::{FluentArgs, SpanLabel}; use rustc_span::hygiene::{ExpnKind, MacroKind}; use std::borrow::Cow; use std::cmp::{max, min, Reverse}; -use std::io; use std::io::prelude::*; +use std::io::{self, IsTerminal}; use std::iter; use std::path::Path; use termcolor::{Ansi, BufferWriter, ColorChoice, ColorSpec, StandardStream}; @@ -64,6 +64,7 @@ impl HumanReadableErrorType { teach: bool, diagnostic_width: Option, macro_backtrace: bool, + track_diagnostics: bool, ) -> EmitterWriter { let (short, color_config) = self.unzip(); let color = color_config.suggests_using_colors(); @@ -77,6 +78,7 @@ impl HumanReadableErrorType { color, diagnostic_width, macro_backtrace, + track_diagnostics, ) } } @@ -246,7 +248,7 @@ pub trait Emitter: Translate { fluent_args: &FluentArgs<'_>, ) -> (MultiSpan, &'a [CodeSuggestion]) { let mut primary_span = diag.span.clone(); - let suggestions = diag.suggestions.as_ref().map_or(&[][..], |suggestions| &suggestions[..]); + let suggestions = diag.suggestions.as_deref().unwrap_or(&[]); if let Some((sugg, rest)) = suggestions.split_first() { let msg = self.translate_message(&sugg.msg, fluent_args); if rest.is_empty() && @@ -281,7 +283,7 @@ pub trait Emitter: Translate { if self .source_map() .map(|sm| is_case_difference( - &**sm, + sm, substitution, sugg.substitutions[0].parts[0].span, )) @@ -312,7 +314,6 @@ pub trait Emitter: Translate { fn fix_multispans_in_extern_macros_and_render_macro_backtrace( &self, - source_map: &Option>, span: &mut MultiSpan, children: &mut Vec, level: &Level, @@ -338,7 +339,7 @@ pub trait Emitter: Translate { .collect(); if !backtrace { - self.fix_multispans_in_extern_macros(source_map, span, children); + self.fix_multispans_in_extern_macros(span, children); } self.render_multispans_macro_backtrace(span, children, backtrace); @@ -478,15 +479,13 @@ pub trait Emitter: Translate { // this will change the span to point at the use site. fn fix_multispans_in_extern_macros( &self, - source_map: &Option>, span: &mut MultiSpan, children: &mut Vec, ) { - let Some(source_map) = source_map else { return }; debug!("fix_multispans_in_extern_macros: before: span={:?} children={:?}", span, children); - self.fix_multispan_in_extern_macros(source_map, span); + self.fix_multispan_in_extern_macros(span); for child in children.iter_mut() { - self.fix_multispan_in_extern_macros(source_map, &mut child.span); + self.fix_multispan_in_extern_macros(&mut child.span); } debug!("fix_multispans_in_extern_macros: after: span={:?} children={:?}", span, children); } @@ -494,7 +493,8 @@ pub trait Emitter: Translate { // This "fixes" MultiSpans that contain `Span`s pointing to locations inside of external macros. // Since these locations are often difficult to read, // we move these spans from the external macros to their corresponding use site. - fn fix_multispan_in_extern_macros(&self, source_map: &Lrc, span: &mut MultiSpan) { + fn fix_multispan_in_extern_macros(&self, span: &mut MultiSpan) { + let Some(source_map) = self.source_map() else { return }; // First, find all the spans in external macros and point instead at their use site. let replacements: Vec<(Span, Span)> = span .primary_spans() @@ -525,7 +525,7 @@ impl Translate for EmitterWriter { } fn fallback_fluent_bundle(&self) -> &FluentBundle { - &**self.fallback_bundle + &self.fallback_bundle } } @@ -538,11 +538,10 @@ impl Emitter for EmitterWriter { let fluent_args = to_fluent_args(diag.args()); let mut children = diag.children.clone(); - let (mut primary_span, suggestions) = self.primary_span_formatted(&diag, &fluent_args); + let (mut primary_span, suggestions) = self.primary_span_formatted(diag, &fluent_args); debug!("emit_diagnostic: suggestions={:?}", suggestions); self.fix_multispans_in_extern_macros_and_render_macro_backtrace( - &self.sm, &mut primary_span, &mut children, &diag.level, @@ -556,7 +555,8 @@ impl Emitter for EmitterWriter { &diag.code, &primary_span, &children, - &suggestions, + suggestions, + self.track_diagnostics.then_some(&diag.emitted_at), ); } @@ -619,14 +619,14 @@ impl ColorConfig { fn to_color_choice(self) -> ColorChoice { match self { ColorConfig::Always => { - if atty::is(atty::Stream::Stderr) { + if io::stderr().is_terminal() { ColorChoice::Always } else { ColorChoice::AlwaysAnsi } } ColorConfig::Never => ColorChoice::Never, - ColorConfig::Auto if atty::is(atty::Stream::Stderr) => ColorChoice::Auto, + ColorConfig::Auto if io::stderr().is_terminal() => ColorChoice::Auto, ColorConfig::Auto => ColorChoice::Never, } } @@ -650,6 +650,7 @@ pub struct EmitterWriter { diagnostic_width: Option, macro_backtrace: bool, + track_diagnostics: bool, } #[derive(Debug)] @@ -669,6 +670,7 @@ impl EmitterWriter { teach: bool, diagnostic_width: Option, macro_backtrace: bool, + track_diagnostics: bool, ) -> EmitterWriter { let dst = Destination::from_stderr(color_config); EmitterWriter { @@ -681,6 +683,7 @@ impl EmitterWriter { ui_testing: false, diagnostic_width, macro_backtrace, + track_diagnostics, } } @@ -694,6 +697,7 @@ impl EmitterWriter { colored: bool, diagnostic_width: Option, macro_backtrace: bool, + track_diagnostics: bool, ) -> EmitterWriter { EmitterWriter { dst: Raw(dst, colored), @@ -705,6 +709,7 @@ impl EmitterWriter { ui_testing: false, diagnostic_width, macro_backtrace, + track_diagnostics, } } @@ -768,6 +773,7 @@ impl EmitterWriter { draw_col_separator_no_space(buffer, line_offset, width_offset - 2); } + #[instrument(level = "trace", skip(self), ret)] fn render_source_line( &self, buffer: &mut StyledBuffer, @@ -796,9 +802,10 @@ impl EmitterWriter { } let source_string = match file.get_line(line.line_index - 1) { - Some(s) => normalize_whitespace(&*s), + Some(s) => normalize_whitespace(&s), None => return Vec::new(), }; + trace!(?source_string); let line_offset = buffer.num_lines(); @@ -1143,7 +1150,7 @@ impl EmitterWriter { (pos + 2, annotation.start_col.saturating_sub(left)) }; if let Some(ref label) = annotation.label { - buffer.puts(line_offset + pos, code_offset + col, &label, style); + buffer.puts(line_offset + pos, code_offset + col, label, style); } } @@ -1318,6 +1325,7 @@ impl EmitterWriter { } } + #[instrument(level = "trace", skip(self, args), ret)] fn emit_message_default( &mut self, msp: &MultiSpan, @@ -1327,6 +1335,7 @@ impl EmitterWriter { level: &Level, max_line_num_len: usize, is_secondary: bool, + emitted_at: Option<&DiagnosticLocation>, ) -> io::Result<()> { let mut buffer = StyledBuffer::new(); @@ -1352,7 +1361,7 @@ impl EmitterWriter { // only render error codes, not lint codes if let Some(DiagnosticId::Error(ref code)) = *code { buffer.append(0, "[", Style::Level(*level)); - buffer.append(0, &code, Style::Level(*level)); + buffer.append(0, code, Style::Level(*level)); buffer.append(0, "]", Style::Level(*level)); label_width += 2 + code.len(); } @@ -1377,24 +1386,16 @@ impl EmitterWriter { } } } - let mut annotated_files = FileWithAnnotatedLines::collect_annotations(self, args, msp); + trace!("{annotated_files:#?}"); // Make sure our primary file comes first - let (primary_lo, sm) = if let (Some(sm), Some(ref primary_span)) = - (self.sm.as_ref(), msp.primary_span().as_ref()) - { - if !primary_span.is_dummy() { - (sm.lookup_char_pos(primary_span.lo()), sm) - } else { - emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?; - return Ok(()); - } - } else { + 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 - emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?; - return Ok(()); + return emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message); }; + let primary_lo = sm.lookup_char_pos(primary_span.lo()); if let Ok(pos) = annotated_files.binary_search_by(|x| x.file.name.cmp(&primary_lo.file.name)) { @@ -1405,6 +1406,54 @@ impl EmitterWriter { for annotated_file in annotated_files { // we can't annotate anything if the source is unavailable. if !sm.ensure_source_file_source_present(annotated_file.file.clone()) { + if !self.short_message { + // We'll just print an unannotated message. + for (annotation_id, line) in annotated_file.lines.into_iter().enumerate() { + let mut annotations = line.annotations.clone(); + annotations.sort_by_key(|a| Reverse(a.start_col)); + let mut line_idx = buffer.num_lines(); + buffer.append( + line_idx, + &format!( + "{}:{}:{}", + sm.filename_for_diagnostics(&annotated_file.file.name), + sm.doctest_offset_line(&annotated_file.file.name, line.line_index), + annotations[0].start_col + 1, + ), + Style::LineAndColumn, + ); + if annotation_id == 0 { + buffer.prepend(line_idx, "--> ", Style::LineNumber); + for _ in 0..max_line_num_len { + buffer.prepend(line_idx, " ", Style::NoStyle); + } + line_idx += 1; + }; + for (i, annotation) in annotations.into_iter().enumerate() { + if let Some(label) = &annotation.label { + let style = if annotation.is_primary { + Style::LabelPrimary + } else { + Style::LabelSecondary + }; + if annotation_id == 0 { + buffer.prepend(line_idx, " |", Style::LineNumber); + for _ in 0..max_line_num_len { + buffer.prepend(line_idx, " ", Style::NoStyle); + } + line_idx += 1; + buffer.append(line_idx + i, " = note: ", style); + for _ in 0..max_line_num_len { + buffer.prepend(line_idx, " ", Style::NoStyle); + } + } else { + buffer.append(line_idx + i, ": ", style); + } + buffer.append(line_idx + i, label, style); + } + } + } + } continue; } @@ -1651,6 +1700,13 @@ impl EmitterWriter { multilines.extend(&to_add); } } + trace!("buffer: {:#?}", buffer.render()); + } + + if let Some(tracked) = emitted_at { + let track = format!("-Ztrack-diagnostics: created at {tracked}"); + let len = buffer.num_lines(); + buffer.append(len, &track, Style::NoStyle); } // final step: take our styled buffer, render it, then output it @@ -1672,7 +1728,7 @@ impl EmitterWriter { }; // Render the replacements for each suggestion - let suggestions = suggestion.splice_lines(&**sm); + let suggestions = suggestion.splice_lines(sm); debug!("emit_suggestion_default: suggestions={:?}", suggestions); if suggestions.is_empty() { @@ -1773,7 +1829,7 @@ impl EmitterWriter { buffer.puts( row_num - 1 + line - line_start, max_line_num_len + 3, - &normalize_whitespace(&*file_lines.file.get_line(line - 1).unwrap()), + &normalize_whitespace(&file_lines.file.get_line(line - 1).unwrap()), Style::Removal, ); } @@ -1915,7 +1971,7 @@ impl EmitterWriter { buffer.putc( row_num, (padding as isize + p) as usize, - if part.is_addition(&sm) { '+' } else { '~' }, + if part.is_addition(sm) { '+' } else { '~' }, Style::Addition, ); } @@ -1962,12 +2018,13 @@ impl EmitterWriter { buffer.puts(row_num, max_line_num_len + 3, &msg, Style::NoStyle); } else if notice_capitalization { let msg = "notice the capitalization difference"; - buffer.puts(row_num, max_line_num_len + 3, &msg, Style::NoStyle); + buffer.puts(row_num, max_line_num_len + 3, msg, Style::NoStyle); } emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?; Ok(()) } + #[instrument(level = "trace", skip(self, args, code, children, suggestions))] fn emit_messages_default( &mut self, level: &Level, @@ -1977,6 +2034,7 @@ impl EmitterWriter { span: &MultiSpan, children: &[SubDiagnostic], suggestions: &[CodeSuggestion], + emitted_at: Option<&DiagnosticLocation>, ) { let max_line_num_len = if self.ui_testing { ANONYMIZED_LINE_NUM.len() @@ -1985,7 +2043,16 @@ impl EmitterWriter { num_decimal_digits(n) }; - match self.emit_message_default(span, message, args, code, level, max_line_num_len, false) { + match self.emit_message_default( + span, + message, + args, + code, + level, + max_line_num_len, + false, + emitted_at, + ) { Ok(()) => { if !children.is_empty() || suggestions.iter().any(|s| s.style != SuggestionStyle::CompletelyHidden) @@ -2007,13 +2074,14 @@ impl EmitterWriter { for child in children { let span = child.render_span.as_ref().unwrap_or(&child.span); if let Err(err) = self.emit_message_default( - &span, + span, &child.message, args, &None, &child.level, max_line_num_len, true, + None, ) { panic!("failed to emit error: {}", err); } @@ -2030,6 +2098,7 @@ impl EmitterWriter { &Level::Help, max_line_num_len, true, + None, ) { panic!("failed to emit error: {}", e); } @@ -2090,7 +2159,7 @@ impl EmitterWriter { *row_num - 1, max_line_num_len + 3, &normalize_whitespace( - &*file_lines.file.get_line(file_lines.lines[line_pos].line_index).unwrap(), + &file_lines.file.get_line(file_lines.lines[line_pos].line_index).unwrap(), ), Style::NoStyle, ); @@ -2186,13 +2255,16 @@ impl FileWithAnnotatedLines { let mut multiline_annotations = vec![]; if let Some(ref sm) = emitter.source_map() { - for span_label in msp.span_labels() { - if span_label.span.is_dummy() { - continue; - } + 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 lo = sm.lookup_char_pos(span_label.span.lo()); - let mut hi = sm.lookup_char_pos(span_label.span.hi()); + let lo = sm.lookup_char_pos(span.lo()); + let mut hi = sm.lookup_char_pos(span.hi()); // Watch out for "empty spans". If we get a span like 6..6, we // want to just display a `^` at 6, so convert that to @@ -2204,6 +2276,8 @@ impl FileWithAnnotatedLines { hi.col_display += 1; } + let label = label.as_ref().map(|m| emitter.translate_message(m, args).to_string()); + if lo.line != hi.line { let ml = MultilineAnnotation { depth: 1, @@ -2211,11 +2285,8 @@ impl FileWithAnnotatedLines { line_end: hi.line, start_col: lo.col_display, end_col: hi.col_display, - is_primary: span_label.is_primary, - label: span_label - .label - .as_ref() - .map(|m| emitter.translate_message(m, args).to_string()), + is_primary, + label, overlaps_exactly: false, }; multiline_annotations.push((lo.file, ml)); @@ -2223,11 +2294,8 @@ impl FileWithAnnotatedLines { let ann = Annotation { start_col: lo.col_display, end_col: hi.col_display, - is_primary: span_label.is_primary, - label: span_label - .label - .as_ref() - .map(|m| emitter.translate_message(m, args).to_string()), + is_primary, + label, annotation_type: AnnotationType::Singleline, }; add_annotation_to_file(&mut output, lo.file, lo.line, ann); @@ -2236,7 +2304,7 @@ impl FileWithAnnotatedLines { } // Find overlapping multiline annotations, put them at different depths - multiline_annotations.sort_by_key(|&(_, ref ml)| (ml.line_start, ml.line_end)); + multiline_annotations.sort_by_key(|&(_, ref ml)| (ml.line_start, usize::MAX - ml.line_end)); for (_, ann) in multiline_annotations.clone() { for (_, a) in multiline_annotations.iter_mut() { // Move all other multiline annotations overlapping with this one @@ -2254,8 +2322,14 @@ impl FileWithAnnotatedLines { } let mut max_depth = 0; // max overlapping multiline spans - for (file, ann) in multiline_annotations { + for (_, ann) in &multiline_annotations { max_depth = max(max_depth, ann.depth); + } + // Change order of multispan depth to minimize the number of overlaps in the ASCII art. + for (_, a) in multiline_annotations.iter_mut() { + a.depth = max_depth - a.depth + 1; + } + for (file, ann) in multiline_annotations { let mut end_ann = ann.as_end(); if !ann.overlaps_exactly { // avoid output like diff --git a/compiler/rustc_errors/src/json.rs b/compiler/rustc_errors/src/json.rs index 4cc7be47f..a37073d8f 100644 --- a/compiler/rustc_errors/src/json.rs +++ b/compiler/rustc_errors/src/json.rs @@ -45,6 +45,7 @@ pub struct JsonEmitter { json_rendered: HumanReadableErrorType, diagnostic_width: Option, macro_backtrace: bool, + track_diagnostics: bool, } impl JsonEmitter { @@ -57,6 +58,7 @@ impl JsonEmitter { json_rendered: HumanReadableErrorType, diagnostic_width: Option, macro_backtrace: bool, + track_diagnostics: bool, ) -> JsonEmitter { JsonEmitter { dst: Box::new(io::BufWriter::new(io::stderr())), @@ -69,6 +71,7 @@ impl JsonEmitter { json_rendered, diagnostic_width, macro_backtrace, + track_diagnostics, } } @@ -79,6 +82,7 @@ impl JsonEmitter { fallback_bundle: LazyFallbackBundle, diagnostic_width: Option, macro_backtrace: bool, + track_diagnostics: bool, ) -> JsonEmitter { let file_path_mapping = FilePathMapping::empty(); JsonEmitter::stderr( @@ -90,6 +94,7 @@ impl JsonEmitter { json_rendered, diagnostic_width, macro_backtrace, + track_diagnostics, ) } @@ -103,6 +108,7 @@ impl JsonEmitter { json_rendered: HumanReadableErrorType, diagnostic_width: Option, macro_backtrace: bool, + track_diagnostics: bool, ) -> JsonEmitter { JsonEmitter { dst, @@ -115,6 +121,7 @@ impl JsonEmitter { json_rendered, diagnostic_width, macro_backtrace, + track_diagnostics, } } @@ -129,7 +136,7 @@ impl Translate for JsonEmitter { } fn fallback_fluent_bundle(&self) -> &FluentBundle { - &**self.fallback_bundle + &self.fallback_bundle } } @@ -350,6 +357,7 @@ impl Diagnostic { false, je.diagnostic_width, je.macro_backtrace, + je.track_diagnostics, ) .ui_testing(je.ui_testing) .emit_diagnostic(diag); diff --git a/compiler/rustc_errors/src/json/tests.rs b/compiler/rustc_errors/src/json/tests.rs index d940d14e1..f13146897 100644 --- a/compiler/rustc_errors/src/json/tests.rs +++ b/compiler/rustc_errors/src/json/tests.rs @@ -59,6 +59,7 @@ fn test_positions(code: &str, span: (u32, u32), expected_output: SpanTestData) { HumanReadableErrorType::Short(ColorConfig::Never), None, false, + false, ); let span = Span::with_root_ctxt(BytePos(span.0), BytePos(span.1)); diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index 0963ea71f..eb0506c45 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -5,6 +5,7 @@ #![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")] #![feature(drain_filter)] #![feature(if_let_guard)] +#![feature(is_terminal)] #![feature(adt_const_params)] #![feature(let_chains)] #![feature(never_type)] @@ -376,7 +377,7 @@ pub use diagnostic::{ DiagnosticStyledString, IntoDiagnosticArg, SubDiagnostic, }; pub use diagnostic_builder::{DiagnosticBuilder, EmissionGuarantee, Noted}; -pub use diagnostic_impls::DiagnosticArgFromDisplay; +pub use diagnostic_impls::{DiagnosticArgFromDisplay, DiagnosticSymbolList}; use std::backtrace::Backtrace; /// A handler deals with errors and other compiler output. @@ -466,6 +467,10 @@ pub enum StashKey { /// When an invalid lifetime e.g. `'2` should be reinterpreted /// as a char literal in the parser LifetimeIsChar, + /// Maybe there was a typo where a comma was forgotten before + /// FRU syntax + MaybeFruTypo, + CallAssocMethod, } fn default_track_diagnostic(_: &Diagnostic) {} @@ -492,6 +497,8 @@ pub struct HandlerFlags { pub macro_backtrace: bool, /// If true, identical diagnostics are reported only once. pub deduplicate_diagnostics: bool, + /// Track where errors are created. Enabled with `-Ztrack-diagnostics`. + pub track_diagnostics: bool, } impl Drop for HandlerInner { @@ -559,6 +566,7 @@ impl Handler { false, None, flags.macro_backtrace, + flags.track_diagnostics, )); Self::with_emitter_and_flags(emitter, flags) } @@ -641,13 +649,14 @@ impl Handler { inner.stashed_diagnostics = Default::default(); } - /// Stash a given diagnostic with the given `Span` and `StashKey` as the key for later stealing. + /// Stash a given diagnostic with the given `Span` and [`StashKey`] as the key. + /// Retrieve a stashed diagnostic with `steal_diagnostic`. pub fn stash_diagnostic(&self, span: Span, key: StashKey, diag: Diagnostic) { let mut inner = self.inner.borrow_mut(); inner.stash((span, key), diag); } - /// Steal a previously stashed diagnostic with the given `Span` and `StashKey` as the key. + /// Steal a previously stashed diagnostic with the given `Span` and [`StashKey`] as the key. pub fn steal_diagnostic(&self, span: Span, key: StashKey) -> Option> { let mut inner = self.inner.borrow_mut(); inner.steal((span, key)).map(|diag| DiagnosticBuilder::new_diagnostic(self, diag)) @@ -664,6 +673,7 @@ impl Handler { /// Construct a builder with the `msg` at the level appropriate for the specific `EmissionGuarantee`. #[rustc_lint_diagnostics] + #[track_caller] pub fn struct_diagnostic( &self, msg: impl Into, @@ -677,6 +687,7 @@ impl Handler { /// * `can_emit_warnings` is `true` /// * `is_force_warn` was set in `DiagnosticId::Lint` #[rustc_lint_diagnostics] + #[track_caller] pub fn struct_span_warn( &self, span: impl Into, @@ -693,6 +704,7 @@ impl Handler { /// Attempting to `.emit()` the builder will only emit if either: /// * `can_emit_warnings` is `true` /// * `is_force_warn` was set in `DiagnosticId::Lint` + #[track_caller] pub fn struct_span_warn_with_expectation( &self, span: impl Into, @@ -706,6 +718,7 @@ impl Handler { /// Construct a builder at the `Allow` level at the given `span` and with the `msg`. #[rustc_lint_diagnostics] + #[track_caller] pub fn struct_span_allow( &self, span: impl Into, @@ -719,6 +732,7 @@ impl Handler { /// Construct a builder at the `Warning` level at the given `span` and with the `msg`. /// Also include a code. #[rustc_lint_diagnostics] + #[track_caller] pub fn struct_span_warn_with_code( &self, span: impl Into, @@ -736,6 +750,7 @@ impl Handler { /// * `can_emit_warnings` is `true` /// * `is_force_warn` was set in `DiagnosticId::Lint` #[rustc_lint_diagnostics] + #[track_caller] pub fn struct_warn(&self, msg: impl Into) -> DiagnosticBuilder<'_, ()> { DiagnosticBuilder::new(self, Level::Warning(None), msg) } @@ -746,6 +761,7 @@ impl Handler { /// Attempting to `.emit()` the builder will only emit if either: /// * `can_emit_warnings` is `true` /// * `is_force_warn` was set in `DiagnosticId::Lint` + #[track_caller] pub fn struct_warn_with_expectation( &self, msg: impl Into, @@ -756,12 +772,14 @@ impl Handler { /// Construct a builder at the `Allow` level with the `msg`. #[rustc_lint_diagnostics] + #[track_caller] pub fn struct_allow(&self, msg: impl Into) -> DiagnosticBuilder<'_, ()> { DiagnosticBuilder::new(self, Level::Allow, msg) } /// Construct a builder at the `Expect` level with the `msg`. #[rustc_lint_diagnostics] + #[track_caller] pub fn struct_expect( &self, msg: impl Into, @@ -772,6 +790,7 @@ impl Handler { /// Construct a builder at the `Error` level at the given `span` and with the `msg`. #[rustc_lint_diagnostics] + #[track_caller] pub fn struct_span_err( &self, span: impl Into, @@ -784,6 +803,7 @@ impl Handler { /// Construct a builder at the `Error` level at the given `span`, with the `msg`, and `code`. #[rustc_lint_diagnostics] + #[track_caller] pub fn struct_span_err_with_code( &self, span: impl Into, @@ -798,6 +818,7 @@ impl Handler { /// Construct a builder at the `Error` level with the `msg`. // FIXME: This method should be removed (every error should have an associated error code). #[rustc_lint_diagnostics] + #[track_caller] pub fn struct_err( &self, msg: impl Into, @@ -807,12 +828,14 @@ impl Handler { /// This should only be used by `rustc_middle::lint::struct_lint_level`. Do not use it for hard errors. #[doc(hidden)] + #[track_caller] pub fn struct_err_lint(&self, msg: impl Into) -> DiagnosticBuilder<'_, ()> { DiagnosticBuilder::new(self, Level::Error { lint: true }, msg) } /// Construct a builder at the `Error` level with the `msg` and the `code`. #[rustc_lint_diagnostics] + #[track_caller] pub fn struct_err_with_code( &self, msg: impl Into, @@ -825,6 +848,7 @@ impl Handler { /// Construct a builder at the `Warn` level with the `msg` and the `code`. #[rustc_lint_diagnostics] + #[track_caller] pub fn struct_warn_with_code( &self, msg: impl Into, @@ -837,6 +861,7 @@ impl Handler { /// Construct a builder at the `Fatal` level at the given `span` and with the `msg`. #[rustc_lint_diagnostics] + #[track_caller] pub fn struct_span_fatal( &self, span: impl Into, @@ -849,6 +874,7 @@ impl Handler { /// Construct a builder at the `Fatal` level at the given `span`, with the `msg`, and `code`. #[rustc_lint_diagnostics] + #[track_caller] pub fn struct_span_fatal_with_code( &self, span: impl Into, @@ -862,6 +888,7 @@ impl Handler { /// Construct a builder at the `Error` level with the `msg`. #[rustc_lint_diagnostics] + #[track_caller] pub fn struct_fatal(&self, msg: impl Into) -> DiagnosticBuilder<'_, !> { DiagnosticBuilder::new_fatal(self, msg) } @@ -874,6 +901,7 @@ impl Handler { /// Construct a builder at the `Note` level with the `msg`. #[rustc_lint_diagnostics] + #[track_caller] pub fn struct_note_without_error( &self, msg: impl Into, @@ -882,12 +910,14 @@ impl Handler { } #[rustc_lint_diagnostics] + #[track_caller] pub fn span_fatal(&self, span: impl Into, msg: impl Into) -> ! { self.emit_diag_at_span(Diagnostic::new(Fatal, msg), span); FatalError.raise() } #[rustc_lint_diagnostics] + #[track_caller] pub fn span_fatal_with_code( &self, span: impl Into, @@ -899,6 +929,7 @@ impl Handler { } #[rustc_lint_diagnostics] + #[track_caller] pub fn span_err( &self, span: impl Into, @@ -908,6 +939,7 @@ impl Handler { } #[rustc_lint_diagnostics] + #[track_caller] pub fn span_err_with_code( &self, span: impl Into, @@ -921,11 +953,13 @@ impl Handler { } #[rustc_lint_diagnostics] + #[track_caller] pub fn span_warn(&self, span: impl Into, msg: impl Into) { self.emit_diag_at_span(Diagnostic::new(Warning(None), msg), span); } #[rustc_lint_diagnostics] + #[track_caller] pub fn span_warn_with_code( &self, span: impl Into, @@ -954,10 +988,12 @@ impl Handler { self.inner.borrow_mut().delay_good_path_bug(msg) } + #[track_caller] pub fn span_bug_no_panic(&self, span: impl Into, msg: impl Into) { self.emit_diag_at_span(Diagnostic::new(Bug, msg), span); } + #[track_caller] pub fn span_note_without_error( &self, span: impl Into, @@ -966,6 +1002,7 @@ impl Handler { self.emit_diag_at_span(Diagnostic::new(Note, msg), span); } + #[track_caller] pub fn span_note_diag( &self, span: Span, @@ -1008,13 +1045,24 @@ impl Handler { } pub fn has_errors_or_lint_errors(&self) -> Option { if self.inner.borrow().has_errors_or_lint_errors() { - Some(ErrorGuaranteed(())) + Some(ErrorGuaranteed::unchecked_claim_error_was_emitted()) } else { None } } - pub fn has_errors_or_delayed_span_bugs(&self) -> bool { - self.inner.borrow().has_errors_or_delayed_span_bugs() + pub fn has_errors_or_delayed_span_bugs(&self) -> Option { + if self.inner.borrow().has_errors_or_delayed_span_bugs() { + Some(ErrorGuaranteed::unchecked_claim_error_was_emitted()) + } else { + None + } + } + pub fn is_compilation_going_to_fail(&self) -> Option { + if self.inner.borrow().is_compilation_going_to_fail() { + Some(ErrorGuaranteed::unchecked_claim_error_was_emitted()) + } else { + None + } } pub fn print_error_count(&self, registry: &Registry) { @@ -1223,6 +1271,10 @@ impl HandlerInner { } if diagnostic.has_future_breakage() { + // Future breakages aren't emitted if they're Level::Allowed, + // but they still need to be constructed and stashed below, + // so they'll trigger the good-path bug check. + self.suppressed_expected_diag = true; self.future_breakage_diagnostics.push(diagnostic.clone()); } @@ -1277,7 +1329,7 @@ impl HandlerInner { diagnostic.children.drain_filter(already_emitted_sub).for_each(|_| {}); - self.emitter.emit_diagnostic(&diagnostic); + self.emitter.emit_diagnostic(diagnostic); if diagnostic.is_error() { self.deduplicated_err_count += 1; } else if let Warning(_) = diagnostic.level { @@ -1444,6 +1496,10 @@ impl HandlerInner { self.err_count() > 0 || self.lint_err_count > 0 || self.warn_count > 0 } + fn is_compilation_going_to_fail(&self) -> bool { + self.has_errors() || self.lint_err_count > 0 || !self.delayed_span_bugs.is_empty() + } + fn abort_if_errors(&mut self) { self.emit_stashed_diagnostics(); @@ -1452,6 +1508,7 @@ impl HandlerInner { } } + #[track_caller] fn span_bug(&mut self, sp: impl Into, msg: impl Into) -> ! { self.emit_diag_at_span(Diagnostic::new(Bug, msg), sp); panic::panic_any(ExplicitBug); diff --git a/compiler/rustc_errors/src/translation.rs b/compiler/rustc_errors/src/translation.rs index a7737b467..afd660ff1 100644 --- a/compiler/rustc_errors/src/translation.rs +++ b/compiler/rustc_errors/src/translation.rs @@ -1,7 +1,10 @@ use crate::snippet::Style; use crate::{DiagnosticArg, DiagnosticMessage, FluentBundle}; use rustc_data_structures::sync::Lrc; -use rustc_error_messages::FluentArgs; +use rustc_error_messages::{ + fluent_bundle::resolver::errors::{ReferenceKind, ResolverError}, + FluentArgs, FluentError, +}; use std::borrow::Cow; /// Convert diagnostic arguments (a rustc internal type that exists to implement @@ -56,13 +59,13 @@ pub trait Translate { trace!(?message, ?args); let (identifier, attr) = match message { DiagnosticMessage::Str(msg) | DiagnosticMessage::Eager(msg) => { - return Cow::Borrowed(&msg); + return Cow::Borrowed(msg); } DiagnosticMessage::FluentIdentifier(identifier, attr) => (identifier, attr), }; let translate_with_bundle = |bundle: &'a FluentBundle| -> Option<(Cow<'_, str>, Vec<_>)> { - let message = bundle.get_message(&identifier)?; + let message = bundle.get_message(identifier)?; let value = match attr { Some(attr) => message.get_attribute(attr)?.value(), None => message.value()?, @@ -70,7 +73,7 @@ pub trait Translate { debug!(?message, ?value); let mut errs = vec![]; - let translated = bundle.format_pattern(value, Some(&args), &mut errs); + let translated = bundle.format_pattern(value, Some(args), &mut errs); debug!(?translated, ?errs); Some((translated, errs)) }; @@ -102,14 +105,31 @@ pub trait Translate { .or_else(|| translate_with_bundle(self.fallback_fluent_bundle())) .map(|(translated, errs)| { // Always bail out for errors with the fallback bundle. - assert!( - errs.is_empty(), - "identifier: {:?}, attr: {:?}, args: {:?}, errors: {:?}", - identifier, - attr, - args, - errs - ); + + let mut help_messages = vec![]; + + if !errs.is_empty() { + for error in &errs { + match error { + FluentError::ResolverError(ResolverError::Reference( + ReferenceKind::Message { id, .. }, + )) if args.iter().any(|(arg_id, _)| arg_id == id) => { + help_messages.push(format!("Argument `{id}` exists but was not referenced correctly. Try using `{{${id}}}` instead")); + } + _ => {} + } + } + + panic!( + "Encountered errors while formatting message for `{identifier}`\n\ + help: {}\n\ + attr: `{attr:?}`\n\ + args: `{args:?}`\n\ + errors: `{errs:?}`", + help_messages.join("\nhelp: ") + ); + } + translated }) .expect("failed to find message in primary or fallback fluent bundles") -- cgit v1.2.3