summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_errors
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_errors')
-rw-r--r--compiler/rustc_errors/Cargo.toml1
-rw-r--r--compiler/rustc_errors/src/diagnostic.rs16
-rw-r--r--compiler/rustc_errors/src/diagnostic_builder.rs55
-rw-r--r--compiler/rustc_errors/src/diagnostic_impls.rs26
-rw-r--r--compiler/rustc_errors/src/emitter.rs124
-rw-r--r--compiler/rustc_errors/src/error.rs137
-rw-r--r--compiler/rustc_errors/src/json.rs9
-rw-r--r--compiler/rustc_errors/src/lib.rs177
-rw-r--r--compiler/rustc_errors/src/tests.rs188
-rw-r--r--compiler/rustc_errors/src/translation.rs117
10 files changed, 640 insertions, 210 deletions
diff --git a/compiler/rustc_errors/Cargo.toml b/compiler/rustc_errors/Cargo.toml
index dee7a31ec..cadd53fbd 100644
--- a/compiler/rustc_errors/Cargo.toml
+++ b/compiler/rustc_errors/Cargo.toml
@@ -17,6 +17,7 @@ rustc_data_structures = { path = "../rustc_data_structures" }
rustc_target = { path = "../rustc_target" }
rustc_hir = { path = "../rustc_hir" }
rustc_lint_defs = { path = "../rustc_lint_defs" }
+rustc_type_ir = { path = "../rustc_type_ir" }
unicode-width = "0.1.4"
termcolor = "1.0"
annotate-snippets = "0.9"
diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs
index 06bb5edc0..51b2ff6a0 100644
--- a/compiler/rustc_errors/src/diagnostic.rs
+++ b/compiler/rustc_errors/src/diagnostic.rs
@@ -114,9 +114,9 @@ pub struct Diagnostic {
pub suggestions: Result<Vec<CodeSuggestion>, SuggestionsDisabled>,
args: FxHashMap<DiagnosticArgName<'static>, DiagnosticArgValue<'static>>,
- /// This is not used for highlighting or rendering any error message. Rather, it can be used
- /// as a sort key to sort a buffer of diagnostics. By default, it is the primary span of
- /// `span` if there is one. Otherwise, it is `DUMMY_SP`.
+ /// This is not used for highlighting or rendering any error message. Rather, it can be used
+ /// as a sort key to sort a buffer of diagnostics. By default, it is the primary span of
+ /// `span` if there is one. Otherwise, it is `DUMMY_SP`.
pub sort_span: Span,
/// If diagnostic is from Lint, custom hash function ignores notes
@@ -365,12 +365,16 @@ impl Diagnostic {
self
}
- pub fn replace_span_with(&mut self, after: Span) -> &mut Self {
+ pub fn replace_span_with(&mut self, after: Span, keep_label: bool) -> &mut Self {
let before = self.span.clone();
self.set_span(after);
for span_label in before.span_labels() {
if let Some(label) = span_label.label {
- self.span.push_span_label(after, label);
+ if span_label.is_primary && keep_label {
+ self.span.push_span_label(after, label);
+ } else {
+ self.span.push_span_label(span_label.span, label);
+ }
}
}
self
@@ -802,7 +806,7 @@ impl Diagnostic {
debug_assert!(
!(suggestions
.iter()
- .flat_map(|suggs| suggs)
+ .flatten()
.any(|(sp, suggestion)| sp.is_empty() && suggestion.is_empty())),
"Span must not be empty and have no suggestion"
);
diff --git a/compiler/rustc_errors/src/diagnostic_builder.rs b/compiler/rustc_errors/src/diagnostic_builder.rs
index a2ed98864..cbfee582d 100644
--- a/compiler/rustc_errors/src/diagnostic_builder.rs
+++ b/compiler/rustc_errors/src/diagnostic_builder.rs
@@ -1,7 +1,7 @@
use crate::diagnostic::IntoDiagnosticArg;
use crate::{
Diagnostic, DiagnosticId, DiagnosticMessage, DiagnosticStyledString, ErrorGuaranteed,
- SubdiagnosticMessage,
+ ExplicitBug, SubdiagnosticMessage,
};
use crate::{Handler, Level, MultiSpan, StashKey};
use rustc_lint_defs::Applicability;
@@ -12,6 +12,7 @@ use std::borrow::Cow;
use std::fmt::{self, Debug};
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
+use std::panic;
use std::thread::panicking;
/// Trait implemented by error types. This should not be implemented manually. Instead, use
@@ -308,6 +309,58 @@ impl EmissionGuarantee for Noted {
}
}
+/// Marker type which enables implementation of `create_bug` and `emit_bug` functions for
+/// bug struct diagnostics.
+#[derive(Copy, Clone)]
+pub struct Bug;
+
+impl<'a> DiagnosticBuilder<'a, Bug> {
+ /// Convenience function for internal use, clients should use one of the
+ /// `struct_*` methods on [`Handler`].
+ #[track_caller]
+ pub(crate) fn new_bug(handler: &'a Handler, message: impl Into<DiagnosticMessage>) -> Self {
+ let diagnostic = Diagnostic::new_with_code(Level::Bug, None, message);
+ Self::new_diagnostic_bug(handler, diagnostic)
+ }
+
+ /// Creates a new `DiagnosticBuilder` with an already constructed
+ /// diagnostic.
+ pub(crate) fn new_diagnostic_bug(handler: &'a Handler, diagnostic: Diagnostic) -> Self {
+ debug!("Created new diagnostic bug");
+ Self {
+ inner: DiagnosticBuilderInner {
+ state: DiagnosticBuilderState::Emittable(handler),
+ diagnostic: Box::new(diagnostic),
+ },
+ _marker: PhantomData,
+ }
+ }
+}
+
+impl EmissionGuarantee for Bug {
+ fn diagnostic_builder_emit_producing_guarantee(db: &mut DiagnosticBuilder<'_, Self>) -> Self {
+ match db.inner.state {
+ // First `.emit()` call, the `&Handler` is still available.
+ DiagnosticBuilderState::Emittable(handler) => {
+ db.inner.state = DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation;
+
+ handler.emit_diagnostic(&mut db.inner.diagnostic);
+ }
+ // `.emit()` was previously called, disallowed from repeating it.
+ DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation => {}
+ }
+ // Then panic. No need to return the marker type.
+ panic::panic_any(ExplicitBug);
+ }
+
+ fn make_diagnostic_builder(
+ handler: &Handler,
+ msg: impl Into<DiagnosticMessage>,
+ ) -> DiagnosticBuilder<'_, Self> {
+ DiagnosticBuilder::new_bug(handler, msg)
+ }
+}
+
impl<'a> DiagnosticBuilder<'a, !> {
/// Convenience function for internal use, clients should use one of the
/// `struct_*` methods on [`Handler`].
diff --git a/compiler/rustc_errors/src/diagnostic_impls.rs b/compiler/rustc_errors/src/diagnostic_impls.rs
index 7155db32e..dad5e98aa 100644
--- a/compiler/rustc_errors/src/diagnostic_impls.rs
+++ b/compiler/rustc_errors/src/diagnostic_impls.rs
@@ -9,6 +9,7 @@ use rustc_span::edition::Edition;
use rustc_span::symbol::{Ident, MacroRulesNormalizedIdent, Symbol};
use rustc_target::abi::TargetDataLayoutErrors;
use rustc_target::spec::{PanicStrategy, SplitDebuginfo, StackProtector, TargetTriple};
+use rustc_type_ir as type_ir;
use std::borrow::Cow;
use std::fmt;
use std::num::ParseIntError;
@@ -59,7 +60,7 @@ into_diagnostic_arg_using_display!(
i128,
u128,
std::io::Error,
- std::boxed::Box<dyn std::error::Error>,
+ Box<dyn std::error::Error>,
std::num::NonZeroU32,
hir::Target,
Edition,
@@ -152,6 +153,12 @@ impl IntoDiagnosticArg for ast::Path {
}
}
+impl IntoDiagnosticArg for &ast::Path {
+ fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+ DiagnosticArgValue::Str(Cow::Owned(pprust::path_to_string(self)))
+ }
+}
+
impl IntoDiagnosticArg for ast::token::Token {
fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
DiagnosticArgValue::Str(pprust::token_to_string(&self))
@@ -164,18 +171,15 @@ impl IntoDiagnosticArg for ast::token::TokenKind {
}
}
+impl IntoDiagnosticArg for type_ir::FloatTy {
+ fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+ DiagnosticArgValue::Str(Cow::Borrowed(self.name_str()))
+ }
+}
+
impl IntoDiagnosticArg for Level {
fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
- DiagnosticArgValue::Str(Cow::Borrowed(match self {
- Level::Allow => "-A",
- Level::Warn => "-W",
- Level::ForceWarn(_) => "--force-warn",
- Level::Deny => "-D",
- Level::Forbid => "-F",
- Level::Expect(_) => {
- unreachable!("lints with the level of `expect` should not run this code");
- }
- }))
+ DiagnosticArgValue::Str(Cow::Borrowed(self.to_cmd_flag()))
}
}
diff --git a/compiler/rustc_errors/src/emitter.rs b/compiler/rustc_errors/src/emitter.rs
index 4df2198fb..628e19999 100644
--- a/compiler/rustc_errors/src/emitter.rs
+++ b/compiler/rustc_errors/src/emitter.rs
@@ -28,6 +28,7 @@ use rustc_error_messages::{FluentArgs, SpanLabel};
use rustc_span::hygiene::{ExpnKind, MacroKind};
use std::borrow::Cow;
use std::cmp::{max, min, Reverse};
+use std::error::Report;
use std::io::prelude::*;
use std::io::{self, IsTerminal};
use std::iter;
@@ -250,7 +251,7 @@ pub trait Emitter: Translate {
let mut primary_span = diag.span.clone();
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);
+ let msg = self.translate_message(&sugg.msg, fluent_args).map_err(Report::new).unwrap();
if rest.is_empty() &&
// ^ if there is only one suggestion
// don't display multi-suggestions as labels
@@ -845,7 +846,10 @@ impl EmitterWriter {
// 3 | |
// 4 | | }
// | |_^ test
- if let [ann] = &line.annotations[..] {
+ let mut buffer_ops = vec![];
+ let mut annotations = vec![];
+ let mut short_start = true;
+ for ann in &line.annotations {
if let AnnotationType::MultilineStart(depth) = ann.annotation_type {
if source_string.chars().take(ann.start_col).all(|c| c.is_whitespace()) {
let style = if ann.is_primary {
@@ -853,11 +857,24 @@ impl EmitterWriter {
} else {
Style::UnderlineSecondary
};
- buffer.putc(line_offset, width_offset + depth - 1, '/', style);
- return vec![(depth, style)];
+ annotations.push((depth, style));
+ buffer_ops.push((line_offset, width_offset + depth - 1, '/', style));
+ } else {
+ short_start = false;
+ break;
}
+ } else if let AnnotationType::MultilineLine(_) = ann.annotation_type {
+ } else {
+ short_start = false;
+ break;
}
}
+ if short_start {
+ for (y, x, c, s) in buffer_ops {
+ buffer.putc(y, x, c, s);
+ }
+ return annotations;
+ }
// We want to display like this:
//
@@ -1308,8 +1325,8 @@ impl EmitterWriter {
// see how it *looks* with
// very *weird* formats
// see?
- for &(ref text, ref style) in msg.iter() {
- let text = self.translate_message(text, args);
+ for (text, style) in msg.iter() {
+ let text = self.translate_message(text, args).map_err(Report::new).unwrap();
let lines = text.split('\n').collect::<Vec<_>>();
if lines.len() > 1 {
for (i, line) in lines.iter().enumerate() {
@@ -1370,8 +1387,8 @@ impl EmitterWriter {
buffer.append(0, ": ", header_style);
label_width += 2;
}
- for &(ref text, _) in msg.iter() {
- let text = self.translate_message(text, args);
+ for (text, _) in msg.iter() {
+ let text = self.translate_message(text, args).map_err(Report::new).unwrap();
// Account for newlines to align output to its label.
for (line, text) in normalize_whitespace(&text).lines().enumerate() {
buffer.append(
@@ -1408,49 +1425,58 @@ impl EmitterWriter {
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() {
+ for (annotation_id, line) in annotated_file.lines.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);
+
+ let labels: Vec<_> = annotations
+ .iter()
+ .filter_map(|a| Some((a.label.as_ref()?, a.is_primary)))
+ .filter(|(l, _)| !l.is_empty())
+ .collect();
+
+ if annotation_id == 0 || !labels.is_empty() {
+ 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);
+ } else {
+ 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);
+ }
+ for (label, is_primary) in labels.into_iter() {
+ let style = if is_primary {
+ Style::LabelPrimary
+ } else {
+ Style::LabelSecondary
+ };
+ 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, " = note: ", style);
+ for _ in 0..max_line_num_len {
+ buffer.prepend(line_idx, " ", Style::NoStyle);
+ }
+ buffer.append(line_idx, label, style);
+ line_idx += 1;
}
}
}
@@ -1765,7 +1791,7 @@ impl EmitterWriter {
if let Some(span) = span.primary_span() {
// Compare the primary span of the diagnostic with the span of the suggestion
- // being emitted. If they belong to the same file, we don't *need* to show the
+ // being emitted. If they belong to the same file, we don't *need* to show the
// file name, saving in verbosity, but if it *isn't* we do need it, otherwise we're
// telling users to make a change but not clarifying *where*.
let loc = sm.lookup_char_pos(parts[0].span.lo());
@@ -2276,7 +2302,9 @@ impl FileWithAnnotatedLines {
hi.col_display += 1;
}
- let label = label.as_ref().map(|m| emitter.translate_message(m, args).to_string());
+ let label = label.as_ref().map(|m| {
+ emitter.translate_message(m, args).map_err(Report::new).unwrap().to_string()
+ });
if lo.line != hi.line {
let ml = MultilineAnnotation {
@@ -2304,7 +2332,7 @@ impl FileWithAnnotatedLines {
}
// Find overlapping multiline annotations, put them at different depths
- multiline_annotations.sort_by_key(|&(_, ref ml)| (ml.line_start, usize::MAX - ml.line_end));
+ multiline_annotations.sort_by_key(|(_, 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
@@ -2501,11 +2529,11 @@ fn emit_to_destination(
//
// On Unix systems, we write into a buffered terminal rather than directly to a terminal. When
// the .flush() is called we take the buffer created from the buffered writes and write it at
- // one shot. Because the Unix systems use ANSI for the colors, which is a text-based styling
+ // one shot. Because the Unix systems use ANSI for the colors, which is a text-based styling
// scheme, this buffered approach works and maintains the styling.
//
// On Windows, styling happens through calls to a terminal API. This prevents us from using the
- // same buffering approach. Instead, we use a global Windows mutex, which we acquire long
+ // same buffering approach. Instead, we use a global Windows mutex, which we acquire long
// enough to output the full error message, then we release.
let _buffer_lock = lock::acquire_global_lock("rustc_errors");
for (pos, line) in rendered_buffer.iter().enumerate() {
diff --git a/compiler/rustc_errors/src/error.rs b/compiler/rustc_errors/src/error.rs
new file mode 100644
index 000000000..ec0a2fe8c
--- /dev/null
+++ b/compiler/rustc_errors/src/error.rs
@@ -0,0 +1,137 @@
+use rustc_error_messages::{
+ fluent_bundle::resolver::errors::{ReferenceKind, ResolverError},
+ FluentArgs, FluentError,
+};
+use std::borrow::Cow;
+use std::error::Error;
+use std::fmt;
+
+#[derive(Debug)]
+pub enum TranslateError<'args> {
+ One {
+ id: &'args Cow<'args, str>,
+ args: &'args FluentArgs<'args>,
+ kind: TranslateErrorKind<'args>,
+ },
+ Two {
+ primary: Box<TranslateError<'args>>,
+ fallback: Box<TranslateError<'args>>,
+ },
+}
+
+impl<'args> TranslateError<'args> {
+ pub fn message(id: &'args Cow<'args, str>, args: &'args FluentArgs<'args>) -> Self {
+ Self::One { id, args, kind: TranslateErrorKind::MessageMissing }
+ }
+ pub fn primary(id: &'args Cow<'args, str>, args: &'args FluentArgs<'args>) -> Self {
+ Self::One { id, args, kind: TranslateErrorKind::PrimaryBundleMissing }
+ }
+ pub fn attribute(
+ id: &'args Cow<'args, str>,
+ args: &'args FluentArgs<'args>,
+ attr: &'args str,
+ ) -> Self {
+ Self::One { id, args, kind: TranslateErrorKind::AttributeMissing { attr } }
+ }
+ pub fn value(id: &'args Cow<'args, str>, args: &'args FluentArgs<'args>) -> Self {
+ Self::One { id, args, kind: TranslateErrorKind::ValueMissing }
+ }
+
+ pub fn fluent(
+ id: &'args Cow<'args, str>,
+ args: &'args FluentArgs<'args>,
+ errs: Vec<FluentError>,
+ ) -> Self {
+ Self::One { id, args, kind: TranslateErrorKind::Fluent { errs } }
+ }
+
+ pub fn and(self, fallback: TranslateError<'args>) -> TranslateError<'args> {
+ Self::Two { primary: Box::new(self), fallback: Box::new(fallback) }
+ }
+}
+
+#[derive(Debug)]
+pub enum TranslateErrorKind<'args> {
+ MessageMissing,
+ PrimaryBundleMissing,
+ AttributeMissing { attr: &'args str },
+ ValueMissing,
+ Fluent { errs: Vec<FluentError> },
+}
+
+impl fmt::Display for TranslateError<'_> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ use TranslateErrorKind::*;
+
+ match self {
+ Self::One { id, args, kind } => {
+ writeln!(f, "failed while formatting fluent string `{id}`: ")?;
+ match kind {
+ MessageMissing => writeln!(f, "message was missing")?,
+ PrimaryBundleMissing => writeln!(f, "the primary bundle was missing")?,
+ AttributeMissing { attr } => {
+ writeln!(f, "the attribute `{attr}` was missing")?;
+ writeln!(f, "help: add `.{attr} = <message>`")?;
+ }
+ ValueMissing => writeln!(f, "the value was missing")?,
+ Fluent { errs } => {
+ for err in errs {
+ match err {
+ FluentError::ResolverError(ResolverError::Reference(
+ ReferenceKind::Message { id, .. }
+ | ReferenceKind::Variable { id, .. },
+ )) => {
+ if args.iter().any(|(arg_id, _)| arg_id == id) {
+ writeln!(
+ f,
+ "argument `{id}` exists but was not referenced correctly"
+ )?;
+ writeln!(f, "help: try using `{{${id}}}` instead")?;
+ } else {
+ writeln!(
+ f,
+ "the fluent string has an argument `{id}` that was not found."
+ )?;
+ let vars: Vec<&str> =
+ args.iter().map(|(a, _v)| a).collect();
+ match &*vars {
+ [] => writeln!(f, "help: no arguments are available")?,
+ [one] => writeln!(
+ f,
+ "help: the argument `{one}` is available"
+ )?,
+ [first, middle @ .., last] => {
+ write!(f, "help: the arguments `{first}`")?;
+ for a in middle {
+ write!(f, ", `{a}`")?;
+ }
+ writeln!(f, " and `{last}` are available")?;
+ }
+ }
+ }
+ }
+ _ => writeln!(f, "{err}")?,
+ }
+ }
+ }
+ }
+ }
+ // If someone cares about primary bundles, they'll probably notice it's missing
+ // regardless or will be using `debug_assertions`
+ // so we skip the arm below this one to avoid confusing the regular user.
+ Self::Two { primary: box Self::One { kind: PrimaryBundleMissing, .. }, fallback } => {
+ fmt::Display::fmt(fallback, f)?;
+ }
+ Self::Two { primary, fallback } => {
+ writeln!(
+ f,
+ "first, fluent formatting using the primary bundle failed:\n {primary}\n \
+ while attempting to recover by using the fallback bundle instead, another error occurred:\n{fallback}"
+ )?;
+ }
+ }
+ Ok(())
+ }
+}
+
+impl Error for TranslateError<'_> {}
diff --git a/compiler/rustc_errors/src/json.rs b/compiler/rustc_errors/src/json.rs
index a37073d8f..dc38b8725 100644
--- a/compiler/rustc_errors/src/json.rs
+++ b/compiler/rustc_errors/src/json.rs
@@ -24,6 +24,7 @@ use rustc_data_structures::sync::Lrc;
use rustc_error_messages::FluentArgs;
use rustc_span::hygiene::ExpnData;
use rustc_span::Span;
+use std::error::Report;
use std::io::{self, Write};
use std::path::Path;
use std::sync::{Arc, Mutex};
@@ -321,7 +322,8 @@ impl Diagnostic {
fn from_errors_diagnostic(diag: &crate::Diagnostic, je: &JsonEmitter) -> Diagnostic {
let args = to_fluent_args(diag.args());
let sugg = diag.suggestions.iter().flatten().map(|sugg| {
- let translated_message = je.translate_message(&sugg.msg, &args);
+ let translated_message =
+ je.translate_message(&sugg.msg, &args).map_err(Report::new).unwrap();
Diagnostic {
message: translated_message.to_string(),
code: None,
@@ -411,7 +413,10 @@ impl DiagnosticSpan {
Self::from_span_etc(
span.span,
span.is_primary,
- span.label.as_ref().map(|m| je.translate_message(m, args)).map(|m| m.to_string()),
+ span.label
+ .as_ref()
+ .map(|m| je.translate_message(m, args).unwrap())
+ .map(|m| m.to_string()),
suggestion,
je,
)
diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs
index eb0506c45..535812fb0 100644
--- a/compiler/rustc_errors/src/lib.rs
+++ b/compiler/rustc_errors/src/lib.rs
@@ -11,6 +11,10 @@
#![feature(never_type)]
#![feature(result_option_inspect)]
#![feature(rustc_attrs)]
+#![feature(yeet_expr)]
+#![feature(try_blocks)]
+#![feature(box_patterns)]
+#![feature(error_reporter)]
#![allow(incomplete_features)]
#[macro_use]
@@ -41,11 +45,12 @@ use rustc_span::HashStableContext;
use rustc_span::{Loc, Span};
use std::borrow::Cow;
+use std::error::Report;
+use std::fmt;
use std::hash::Hash;
use std::num::NonZeroUsize;
use std::panic;
use std::path::Path;
-use std::{error, fmt};
use termcolor::{Color, ColorSpec};
@@ -54,11 +59,14 @@ mod diagnostic;
mod diagnostic_builder;
mod diagnostic_impls;
pub mod emitter;
+pub mod error;
pub mod json;
mod lock;
pub mod registry;
mod snippet;
mod styled_buffer;
+#[cfg(test)]
+mod tests;
pub mod translation;
pub use diagnostic_builder::IntoDiagnostic;
@@ -324,7 +332,7 @@ impl CodeSuggestion {
// Account for the difference between the width of the current code and the
// snippet being suggested, so that the *later* suggestions are correctly
// aligned on the screen.
- acc += len as isize - (cur_hi.col.0 - cur_lo.col.0) as isize;
+ acc += len - (cur_hi.col.0 - cur_lo.col.0) as isize;
}
prev_hi = cur_hi;
prev_line = sf.get_line(prev_hi.line - 1);
@@ -361,16 +369,11 @@ pub use rustc_span::fatal_error::{FatalError, FatalErrorMarker};
/// Signifies that the compiler died with an explicit call to `.bug`
/// or `.span_bug` rather than a failed assertion, etc.
-#[derive(Copy, Clone, Debug)]
pub struct ExplicitBug;
-impl fmt::Display for ExplicitBug {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "parser internal bug")
- }
-}
-
-impl error::Error for ExplicitBug {}
+/// Signifies that the compiler died with an explicit call to `.delay_*_bug`
+/// rather than a failed assertion, etc.
+pub struct DelayedBugPanic;
pub use diagnostic::{
AddToDiagnostic, DecorateLint, Diagnostic, DiagnosticArg, DiagnosticArgValue, DiagnosticId,
@@ -403,7 +406,7 @@ struct HandlerInner {
warn_count: usize,
deduplicated_err_count: usize,
emitter: Box<dyn Emitter + sync::Send>,
- delayed_span_bugs: Vec<Diagnostic>,
+ delayed_span_bugs: Vec<DelayedDiagnostic>,
delayed_good_path_bugs: Vec<DelayedDiagnostic>,
/// This flag indicates that an expected diagnostic was emitted and suppressed.
/// This is used for the `delayed_good_path_bugs` check.
@@ -473,10 +476,12 @@ pub enum StashKey {
CallAssocMethod,
}
-fn default_track_diagnostic(_: &Diagnostic) {}
+fn default_track_diagnostic(d: &mut Diagnostic, f: &mut dyn FnMut(&mut Diagnostic)) {
+ (*f)(d)
+}
-pub static TRACK_DIAGNOSTICS: AtomicRef<fn(&Diagnostic)> =
- AtomicRef::new(&(default_track_diagnostic as fn(&_)));
+pub static TRACK_DIAGNOSTICS: AtomicRef<fn(&mut Diagnostic, &mut dyn FnMut(&mut Diagnostic))> =
+ AtomicRef::new(&(default_track_diagnostic as _));
#[derive(Copy, Clone, Default)]
pub struct HandlerFlags {
@@ -518,7 +523,7 @@ impl Drop for HandlerInner {
if !self.has_any_message() && !self.suppressed_expected_diag {
let bugs = std::mem::replace(&mut self.delayed_good_path_bugs, Vec::new());
self.flush_delayed(
- bugs.into_iter().map(DelayedDiagnostic::decorate),
+ bugs,
"no warnings or errors encountered even though `delayed_good_path_bugs` issued",
);
}
@@ -619,7 +624,14 @@ impl Handler {
) -> SubdiagnosticMessage {
let inner = self.inner.borrow();
let args = crate::translation::to_fluent_args(args);
- SubdiagnosticMessage::Eager(inner.emitter.translate_message(&message, &args).to_string())
+ SubdiagnosticMessage::Eager(
+ inner
+ .emitter
+ .translate_message(&message, &args)
+ .map_err(Report::new)
+ .unwrap()
+ .to_string(),
+ )
}
// This is here to not allow mutation of flags;
@@ -653,17 +665,19 @@ impl Handler {
/// 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);
+ inner.stash((span.with_parent(None), key), diag);
}
/// Steal a previously stashed diagnostic with the given `Span` and [`StashKey`] as the key.
pub fn steal_diagnostic(&self, span: Span, key: StashKey) -> Option<DiagnosticBuilder<'_, ()>> {
let mut inner = self.inner.borrow_mut();
- inner.steal((span, key)).map(|diag| DiagnosticBuilder::new_diagnostic(self, diag))
+ inner
+ .steal((span.with_parent(None), key))
+ .map(|diag| DiagnosticBuilder::new_diagnostic(self, diag))
}
pub fn has_stashed_diagnostic(&self, span: Span, key: StashKey) -> bool {
- self.inner.borrow().stashed_diagnostics.get(&(span, key)).is_some()
+ self.inner.borrow().stashed_diagnostics.get(&(span.with_parent(None), key)).is_some()
}
/// Emit all stashed diagnostics.
@@ -973,6 +987,7 @@ impl Handler {
self.inner.borrow_mut().span_bug(span, msg)
}
+ /// For documentation on this, see `Session::delay_span_bug`.
#[track_caller]
pub fn delay_span_bug(
&self,
@@ -1127,6 +1142,20 @@ impl Handler {
self.create_fatal(fatal).emit()
}
+ pub fn create_bug<'a>(
+ &'a self,
+ bug: impl IntoDiagnostic<'a, diagnostic_builder::Bug>,
+ ) -> DiagnosticBuilder<'a, diagnostic_builder::Bug> {
+ bug.into_diagnostic(self)
+ }
+
+ pub fn emit_bug<'a>(
+ &'a self,
+ bug: impl IntoDiagnostic<'a, diagnostic_builder::Bug>,
+ ) -> diagnostic_builder::Bug {
+ self.create_bug(bug).emit()
+ }
+
fn emit_diag_at_span(
&self,
mut diag: Diagnostic,
@@ -1212,8 +1241,8 @@ impl HandlerInner {
self.taught_diagnostics.insert(code.clone())
}
- fn force_print_diagnostic(&mut self, mut db: Diagnostic) {
- self.emitter.emit_diagnostic(&mut db);
+ fn force_print_diagnostic(&mut self, db: Diagnostic) {
+ self.emitter.emit_diagnostic(&db);
}
/// Emit all stashed diagnostics.
@@ -1263,7 +1292,9 @@ impl HandlerInner {
// once *any* errors were emitted (and truncate `delayed_span_bugs`
// when an error is first emitted, also), but maybe there's a case
// in which that's not sound? otherwise this is really inefficient.
- self.delayed_span_bugs.push(diagnostic.clone());
+ let backtrace = std::backtrace::Backtrace::force_capture();
+ self.delayed_span_bugs
+ .push(DelayedDiagnostic::with_backtrace(diagnostic.clone(), backtrace));
if !self.flags.report_delayed_bugs {
return Some(ErrorGuaranteed::unchecked_claim_error_was_emitted());
@@ -1288,67 +1319,69 @@ impl HandlerInner {
&& !diagnostic.is_force_warn()
{
if diagnostic.has_future_breakage() {
- (*TRACK_DIAGNOSTICS)(diagnostic);
+ (*TRACK_DIAGNOSTICS)(diagnostic, &mut |_| {});
}
return None;
}
- (*TRACK_DIAGNOSTICS)(diagnostic);
-
if matches!(diagnostic.level, Level::Expect(_) | Level::Allow) {
+ (*TRACK_DIAGNOSTICS)(diagnostic, &mut |_| {});
return None;
}
- if let Some(ref code) = diagnostic.code {
- self.emitted_diagnostic_codes.insert(code.clone());
- }
-
- let already_emitted = |this: &mut Self| {
- let mut hasher = StableHasher::new();
- diagnostic.hash(&mut hasher);
- let diagnostic_hash = hasher.finish();
- !this.emitted_diagnostics.insert(diagnostic_hash)
- };
+ let mut guaranteed = None;
+ (*TRACK_DIAGNOSTICS)(diagnostic, &mut |diagnostic| {
+ if let Some(ref code) = diagnostic.code {
+ self.emitted_diagnostic_codes.insert(code.clone());
+ }
- // Only emit the diagnostic if we've been asked to deduplicate or
- // haven't already emitted an equivalent diagnostic.
- if !(self.flags.deduplicate_diagnostics && already_emitted(self)) {
- debug!(?diagnostic);
- debug!(?self.emitted_diagnostics);
- let already_emitted_sub = |sub: &mut SubDiagnostic| {
- debug!(?sub);
- if sub.level != Level::OnceNote {
- return false;
- }
+ let already_emitted = |this: &mut Self| {
let mut hasher = StableHasher::new();
- sub.hash(&mut hasher);
+ diagnostic.hash(&mut hasher);
let diagnostic_hash = hasher.finish();
- debug!(?diagnostic_hash);
- !self.emitted_diagnostics.insert(diagnostic_hash)
+ !this.emitted_diagnostics.insert(diagnostic_hash)
};
- diagnostic.children.drain_filter(already_emitted_sub).for_each(|_| {});
-
- self.emitter.emit_diagnostic(diagnostic);
- if diagnostic.is_error() {
- self.deduplicated_err_count += 1;
- } else if let Warning(_) = diagnostic.level {
- self.deduplicated_warn_count += 1;
+ // Only emit the diagnostic if we've been asked to deduplicate or
+ // haven't already emitted an equivalent diagnostic.
+ if !(self.flags.deduplicate_diagnostics && already_emitted(self)) {
+ debug!(?diagnostic);
+ debug!(?self.emitted_diagnostics);
+ let already_emitted_sub = |sub: &mut SubDiagnostic| {
+ debug!(?sub);
+ if sub.level != Level::OnceNote {
+ return false;
+ }
+ let mut hasher = StableHasher::new();
+ sub.hash(&mut hasher);
+ let diagnostic_hash = hasher.finish();
+ debug!(?diagnostic_hash);
+ !self.emitted_diagnostics.insert(diagnostic_hash)
+ };
+
+ diagnostic.children.drain_filter(already_emitted_sub).for_each(|_| {});
+
+ self.emitter.emit_diagnostic(diagnostic);
+ if diagnostic.is_error() {
+ self.deduplicated_err_count += 1;
+ } else if let Warning(_) = diagnostic.level {
+ self.deduplicated_warn_count += 1;
+ }
}
- }
- if diagnostic.is_error() {
- if matches!(diagnostic.level, Level::Error { lint: true }) {
- self.bump_lint_err_count();
+ if diagnostic.is_error() {
+ if matches!(diagnostic.level, Level::Error { lint: true }) {
+ self.bump_lint_err_count();
+ } else {
+ self.bump_err_count();
+ }
+
+ guaranteed = Some(ErrorGuaranteed::unchecked_claim_error_was_emitted());
} else {
- self.bump_err_count();
+ self.bump_warn_count();
}
+ });
- Some(ErrorGuaranteed::unchecked_claim_error_was_emitted())
- } else {
- self.bump_warn_count();
-
- None
- }
+ guaranteed
}
fn emit_artifact_notification(&mut self, path: &Path, artifact_type: &str) {
@@ -1518,6 +1551,7 @@ impl HandlerInner {
self.emit_diagnostic(diag.set_span(sp));
}
+ /// For documentation on this, see `Session::delay_span_bug`.
#[track_caller]
fn delay_span_bug(
&mut self,
@@ -1535,7 +1569,6 @@ impl HandlerInner {
}
let mut diagnostic = Diagnostic::new(Level::DelayedBug, msg);
diagnostic.set_span(sp.into());
- diagnostic.note(&format!("delayed at {}", std::panic::Location::caller()));
self.emit_diagnostic(&mut diagnostic).unwrap()
}
@@ -1578,11 +1611,13 @@ impl HandlerInner {
fn flush_delayed(
&mut self,
- bugs: impl IntoIterator<Item = Diagnostic>,
+ bugs: impl IntoIterator<Item = DelayedDiagnostic>,
explanation: impl Into<DiagnosticMessage> + Copy,
) {
let mut no_bugs = true;
- for mut bug in bugs {
+ for bug in bugs {
+ let mut bug = bug.decorate();
+
if no_bugs {
// Put the overall explanation before the `DelayedBug`s, to
// frame them better (e.g. separate warnings from them).
@@ -1605,9 +1640,9 @@ impl HandlerInner {
self.emit_diagnostic(&mut bug);
}
- // Panic with `ExplicitBug` to avoid "unexpected panic" messages.
+ // Panic with `DelayedBugPanic` to avoid "unexpected panic" messages.
if !no_bugs {
- panic::panic_any(ExplicitBug);
+ panic::panic_any(DelayedBugPanic);
}
}
diff --git a/compiler/rustc_errors/src/tests.rs b/compiler/rustc_errors/src/tests.rs
new file mode 100644
index 000000000..52103e460
--- /dev/null
+++ b/compiler/rustc_errors/src/tests.rs
@@ -0,0 +1,188 @@
+use crate::error::{TranslateError, TranslateErrorKind};
+use crate::fluent_bundle::*;
+use crate::translation::Translate;
+use crate::FluentBundle;
+use rustc_data_structures::sync::Lrc;
+use rustc_error_messages::fluent_bundle::resolver::errors::{ReferenceKind, ResolverError};
+use rustc_error_messages::langid;
+use rustc_error_messages::DiagnosticMessage;
+
+struct Dummy {
+ bundle: FluentBundle,
+}
+
+impl Translate for Dummy {
+ fn fluent_bundle(&self) -> Option<&Lrc<FluentBundle>> {
+ None
+ }
+
+ fn fallback_fluent_bundle(&self) -> &FluentBundle {
+ &self.bundle
+ }
+}
+
+fn make_dummy(ftl: &'static str) -> Dummy {
+ let resource = FluentResource::try_new(ftl.into()).expect("Failed to parse an FTL string.");
+
+ let langid_en = langid!("en-US");
+
+ #[cfg(parallel_compiler)]
+ let mut bundle = FluentBundle::new_concurrent(vec![langid_en]);
+
+ #[cfg(not(parallel_compiler))]
+ let mut bundle = FluentBundle::new(vec![langid_en]);
+
+ bundle.add_resource(resource).expect("Failed to add FTL resources to the bundle.");
+
+ Dummy { bundle }
+}
+
+#[test]
+fn wellformed_fluent() {
+ let dummy = make_dummy("mir_build_borrow_of_moved_value = borrow of moved value
+ .label = value moved into `{$name}` here
+ .occurs_because_label = move occurs because `{$name}` has type `{$ty}` which does not implement the `Copy` trait
+ .value_borrowed_label = value borrowed here after move
+ .suggestion = borrow this binding in the pattern to avoid moving the value");
+
+ let mut args = FluentArgs::new();
+ args.set("name", "Foo");
+ args.set("ty", "std::string::String");
+ {
+ let message = DiagnosticMessage::FluentIdentifier(
+ "mir_build_borrow_of_moved_value".into(),
+ Some("suggestion".into()),
+ );
+
+ assert_eq!(
+ dummy.translate_message(&message, &args).unwrap(),
+ "borrow this binding in the pattern to avoid moving the value"
+ );
+ }
+
+ {
+ let message = DiagnosticMessage::FluentIdentifier(
+ "mir_build_borrow_of_moved_value".into(),
+ Some("value_borrowed_label".into()),
+ );
+
+ assert_eq!(
+ dummy.translate_message(&message, &args).unwrap(),
+ "value borrowed here after move"
+ );
+ }
+
+ {
+ let message = DiagnosticMessage::FluentIdentifier(
+ "mir_build_borrow_of_moved_value".into(),
+ Some("occurs_because_label".into()),
+ );
+
+ assert_eq!(
+ dummy.translate_message(&message, &args).unwrap(),
+ "move occurs because `\u{2068}Foo\u{2069}` has type `\u{2068}std::string::String\u{2069}` which does not implement the `Copy` trait"
+ );
+
+ {
+ let message = DiagnosticMessage::FluentIdentifier(
+ "mir_build_borrow_of_moved_value".into(),
+ Some("label".into()),
+ );
+
+ assert_eq!(
+ dummy.translate_message(&message, &args).unwrap(),
+ "value moved into `\u{2068}Foo\u{2069}` here"
+ );
+ }
+ }
+}
+
+#[test]
+fn misformed_fluent() {
+ let dummy = make_dummy("mir_build_borrow_of_moved_value = borrow of moved value
+ .label = value moved into `{name}` here
+ .occurs_because_label = move occurs because `{$oops}` has type `{$ty}` which does not implement the `Copy` trait
+ .suggestion = borrow this binding in the pattern to avoid moving the value");
+
+ let mut args = FluentArgs::new();
+ args.set("name", "Foo");
+ args.set("ty", "std::string::String");
+ {
+ let message = DiagnosticMessage::FluentIdentifier(
+ "mir_build_borrow_of_moved_value".into(),
+ Some("value_borrowed_label".into()),
+ );
+
+ let err = dummy.translate_message(&message, &args).unwrap_err();
+ assert!(
+ matches!(
+ &err,
+ TranslateError::Two {
+ primary: box TranslateError::One {
+ kind: TranslateErrorKind::PrimaryBundleMissing,
+ ..
+ },
+ fallback: box TranslateError::One {
+ kind: TranslateErrorKind::AttributeMissing { attr: "value_borrowed_label" },
+ ..
+ }
+ }
+ ),
+ "{err:#?}"
+ );
+ assert_eq!(
+ format!("{err}"),
+ "failed while formatting fluent string `mir_build_borrow_of_moved_value`: \nthe attribute `value_borrowed_label` was missing\nhelp: add `.value_borrowed_label = <message>`\n"
+ );
+ }
+
+ {
+ let message = DiagnosticMessage::FluentIdentifier(
+ "mir_build_borrow_of_moved_value".into(),
+ Some("label".into()),
+ );
+
+ let err = dummy.translate_message(&message, &args).unwrap_err();
+ if let TranslateError::Two {
+ primary: box TranslateError::One { kind: TranslateErrorKind::PrimaryBundleMissing, .. },
+ fallback: box TranslateError::One { kind: TranslateErrorKind::Fluent { errs }, .. },
+ } = &err
+ && let [FluentError::ResolverError(ResolverError::Reference(
+ ReferenceKind::Message { id, .. }
+ | ReferenceKind::Variable { id, .. },
+ ))] = &**errs
+ && id == "name"
+ {} else {
+ panic!("{err:#?}")
+ };
+ assert_eq!(
+ format!("{err}"),
+ "failed while formatting fluent string `mir_build_borrow_of_moved_value`: \nargument `name` exists but was not referenced correctly\nhelp: try using `{$name}` instead\n"
+ );
+ }
+
+ {
+ let message = DiagnosticMessage::FluentIdentifier(
+ "mir_build_borrow_of_moved_value".into(),
+ Some("occurs_because_label".into()),
+ );
+
+ let err = dummy.translate_message(&message, &args).unwrap_err();
+ if let TranslateError::Two {
+ primary: box TranslateError::One { kind: TranslateErrorKind::PrimaryBundleMissing, .. },
+ fallback: box TranslateError::One { kind: TranslateErrorKind::Fluent { errs }, .. },
+ } = &err
+ && let [FluentError::ResolverError(ResolverError::Reference(
+ ReferenceKind::Message { id, .. }
+ | ReferenceKind::Variable { id, .. },
+ ))] = &**errs
+ && id == "oops"
+ {} else {
+ panic!("{err:#?}")
+ };
+ assert_eq!(
+ format!("{err}"),
+ "failed while formatting fluent string `mir_build_borrow_of_moved_value`: \nthe fluent string has an argument `oops` that was not found.\nhelp: the arguments `name` and `ty` are available\n"
+ );
+ }
+}
diff --git a/compiler/rustc_errors/src/translation.rs b/compiler/rustc_errors/src/translation.rs
index afd660ff1..addfc9726 100644
--- a/compiler/rustc_errors/src/translation.rs
+++ b/compiler/rustc_errors/src/translation.rs
@@ -1,11 +1,10 @@
+use crate::error::TranslateError;
use crate::snippet::Style;
use crate::{DiagnosticArg, DiagnosticMessage, FluentBundle};
use rustc_data_structures::sync::Lrc;
-use rustc_error_messages::{
- fluent_bundle::resolver::errors::{ReferenceKind, ResolverError},
- FluentArgs, FluentError,
-};
+use rustc_error_messages::FluentArgs;
use std::borrow::Cow;
+use std::error::Report;
/// Convert diagnostic arguments (a rustc internal type that exists to implement
/// `Encodable`/`Decodable`) into `FluentArgs` which is necessary to perform translation.
@@ -46,7 +45,10 @@ pub trait Translate {
args: &FluentArgs<'_>,
) -> Cow<'_, str> {
Cow::Owned(
- messages.iter().map(|(m, _)| self.translate_message(m, args)).collect::<String>(),
+ messages
+ .iter()
+ .map(|(m, _)| self.translate_message(m, args).map_err(Report::new).unwrap())
+ .collect::<String>(),
)
}
@@ -55,83 +57,56 @@ pub trait Translate {
&'a self,
message: &'a DiagnosticMessage,
args: &'a FluentArgs<'_>,
- ) -> Cow<'_, str> {
+ ) -> Result<Cow<'_, str>, TranslateError<'_>> {
trace!(?message, ?args);
let (identifier, attr) = match message {
DiagnosticMessage::Str(msg) | DiagnosticMessage::Eager(msg) => {
- return Cow::Borrowed(msg);
+ return Ok(Cow::Borrowed(msg));
}
DiagnosticMessage::FluentIdentifier(identifier, attr) => (identifier, attr),
};
+ let translate_with_bundle =
+ |bundle: &'a FluentBundle| -> Result<Cow<'_, str>, TranslateError<'_>> {
+ let message = bundle
+ .get_message(identifier)
+ .ok_or(TranslateError::message(identifier, args))?;
+ let value = match attr {
+ Some(attr) => message
+ .get_attribute(attr)
+ .ok_or(TranslateError::attribute(identifier, args, attr))?
+ .value(),
+ None => message.value().ok_or(TranslateError::value(identifier, args))?,
+ };
+ debug!(?message, ?value);
- let translate_with_bundle = |bundle: &'a FluentBundle| -> Option<(Cow<'_, str>, Vec<_>)> {
- let message = bundle.get_message(identifier)?;
- let value = match attr {
- Some(attr) => message.get_attribute(attr)?.value(),
- None => message.value()?,
+ let mut errs = vec![];
+ let translated = bundle.format_pattern(value, Some(args), &mut errs);
+ debug!(?translated, ?errs);
+ if errs.is_empty() {
+ Ok(translated)
+ } else {
+ Err(TranslateError::fluent(identifier, args, errs))
+ }
};
- debug!(?message, ?value);
-
- let mut errs = vec![];
- let translated = bundle.format_pattern(value, Some(args), &mut errs);
- debug!(?translated, ?errs);
- Some((translated, errs))
- };
- self.fluent_bundle()
- .and_then(|bundle| translate_with_bundle(bundle))
- // If `translate_with_bundle` returns `None` with the primary bundle, this is likely
- // just that the primary bundle doesn't contain the message being translated, so
- // proceed to the fallback bundle.
- //
- // However, when errors are produced from translation, then that means the translation
- // is broken (e.g. `{$foo}` exists in a translation but `foo` isn't provided).
- //
- // In debug builds, assert so that compiler devs can spot the broken translation and
- // fix it..
- .inspect(|(_, errs)| {
- debug_assert!(
- errs.is_empty(),
- "identifier: {:?}, attr: {:?}, args: {:?}, errors: {:?}",
- identifier,
- attr,
- args,
- errs
- );
- })
- // ..otherwise, for end users, an error about this wouldn't be useful or actionable, so
- // just hide it and try with the fallback bundle.
- .filter(|(_, errs)| errs.is_empty())
- .or_else(|| translate_with_bundle(self.fallback_fluent_bundle()))
- .map(|(translated, errs)| {
- // Always bail out for errors with the fallback bundle.
+ try {
+ match self.fluent_bundle().map(|b| translate_with_bundle(b)) {
+ // The primary bundle was present and translation succeeded
+ Some(Ok(t)) => t,
- let mut help_messages = vec![];
+ // Always yeet out for errors on debug
+ Some(Err(primary)) if cfg!(debug_assertions) => do yeet primary,
- 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"));
- }
- _ => {}
- }
- }
+ // If `translate_with_bundle` returns `Err` with the primary bundle, this is likely
+ // just that the primary bundle doesn't contain the message being translated or
+ // something else went wrong) so proceed to the fallback bundle.
+ Some(Err(primary)) => translate_with_bundle(self.fallback_fluent_bundle())
+ .map_err(|fallback| primary.and(fallback))?,
- 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")
+ // The primary bundle is missing, proceed to the fallback bundle
+ None => translate_with_bundle(self.fallback_fluent_bundle())
+ .map_err(|fallback| TranslateError::primary(identifier, args).and(fallback))?,
+ }
+ }
}
}