Skip to content

Commit 9e69dbc

Browse files
authored
Rollup merge of #100366 - davidtwco:translation-never-fail, r=compiler-errors
errors: don't fail on broken primary translations If a primary bundle doesn't contain a message then the fallback bundle is used. However, if the primary bundle's message is broken (e.g. it refers to a interpolated variable that the compiler isn't providing) then this would just result in a compiler panic. While there aren't any primary bundles right now, this is the type of issue that could come up once translation is further along. r? ```@compiler-errors``` (since this comes out of a in-person discussion we had at RustConf)
2 parents da3b89d + 2eebd34 commit 9e69dbc

File tree

7 files changed

+76
-38
lines changed

7 files changed

+76
-38
lines changed

compiler/rustc_errors/src/emitter.rs

+49-31
Original file line numberDiff line numberDiff line change
@@ -273,40 +273,58 @@ pub trait Emitter {
273273
DiagnosticMessage::FluentIdentifier(identifier, attr) => (identifier, attr),
274274
};
275275

276-
let bundle = match self.fluent_bundle() {
277-
Some(bundle) if bundle.has_message(&identifier) => bundle,
278-
_ => self.fallback_fluent_bundle(),
279-
};
276+
let translate_with_bundle = |bundle: &'a FluentBundle| -> Option<(Cow<'_, str>, Vec<_>)> {
277+
let message = bundle.get_message(&identifier)?;
278+
let value = match attr {
279+
Some(attr) => message.get_attribute(attr)?.value(),
280+
None => message.value()?,
281+
};
282+
debug!(?message, ?value);
280283

281-
let message = bundle.get_message(&identifier).expect("missing diagnostic in fluent bundle");
282-
let value = match attr {
283-
Some(attr) => {
284-
if let Some(attr) = message.get_attribute(attr) {
285-
attr.value()
286-
} else {
287-
panic!("missing attribute `{attr}` in fluent message `{identifier}`")
288-
}
289-
}
290-
None => {
291-
if let Some(value) = message.value() {
292-
value
293-
} else {
294-
panic!("missing value in fluent message `{identifier}`")
295-
}
296-
}
284+
let mut errs = vec![];
285+
let translated = bundle.format_pattern(value, Some(&args), &mut errs);
286+
debug!(?translated, ?errs);
287+
Some((translated, errs))
297288
};
298289

299-
let mut err = vec![];
300-
let translated = bundle.format_pattern(value, Some(&args), &mut err);
301-
trace!(?translated, ?err);
302-
debug_assert!(
303-
err.is_empty(),
304-
"identifier: {:?}, args: {:?}, errors: {:?}",
305-
identifier,
306-
args,
307-
err
308-
);
309-
translated
290+
self.fluent_bundle()
291+
.and_then(|bundle| translate_with_bundle(bundle))
292+
// If `translate_with_bundle` returns `None` with the primary bundle, this is likely
293+
// just that the primary bundle doesn't contain the message being translated, so
294+
// proceed to the fallback bundle.
295+
//
296+
// However, when errors are produced from translation, then that means the translation
297+
// is broken (e.g. `{$foo}` exists in a translation but `foo` isn't provided).
298+
//
299+
// In debug builds, assert so that compiler devs can spot the broken translation and
300+
// fix it..
301+
.inspect(|(_, errs)| {
302+
debug_assert!(
303+
errs.is_empty(),
304+
"identifier: {:?}, attr: {:?}, args: {:?}, errors: {:?}",
305+
identifier,
306+
attr,
307+
args,
308+
errs
309+
);
310+
})
311+
// ..otherwise, for end users, an error about this wouldn't be useful or actionable, so
312+
// just hide it and try with the fallback bundle.
313+
.filter(|(_, errs)| errs.is_empty())
314+
.or_else(|| translate_with_bundle(self.fallback_fluent_bundle()))
315+
.map(|(translated, errs)| {
316+
// Always bail out for errors with the fallback bundle.
317+
assert!(
318+
errs.is_empty(),
319+
"identifier: {:?}, attr: {:?}, args: {:?}, errors: {:?}",
320+
identifier,
321+
attr,
322+
args,
323+
errs
324+
);
325+
translated
326+
})
327+
.expect("failed to find message in primary or fallback fluent bundles")
310328
}
311329

312330
/// Formats the substitutions of the primary_span

compiler/rustc_errors/src/lib.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
#![feature(drain_filter)]
77
#![feature(if_let_guard)]
88
#![cfg_attr(bootstrap, feature(let_chains))]
9+
#![feature(adt_const_params)]
910
#![feature(let_else)]
1011
#![feature(never_type)]
11-
#![feature(adt_const_params)]
12+
#![feature(result_option_inspect)]
1213
#![feature(rustc_attrs)]
1314
#![allow(incomplete_features)]
1415
#![allow(rustc::potential_query_instability)]

src/test/run-make/translation/Makefile

+19-6
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,29 @@ FAKEROOT=$(TMPDIR)/fakeroot
99

1010
all: normal custom sysroot
1111

12-
normal: basic-translation.rs
12+
# Check that the test works normally, using the built-in fallback bundle.
13+
normal: test.rs
1314
$(RUSTC) $< 2>&1 | grep "struct literal body without path"
1415

15-
custom: basic-translation.rs basic-translation.ftl
16-
$(RUSTC) $< -Ztranslate-additional-ftl=$(CURDIR)/basic-translation.ftl 2>&1 | grep "this is a test message"
16+
# Check that a primary bundle can be loaded and will be preferentially used
17+
# where possible.
18+
custom: test.rs working.ftl
19+
$(RUSTC) $< -Ztranslate-additional-ftl=$(CURDIR)/working.ftl 2>&1 | grep "this is a test message"
20+
21+
# Check that a primary bundle with a broken message (e.g. a interpolated
22+
# variable is missing) will use the fallback bundle.
23+
missing: test.rs missing.ftl
24+
$(RUSTC) $< -Ztranslate-additional-ftl=$(CURDIR)/missing.ftl 2>&1 | grep "struct literal body without path"
25+
26+
# Check that a primary bundle without the desired message will use the fallback
27+
# bundle.
28+
broken: test.rs broken.ftl
29+
$(RUSTC) $< -Ztranslate-additional-ftl=$(CURDIR)/broken.ftl 2>&1 | grep "struct literal body without path"
1730

1831
# Check that a locale can be loaded from the sysroot given a language
1932
# identifier by making a local copy of the sysroot and adding the custom locale
2033
# to it.
21-
sysroot: basic-translation.rs basic-translation.ftl
34+
sysroot: test.rs working.ftl
2235
mkdir $(FAKEROOT)
2336
ln -s $(SYSROOT)/* $(FAKEROOT)
2437
rm -f $(FAKEROOT)/lib
@@ -31,7 +44,7 @@ sysroot: basic-translation.rs basic-translation.ftl
3144
mkdir $(FAKEROOT)/lib/rustlib/src
3245
ln -s $(SYSROOT)/lib/rustlib/src/* $(FAKEROOT)/lib/rustlib/src
3346
mkdir -p $(FAKEROOT)/share/locale/zh-CN/
34-
ln -s $(CURDIR)/basic-translation.ftl $(FAKEROOT)/share/locale/zh-CN/basic-translation.ftl
47+
ln -s $(CURDIR)/working.ftl $(FAKEROOT)/share/locale/zh-CN/basic-translation.ftl
3548
$(RUSTC) $< --sysroot $(FAKEROOT) -Ztranslate-lang=zh-CN 2>&1 | grep "this is a test message"
3649

3750
# Check that the compiler errors out when the sysroot requested cannot be
@@ -43,7 +56,7 @@ sysroot-missing:
4356
# Check that the compiler errors out when the sysroot requested cannot be
4457
# found. This test might start failing if there actually exists a Klingon
4558
# translation of rustc's error messages.
46-
sysroot-invalid: basic-translation.rs basic-translation.ftl
59+
sysroot-invalid: test.rs working.ftl
4760
mkdir $(FAKEROOT)
4861
ln -s $(SYSROOT)/* $(FAKEROOT)
4962
rm -f $(FAKEROOT)/lib
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# `foo` isn't provided by this diagnostic so it is expected that the fallback message is used.
2+
parser-struct-literal-body-without-path = this is a {$foo} message
3+
.suggestion = this is a test suggestion
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# `parser-struct-literal-body-without-path` isn't provided by this resource at all, so the
2+
# fallback should be used.
3+
foo = bar

0 commit comments

Comments
 (0)