Skip to content

Commit f254287

Browse files
committed
macros: raw fluent translation with args
Signed-off-by: David Wood <[email protected]>
1 parent 190fc6a commit f254287

File tree

8 files changed

+155
-81
lines changed

8 files changed

+155
-81
lines changed

compiler/rustc_error_messages/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,12 @@ pub type FluentBundle =
3838
IntoDynSyncSend<fluent_bundle::bundle::FluentBundle<FluentResource, IntlLangMemoizer>>;
3939

4040
#[cfg(not(parallel_compiler))]
41-
fn new_bundle(locales: Vec<LanguageIdentifier>) -> FluentBundle {
41+
pub fn new_bundle(locales: Vec<LanguageIdentifier>) -> FluentBundle {
4242
IntoDynSyncSend(fluent_bundle::bundle::FluentBundle::new(locales))
4343
}
4444

4545
#[cfg(parallel_compiler)]
46-
fn new_bundle(locales: Vec<LanguageIdentifier>) -> FluentBundle {
46+
pub fn new_bundle(locales: Vec<LanguageIdentifier>) -> FluentBundle {
4747
IntoDynSyncSend(fluent_bundle::bundle::FluentBundle::new_concurrent(locales))
4848
}
4949

compiler/rustc_errors/src/lib.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#![feature(error_reporter)]
1818
#![feature(extract_if)]
1919
#![feature(generic_nonzero)]
20+
#![feature(lazy_cell)]
2021
#![feature(let_chains)]
2122
#![feature(negative_impls)]
2223
#![feature(never_type)]
@@ -657,6 +658,17 @@ impl DiagCtxt {
657658
inner.eagerly_translate_to_string(message, args)
658659
}
659660

661+
pub fn raw_translate<'a>(
662+
&self,
663+
slug: &str,
664+
raw: &str,
665+
args: impl Iterator<Item = DiagnosticArg<'a>>,
666+
) -> String {
667+
let inner = self.inner.borrow();
668+
let args = crate::translation::to_fluent_args(args);
669+
inner.emitter.raw_translate_message(slug, raw, &args)
670+
}
671+
660672
// This is here to not allow mutation of flags;
661673
// as of this writing it's used in Session::consider_optimizing and
662674
// in tests in rustc_interface.
Lines changed: 94 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
use crate::error::{TranslateError, TranslateErrorKind};
22
use crate::snippet::Style;
3-
use crate::{DiagnosticArg, DiagnosticMessage, FluentBundle};
3+
use crate::{DiagnosticArg, DiagnosticMessage};
44
use rustc_data_structures::sync::Lrc;
5+
use rustc_error_messages::fluent_bundle::FluentResource;
56
pub use rustc_error_messages::FluentArgs;
7+
use rustc_error_messages::{langid, new_bundle, FluentBundle};
68
use std::borrow::Cow;
79
use std::env;
810
use std::error::Report;
911

12+
#[cfg(not(parallel_compiler))]
13+
use std::cell::LazyCell as Lazy;
14+
#[cfg(parallel_compiler)]
15+
use std::sync::LazyLock as Lazy;
16+
1017
/// Convert diagnostic arguments (a rustc internal type that exists to implement
1118
/// `Encodable`/`Decodable`) into `FluentArgs` which is necessary to perform translation.
1219
///
@@ -59,72 +66,101 @@ pub trait Translate {
5966
message: &'a DiagnosticMessage,
6067
args: &'a FluentArgs<'_>,
6168
) -> Result<Cow<'_, str>, TranslateError<'_>> {
62-
trace!(?message, ?args);
69+
let fallback = |translator: &'a Self| translator.fallback_fluent_bundle();
6370
let (identifier, attr) = match message {
6471
DiagnosticMessage::Str(msg) | DiagnosticMessage::Translated(msg) => {
6572
return Ok(Cow::Borrowed(msg));
6673
}
6774
DiagnosticMessage::FluentIdentifier(identifier, attr) => (identifier, attr),
6875
};
69-
let translate_with_bundle =
70-
|bundle: &'a FluentBundle| -> Result<Cow<'_, str>, TranslateError<'_>> {
71-
let message = bundle
72-
.get_message(identifier)
73-
.ok_or(TranslateError::message(identifier, args))?;
74-
let value = match attr {
75-
Some(attr) => message
76-
.get_attribute(attr)
77-
.ok_or(TranslateError::attribute(identifier, args, attr))?
78-
.value(),
79-
None => message.value().ok_or(TranslateError::value(identifier, args))?,
80-
};
81-
debug!(?message, ?value);
82-
83-
let mut errs = vec![];
84-
let translated = bundle.format_pattern(value, Some(args), &mut errs);
85-
debug!(?translated, ?errs);
86-
if errs.is_empty() {
87-
Ok(translated)
88-
} else {
89-
Err(TranslateError::fluent(identifier, args, errs))
90-
}
76+
translate_message(self, fallback, identifier, attr.as_ref(), args)
77+
}
78+
79+
/// Translate a raw Fluent string.
80+
fn raw_translate_message<'t: 'msg, 'msg>(
81+
&'t self,
82+
slug: &'msg str,
83+
raw: &'msg str,
84+
args: &'msg FluentArgs<'_>,
85+
) -> String {
86+
let fallback = Lazy::new(move || {
87+
let res = FluentResource::try_new(format!("{slug} = {raw}"))
88+
.expect("failed to parse fallback fluent resource");
89+
let mut bundle = new_bundle(vec![langid!("en-US")]);
90+
// FIXME(davidtwco): get this value from the option
91+
bundle.set_use_isolating(false);
92+
bundle.add_resource(res).expect("adding resource");
93+
bundle
94+
});
95+
let fallback = |_| &*fallback;
96+
let slug = Cow::Borrowed(slug);
97+
let translated = translate_message(self, fallback, &slug, None, args);
98+
translated.map_err(Report::new).unwrap().to_string()
99+
}
100+
}
101+
102+
fn translate_message<'t: 'bundle, 'bundle: 'msg, 'msg, T: Translate + ?Sized>(
103+
translator: &'t T,
104+
fallback: impl Fn(&'t T) -> &'bundle FluentBundle,
105+
identifier: &'msg Cow<'msg, str>,
106+
attr: Option<&'msg Cow<'msg, str>>,
107+
args: &'msg FluentArgs<'msg>,
108+
) -> Result<Cow<'msg, str>, TranslateError<'msg>> {
109+
let translate_with_bundle =
110+
|bundle: &'bundle FluentBundle| -> Result<Cow<'msg, str>, TranslateError<'msg>> {
111+
let message =
112+
bundle.get_message(identifier).ok_or(TranslateError::message(identifier, args))?;
113+
let value = match attr {
114+
Some(attr) => message
115+
.get_attribute(attr)
116+
.ok_or(TranslateError::attribute(identifier, args, attr))?
117+
.value(),
118+
None => message.value().ok_or(TranslateError::value(identifier, args))?,
91119
};
120+
debug!(?message, ?value);
92121

93-
try {
94-
match self.fluent_bundle().map(|b| translate_with_bundle(b)) {
95-
// The primary bundle was present and translation succeeded
96-
Some(Ok(t)) => t,
97-
98-
// If `translate_with_bundle` returns `Err` with the primary bundle, this is likely
99-
// just that the primary bundle doesn't contain the message being translated, so
100-
// proceed to the fallback bundle.
101-
Some(Err(
102-
primary @ TranslateError::One {
103-
kind: TranslateErrorKind::MessageMissing, ..
104-
},
105-
)) => translate_with_bundle(self.fallback_fluent_bundle())
106-
.map_err(|fallback| primary.and(fallback))?,
107-
108-
// Always yeet out for errors on debug (unless
109-
// `RUSTC_TRANSLATION_NO_DEBUG_ASSERT` is set in the environment - this allows
110-
// local runs of the test suites, of builds with debug assertions, to test the
111-
// behaviour in a normal build).
112-
Some(Err(primary))
113-
if cfg!(debug_assertions)
114-
&& env::var("RUSTC_TRANSLATION_NO_DEBUG_ASSERT").is_err() =>
115-
{
116-
do yeet primary
117-
}
118-
119-
// ..otherwise, for end users, an error about this wouldn't be useful or actionable, so
120-
// just hide it and try with the fallback bundle.
121-
Some(Err(primary)) => translate_with_bundle(self.fallback_fluent_bundle())
122-
.map_err(|fallback| primary.and(fallback))?,
123-
124-
// The primary bundle is missing, proceed to the fallback bundle
125-
None => translate_with_bundle(self.fallback_fluent_bundle())
126-
.map_err(|fallback| TranslateError::primary(identifier, args).and(fallback))?,
122+
let mut errs = vec![];
123+
let translated = bundle.format_pattern(value, Some(args), &mut errs);
124+
debug!(?translated, ?errs);
125+
if errs.is_empty() {
126+
Ok(translated)
127+
} else {
128+
Err(TranslateError::fluent(identifier, args, errs))
127129
}
130+
};
131+
132+
try {
133+
match translator.fluent_bundle().map(|b| translate_with_bundle(b)) {
134+
// The primary bundle was present and translation succeeded
135+
Some(Ok(t)) => t,
136+
137+
// If `translate_with_bundle` returns `Err` with the primary bundle, this is likely
138+
// just that the primary bundle doesn't contain the message being translated, so
139+
// proceed to the fallback bundle.
140+
Some(Err(
141+
primary @ TranslateError::One { kind: TranslateErrorKind::MessageMissing, .. },
142+
)) => translate_with_bundle(fallback(translator))
143+
.map_err(|fallback| primary.and(fallback))?,
144+
145+
// Always yeet out for errors on debug (unless
146+
// `RUSTC_TRANSLATION_NO_DEBUG_ASSERT` is set in the environment - this allows
147+
// local runs of the test suites, of builds with debug assertions, to test the
148+
// behaviour in a normal build).
149+
Some(Err(primary))
150+
if cfg!(debug_assertions)
151+
&& env::var("RUSTC_TRANSLATION_NO_DEBUG_ASSERT").is_err() =>
152+
{
153+
do yeet primary
154+
}
155+
156+
// ..otherwise, for end users, an error about this wouldn't be useful or actionable, so
157+
// just hide it and try with the fallback bundle.
158+
Some(Err(primary)) => translate_with_bundle(fallback(translator))
159+
.map_err(|fallback| primary.and(fallback))?,
160+
161+
// The primary bundle is missing, proceed to the fallback bundle
162+
None => translate_with_bundle(fallback(translator))
163+
.map_err(|fallback| TranslateError::primary(identifier, args).and(fallback))?,
128164
}
129165
}
130166
}

compiler/rustc_macros/src/diagnostics/diagnostic.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,18 @@ impl<'a> DiagnosticDerive<'a> {
2828
let preamble = builder.preamble(variant);
2929
let body = builder.body(variant);
3030

31+
let args = if matches!(builder.slug.value_ref(), Some(SlugOrRawFluent::RawFluent(_))) {
32+
let args = builder.args;
33+
quote! {
34+
use rustc_data_structures::fx::FxHashMap;
35+
use rustc_errors::{DiagnosticArgName, DiagnosticArgValue, IntoDiagnosticArg};
36+
let mut args: FxHashMap<DiagnosticArgName, DiagnosticArgValue> = Default::default();
37+
#(#args)*
38+
}
39+
} else {
40+
quote! {}
41+
};
42+
3143
let init = match builder.slug.value_ref() {
3244
None => {
3345
span_err(builder.span, "diagnostic slug not specified")
@@ -60,17 +72,19 @@ impl<'a> DiagnosticDerive<'a> {
6072
}
6173
Some(SlugOrRawFluent::RawFluent(raw)) => {
6274
quote! {
75+
let raw = dcx.raw_translate("foo", #raw, args.iter());
6376
let mut diag = rustc_errors::DiagnosticBuilder::new(
6477
dcx,
6578
level,
66-
#raw,
79+
raw,
6780
);
6881
}
6982
}
7083
};
7184

7285
let formatting_init = &builder.formatting_init;
7386
quote! {
87+
#args
7488
#init
7589
#formatting_init
7690
#preamble
@@ -87,6 +101,7 @@ impl<'a> DiagnosticDerive<'a> {
87101
where G: rustc_errors::EmissionGuarantee
88102
{
89103

104+
#[allow(rustc::potential_query_instability)]
90105
#[track_caller]
91106
fn into_diagnostic(
92107
self,

compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ pub(crate) struct DiagnosticDeriveVariantBuilder {
5959
/// Error codes are a optional part of the struct attribute - this is only set to detect
6060
/// multiple specifications.
6161
pub code: SpannedOption<()>,
62+
63+
pub args: Vec<TokenStream>,
6264
}
6365

6466
impl HasFieldMap for DiagnosticDeriveVariantBuilder {
@@ -109,6 +111,7 @@ impl DiagnosticDeriveKind {
109111
formatting_init: TokenStream::new(),
110112
slug: None,
111113
code: None,
114+
args: Vec::new(),
112115
};
113116
f(builder, variant)
114117
});
@@ -275,11 +278,19 @@ impl DiagnosticDeriveVariantBuilder {
275278
let ident = field.ident.as_ref().unwrap();
276279
let ident = format_ident!("{}", ident); // strip `r#` prefix, if present
277280

278-
quote! {
279-
diag.arg(
280-
stringify!(#ident),
281-
#field_binding
282-
);
281+
if matches!(self.slug.value_ref(), Some(SlugOrRawFluent::RawFluent(_))) {
282+
// Cloning should not be necessary once there are no `diag.arg` calls.
283+
self.args.push(quote! {
284+
args.insert(stringify!(#ident).into(), #field_binding.clone().into_diagnostic_arg());
285+
});
286+
quote! {}
287+
} else {
288+
quote! {
289+
diag.arg(
290+
stringify!(#ident),
291+
#field_binding
292+
);
293+
}
283294
}
284295
}
285296

@@ -422,9 +433,10 @@ impl DiagnosticDeriveVariantBuilder {
422433
);
423434
}),
424435
SlugOrRawFluent::RawFluent(raw) => Ok(quote! {
436+
let raw = dcx.raw_translate("foo", #raw, args.iter());
425437
diag.span_suggestions_with_style(
426438
#span_field,
427-
#raw,
439+
raw,
428440
#code_field,
429441
#applicability,
430442
#style
@@ -455,7 +467,10 @@ impl DiagnosticDeriveVariantBuilder {
455467
}
456468
}
457469
SlugOrRawFluent::RawFluent(raw) => {
458-
quote! { diag.#fn_name(#field_binding, #raw); }
470+
quote! {
471+
let raw = dcx.raw_translate("foo", #raw, args.iter());
472+
diag.#fn_name(#field_binding, raw);
473+
}
459474
}
460475
}
461476
}
@@ -470,7 +485,10 @@ impl DiagnosticDeriveVariantBuilder {
470485
}
471486
}
472487
SlugOrRawFluent::RawFluent(raw) => {
473-
quote! { diag.#kind(#raw); }
488+
quote! {
489+
let raw = dcx.raw_translate("foo", #raw, args.iter());
490+
diag.#kind(raw);
491+
}
474492
}
475493
}
476494
}

compiler/rustc_macros/src/diagnostics/subdiagnostic.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -521,7 +521,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
521521
);
522522
}
523523
SlugOrRawFluent::RawFluent(raw) => {
524-
calls.extend(quote! { let #message = #f(#diag, #raw); });
524+
calls.extend(quote! { let #message = #raw; });
525525
}
526526
}
527527

compiler/rustc_parse/messages.ftl

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,6 @@ parse_expect_eq_instead_of_eqeq = expected `=`, found `==`
180180
parse_expect_label_found_ident = expected a label, found an identifier
181181
.suggestion = labels start with a tick
182182
183-
parse_expect_path = expected a path
184-
185183
parse_expected_binding_left_of_at = left-hand side of `@` must be a binding
186184
.label_lhs = interpreted as a pattern, not a binding
187185
.label_rhs = also a pattern
@@ -274,8 +272,6 @@ parse_fn_ptr_with_generics = function pointer types may not have generic paramet
274272
parse_fn_trait_missing_paren = `Fn` bounds require arguments in parentheses
275273
.add_paren = add the missing parentheses
276274
277-
parse_forgot_paren = perhaps you forgot parentheses?
278-
279275
parse_found_expr_would_be_stmt = expected expression, found `{$token}`
280276
.label = expected expression
281277
@@ -507,9 +503,6 @@ parse_maybe_recover_from_bad_qpath_stage_2 =
507503
missing angle brackets in associated item path
508504
.suggestion = types that don't start with an identifier need to be surrounded with angle brackets in qualified paths
509505
510-
parse_maybe_recover_from_bad_type_plus =
511-
expected a path on the left-hand side of `+`, not `{$ty}`
512-
513506
parse_meta_bad_delim = wrong meta list delimiters
514507
parse_meta_bad_delim_suggestion = the delimiters should be `(` and `)`
515508

0 commit comments

Comments
 (0)