Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve fluent error messages #106427

Merged
merged 3 commits into from
Jan 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions compiler/rustc_errors/src/emitter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1325,7 +1326,7 @@ impl EmitterWriter {
// very *weird* formats
// see?
for (text, style) in msg.iter() {
let text = self.translate_message(text, args);
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() {
Expand Down Expand Up @@ -1387,7 +1388,7 @@ impl EmitterWriter {
label_width += 2;
}
for (text, _) in msg.iter() {
let text = self.translate_message(text, args);
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(
Expand Down Expand Up @@ -2301,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 {
Expand Down
137 changes: 137 additions & 0 deletions compiler/rustc_errors/src/error.rs
Original file line number Diff line number Diff line change
@@ -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<'_> {}
9 changes: 7 additions & 2 deletions compiler/rustc_errors/src/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
)
Expand Down
17 changes: 16 additions & 1 deletion compiler/rustc_errors/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -42,6 +46,7 @@ use rustc_span::{Loc, Span};

use std::any::Any;
use std::borrow::Cow;
use std::error::Report;
use std::fmt;
use std::hash::Hash;
use std::num::NonZeroUsize;
Expand All @@ -55,11 +60,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;
Expand Down Expand Up @@ -622,7 +630,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;
Expand Down
Loading