Make translate_message return result and add tests
This commit is contained in:
parent
0b5d6ae5db
commit
262ff86138
7 changed files with 224 additions and 17 deletions
|
@ -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
|
||||
|
@ -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() {
|
||||
|
@ -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(
|
||||
|
@ -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 {
|
||||
|
|
|
@ -65,11 +65,14 @@ impl fmt::Display for TranslateError<'_> {
|
|||
|
||||
match self {
|
||||
Self::One { id, args, kind } => {
|
||||
writeln!(f, "\nfailed while formatting fluent string `{id}`: ")?;
|
||||
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")?,
|
||||
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 {
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -46,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;
|
||||
|
@ -65,6 +66,8 @@ mod lock;
|
|||
pub mod registry;
|
||||
mod snippet;
|
||||
mod styled_buffer;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
pub mod translation;
|
||||
|
||||
pub use diagnostic_builder::IntoDiagnostic;
|
||||
|
@ -627,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;
|
||||
|
|
183
compiler/rustc_errors/src/tests.rs
Normal file
183
compiler/rustc_errors/src/tests.rs
Normal file
|
@ -0,0 +1,183 @@
|
|||
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");
|
||||
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"
|
||||
);
|
||||
}
|
||||
}
|
|
@ -45,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>(),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -54,11 +57,11 @@ 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),
|
||||
};
|
||||
|
@ -86,7 +89,7 @@ pub trait Translate {
|
|||
}
|
||||
};
|
||||
|
||||
let ret: Result<Cow<'_, str>, TranslateError<'_>> = try {
|
||||
try {
|
||||
match self.fluent_bundle().map(|b| translate_with_bundle(b)) {
|
||||
// The primary bundle was present and translation succeeded
|
||||
Some(Ok(t)) => t,
|
||||
|
@ -104,8 +107,6 @@ pub trait Translate {
|
|||
None => translate_with_bundle(self.fallback_fluent_bundle())
|
||||
.map_err(|fallback| TranslateError::primary(identifier, args).and(fallback))?,
|
||||
}
|
||||
};
|
||||
ret.map_err(Report::new)
|
||||
.expect("failed to find message in primary or fallback fluent bundles")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -156,7 +156,9 @@ impl Emitter for BufferEmitter {
|
|||
let mut buffer = self.buffer.borrow_mut();
|
||||
|
||||
let fluent_args = to_fluent_args(diag.args());
|
||||
let translated_main_message = self.translate_message(&diag.message[0].0, &fluent_args);
|
||||
let translated_main_message = self
|
||||
.translate_message(&diag.message[0].0, &fluent_args)
|
||||
.unwrap_or_else(|e| panic!("{e}"));
|
||||
|
||||
buffer.messages.push(format!("error from rustc: {}", translated_main_message));
|
||||
if diag.is_error() {
|
||||
|
|
Loading…
Add table
Reference in a new issue