Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 7f91697

Browse files
committed
errors: implement fallback diagnostic translation
This commit updates the signatures of all diagnostic functions to accept types that can be converted into a `DiagnosticMessage`. This enables existing diagnostic calls to continue to work as before and Fluent identifiers to be provided. The `SessionDiagnostic` derive just generates normal diagnostic calls, so these APIs had to be modified to accept Fluent identifiers. In addition, loading of the "fallback" Fluent bundle, which contains the built-in English messages, has been implemented. Each diagnostic now has "arguments" which correspond to variables in the Fluent messages (necessary to render a Fluent message) but no API for adding arguments has been added yet. Therefore, diagnostics (that do not require interpolation) can be converted to use Fluent identifiers and will be output as before.
1 parent c45f295 commit 7f91697

File tree

46 files changed

+920
-294
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+920
-294
lines changed

Cargo.lock

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1238,6 +1238,50 @@ dependencies = [
12381238
"miniz_oxide",
12391239
]
12401240

1241+
[[package]]
1242+
name = "fluent"
1243+
version = "0.16.0"
1244+
source = "registry+https://github.com/rust-lang/crates.io-index"
1245+
checksum = "61f69378194459db76abd2ce3952b790db103ceb003008d3d50d97c41ff847a7"
1246+
dependencies = [
1247+
"fluent-bundle",
1248+
"unic-langid",
1249+
]
1250+
1251+
[[package]]
1252+
name = "fluent-bundle"
1253+
version = "0.15.2"
1254+
source = "registry+https://github.com/rust-lang/crates.io-index"
1255+
checksum = "e242c601dec9711505f6d5bbff5bedd4b61b2469f2e8bb8e57ee7c9747a87ffd"
1256+
dependencies = [
1257+
"fluent-langneg",
1258+
"fluent-syntax",
1259+
"intl-memoizer",
1260+
"intl_pluralrules",
1261+
"rustc-hash",
1262+
"self_cell",
1263+
"smallvec",
1264+
"unic-langid",
1265+
]
1266+
1267+
[[package]]
1268+
name = "fluent-langneg"
1269+
version = "0.13.0"
1270+
source = "registry+https://github.com/rust-lang/crates.io-index"
1271+
checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94"
1272+
dependencies = [
1273+
"unic-langid",
1274+
]
1275+
1276+
[[package]]
1277+
name = "fluent-syntax"
1278+
version = "0.11.0"
1279+
source = "registry+https://github.com/rust-lang/crates.io-index"
1280+
checksum = "c0abed97648395c902868fee9026de96483933faa54ea3b40d652f7dfe61ca78"
1281+
dependencies = [
1282+
"thiserror",
1283+
]
1284+
12411285
[[package]]
12421286
name = "fnv"
12431287
version = "1.0.7"
@@ -1782,6 +1826,26 @@ dependencies = [
17821826
"cfg-if 1.0.0",
17831827
]
17841828

1829+
[[package]]
1830+
name = "intl-memoizer"
1831+
version = "0.5.1"
1832+
source = "registry+https://github.com/rust-lang/crates.io-index"
1833+
checksum = "c310433e4a310918d6ed9243542a6b83ec1183df95dff8f23f87bb88a264a66f"
1834+
dependencies = [
1835+
"type-map",
1836+
"unic-langid",
1837+
]
1838+
1839+
[[package]]
1840+
name = "intl_pluralrules"
1841+
version = "7.0.1"
1842+
source = "registry+https://github.com/rust-lang/crates.io-index"
1843+
checksum = "b18f988384267d7066cc2be425e6faf352900652c046b6971d2e228d3b1c5ecf"
1844+
dependencies = [
1845+
"tinystr",
1846+
"unic-langid",
1847+
]
1848+
17851849
[[package]]
17861850
name = "itertools"
17871851
version = "0.10.1"
@@ -2812,6 +2876,12 @@ dependencies = [
28122876
"version_check",
28132877
]
28142878

2879+
[[package]]
2880+
name = "proc-macro-hack"
2881+
version = "0.5.19"
2882+
source = "registry+https://github.com/rust-lang/crates.io-index"
2883+
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
2884+
28152885
[[package]]
28162886
name = "proc-macro2"
28172887
version = "1.0.30"
@@ -3649,9 +3719,13 @@ version = "0.0.0"
36493719
name = "rustc_error_messages"
36503720
version = "0.0.0"
36513721
dependencies = [
3722+
"fluent",
3723+
"rustc_data_structures",
36523724
"rustc_macros",
36533725
"rustc_serialize",
36543726
"rustc_span",
3727+
"tracing",
3728+
"unic-langid",
36553729
]
36563730

36573731
[[package]]
@@ -4585,6 +4659,12 @@ dependencies = [
45854659
"libc",
45864660
]
45874661

4662+
[[package]]
4663+
name = "self_cell"
4664+
version = "0.10.2"
4665+
source = "registry+https://github.com/rust-lang/crates.io-index"
4666+
checksum = "1ef965a420fe14fdac7dd018862966a4c14094f900e1650bbc71ddd7d580c8af"
4667+
45884668
[[package]]
45894669
name = "semver"
45904670
version = "1.0.3"
@@ -5116,6 +5196,12 @@ dependencies = [
51165196
"winapi",
51175197
]
51185198

5199+
[[package]]
5200+
name = "tinystr"
5201+
version = "0.3.4"
5202+
source = "registry+https://github.com/rust-lang/crates.io-index"
5203+
checksum = "29738eedb4388d9ea620eeab9384884fc3f06f586a2eddb56bedc5885126c7c1"
5204+
51195205
[[package]]
51205206
name = "tinyvec"
51215207
version = "0.3.4"
@@ -5274,6 +5360,15 @@ dependencies = [
52745360
"tracing-subscriber",
52755361
]
52765362

5363+
[[package]]
5364+
name = "type-map"
5365+
version = "0.4.0"
5366+
source = "registry+https://github.com/rust-lang/crates.io-index"
5367+
checksum = "b6d3364c5e96cb2ad1603037ab253ddd34d7fb72a58bdddf4b7350760fc69a46"
5368+
dependencies = [
5369+
"rustc-hash",
5370+
]
5371+
52775372
[[package]]
52785373
name = "typenum"
52795374
version = "1.12.0"
@@ -5328,6 +5423,49 @@ dependencies = [
53285423
"unic-ucd-version",
53295424
]
53305425

5426+
[[package]]
5427+
name = "unic-langid"
5428+
version = "0.9.0"
5429+
source = "registry+https://github.com/rust-lang/crates.io-index"
5430+
checksum = "73328fcd730a030bdb19ddf23e192187a6b01cd98be6d3140622a89129459ce5"
5431+
dependencies = [
5432+
"unic-langid-impl",
5433+
"unic-langid-macros",
5434+
]
5435+
5436+
[[package]]
5437+
name = "unic-langid-impl"
5438+
version = "0.9.0"
5439+
source = "registry+https://github.com/rust-lang/crates.io-index"
5440+
checksum = "1a4a8eeaf0494862c1404c95ec2f4c33a2acff5076f64314b465e3ddae1b934d"
5441+
dependencies = [
5442+
"tinystr",
5443+
]
5444+
5445+
[[package]]
5446+
name = "unic-langid-macros"
5447+
version = "0.9.0"
5448+
source = "registry+https://github.com/rust-lang/crates.io-index"
5449+
checksum = "18f980d6d87e8805f2836d64b4138cc95aa7986fa63b1f51f67d5fbff64dd6e5"
5450+
dependencies = [
5451+
"proc-macro-hack",
5452+
"tinystr",
5453+
"unic-langid-impl",
5454+
"unic-langid-macros-impl",
5455+
]
5456+
5457+
[[package]]
5458+
name = "unic-langid-macros-impl"
5459+
version = "0.9.0"
5460+
source = "registry+https://github.com/rust-lang/crates.io-index"
5461+
checksum = "29396ffd97e27574c3e01368b1a64267d3064969e4848e2e130ff668be9daa9f"
5462+
dependencies = [
5463+
"proc-macro-hack",
5464+
"quote",
5465+
"syn",
5466+
"unic-langid-impl",
5467+
]
5468+
53315469
[[package]]
53325470
name = "unic-ucd-version"
53335471
version = "0.9.0"

compiler/rustc_borrowck/src/diagnostics/region_name.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ impl RegionName {
109109
*span,
110110
format!("lifetime `{}` represents this closure's body", self),
111111
);
112-
diag.note(&note);
112+
diag.note(note);
113113
}
114114
RegionNameSource::AnonRegionFromArgument(RegionNameHighlight::CannotMatchHirTy(
115115
span,

compiler/rustc_codegen_ssa/src/back/write.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1707,23 +1707,33 @@ impl SharedEmitter {
17071707

17081708
impl Emitter for SharedEmitter {
17091709
fn emit_diagnostic(&mut self, diag: &rustc_errors::Diagnostic) {
1710+
let fluent_args = self.to_fluent_args(diag.args());
17101711
drop(self.sender.send(SharedEmitterMessage::Diagnostic(Diagnostic {
1711-
msg: diag.message().to_string(),
1712+
msg: self.translate_messages(&diag.message, &fluent_args).to_string(),
17121713
code: diag.code.clone(),
17131714
lvl: diag.level(),
17141715
})));
17151716
for child in &diag.children {
17161717
drop(self.sender.send(SharedEmitterMessage::Diagnostic(Diagnostic {
1717-
msg: child.message().to_string(),
1718+
msg: self.translate_messages(&child.message, &fluent_args).to_string(),
17181719
code: None,
17191720
lvl: child.level,
17201721
})));
17211722
}
17221723
drop(self.sender.send(SharedEmitterMessage::AbortIfErrors));
17231724
}
1725+
17241726
fn source_map(&self) -> Option<&Lrc<SourceMap>> {
17251727
None
17261728
}
1729+
1730+
fn fluent_bundle(&self) -> Option<&Lrc<rustc_errors::FluentBundle>> {
1731+
None
1732+
}
1733+
1734+
fn fallback_fluent_bundle(&self) -> &Lrc<rustc_errors::FluentBundle> {
1735+
panic!("shared emitter attempted to translate a diagnostic");
1736+
}
17271737
}
17281738

17291739
impl SharedEmitterMain {
@@ -1754,9 +1764,9 @@ impl SharedEmitterMain {
17541764
let msg = msg.strip_prefix("error: ").unwrap_or(&msg);
17551765

17561766
let mut err = match level {
1757-
Level::Error { lint: false } => sess.struct_err(&msg).forget_guarantee(),
1758-
Level::Warning => sess.struct_warn(&msg),
1759-
Level::Note => sess.struct_note_without_error(&msg),
1767+
Level::Error { lint: false } => sess.struct_err(msg).forget_guarantee(),
1768+
Level::Warning => sess.struct_warn(msg),
1769+
Level::Note => sess.struct_note_without_error(msg),
17601770
_ => bug!("Invalid inline asm diagnostic level"),
17611771
};
17621772

compiler/rustc_driver/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1172,9 +1172,11 @@ static DEFAULT_HOOK: SyncLazy<Box<dyn Fn(&panic::PanicInfo<'_>) + Sync + Send +
11721172
/// When `install_ice_hook` is called, this function will be called as the panic
11731173
/// hook.
11741174
pub fn report_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) {
1175+
let fallback_bundle = rustc_errors::fallback_fluent_bundle();
11751176
let emitter = Box::new(rustc_errors::emitter::EmitterWriter::stderr(
11761177
rustc_errors::ColorConfig::Auto,
11771178
None,
1179+
fallback_bundle,
11781180
false,
11791181
false,
11801182
None,
@@ -1209,7 +1211,7 @@ pub fn report_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) {
12091211
}
12101212

12111213
for note in &xs {
1212-
handler.note_without_error(note);
1214+
handler.note_without_error(note.as_ref());
12131215
}
12141216

12151217
// If backtraces are enabled, also print the query stack

compiler/rustc_error_messages/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ edition = "2021"
77
doctest = false
88

99
[dependencies]
10+
fluent = "0.16.0"
11+
rustc_data_structures = { path = "../rustc_data_structures" }
1012
rustc_serialize = { path = "../rustc_serialize" }
1113
rustc_span = { path = "../rustc_span" }
1214
rustc_macros = { path = "../rustc_macros" }
15+
tracing = "0.1"
16+
unic-langid = { version = "0.9.0", features = ["macros"] }
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
parser-struct-literal-body-without-path = struct literal body without path
2+
.suggestion = you might have forgotten to add the struct literal inside the block

compiler/rustc_error_messages/src/lib.rs

Lines changed: 57 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,75 @@
1+
use rustc_data_structures::sync::Lrc;
12
use rustc_macros::{Decodable, Encodable};
23
use rustc_span::Span;
4+
use std::borrow::Cow;
5+
use tracing::debug;
6+
7+
pub use fluent::{FluentArgs, FluentValue};
8+
9+
static FALLBACK_FLUENT_RESOURCE: &'static str = include_str!("../locales/en-US/diagnostics.ftl");
10+
11+
pub type FluentBundle = fluent::FluentBundle<fluent::FluentResource>;
12+
13+
/// Return the default `FluentBundle` with standard en-US diagnostic messages.
14+
pub fn fallback_fluent_bundle() -> Lrc<FluentBundle> {
15+
let fallback_resource = fluent::FluentResource::try_new(FALLBACK_FLUENT_RESOURCE.to_string())
16+
.expect("failed to parse ftl resource");
17+
debug!(?fallback_resource);
18+
let mut fallback_bundle = FluentBundle::new(vec![unic_langid::langid!("en-US")]);
19+
fallback_bundle.add_resource(fallback_resource).expect("failed to add resource to bundle");
20+
let fallback_bundle = Lrc::new(fallback_bundle);
21+
fallback_bundle
22+
}
23+
24+
/// Identifier for the Fluent message/attribute corresponding to a diagnostic message.
25+
type FluentId = Cow<'static, str>;
326

427
/// Abstraction over a message in a diagnostic to support both translatable and non-translatable
528
/// diagnostic messages.
29+
///
30+
/// Intended to be removed once diagnostics are entirely translatable.
631
#[derive(Clone, Debug, PartialEq, Eq, Hash, Encodable, Decodable)]
732
pub enum DiagnosticMessage {
833
/// Non-translatable diagnostic message.
34+
// FIXME(davidtwco): can a `Cow<'static, str>` be used here?
935
Str(String),
1036
/// Identifier for a Fluent message corresponding to the diagnostic message.
11-
FluentIdentifier(String),
37+
FluentIdentifier(FluentId, Option<FluentId>),
1238
}
1339

1440
impl DiagnosticMessage {
15-
/// Convert `DiagnosticMessage` to a `&str`.
16-
pub fn as_str(&self) -> &str {
41+
/// Returns the `String` contained within the `DiagnosticMessage::Str` variant, assuming that
42+
/// this diagnostic message is of the legacy, non-translatable variety. Panics if this
43+
/// assumption does not hold.
44+
///
45+
/// Don't use this - it exists to support some places that do comparison with diagnostic
46+
/// strings.
47+
pub fn expect_str(&self) -> &str {
1748
match self {
18-
DiagnosticMessage::Str(msg) => msg,
19-
DiagnosticMessage::FluentIdentifier(..) => unimplemented!(),
49+
DiagnosticMessage::Str(s) => s,
50+
_ => panic!("expected non-translatable diagnostic message"),
2051
}
2152
}
2253

23-
/// Convert `DiagnosticMessage` to an owned `String`.
24-
pub fn to_string(self) -> String {
25-
match self {
26-
DiagnosticMessage::Str(msg) => msg,
27-
DiagnosticMessage::FluentIdentifier(..) => unimplemented!(),
28-
}
54+
/// Create a `DiagnosticMessage` for the provided Fluent identifier.
55+
pub fn fluent(id: impl Into<Cow<'static, str>>) -> Self {
56+
DiagnosticMessage::FluentIdentifier(id.into(), None)
57+
}
58+
59+
/// Create a `DiagnosticMessage` for the provided Fluent identifier and attribute.
60+
pub fn fluent_attr(
61+
id: impl Into<Cow<'static, str>>,
62+
attr: impl Into<Cow<'static, str>>,
63+
) -> Self {
64+
DiagnosticMessage::FluentIdentifier(id.into(), Some(attr.into()))
65+
}
66+
}
67+
68+
/// `From` impl that enables existing diagnostic calls to functions which now take
69+
/// `impl Into<DiagnosticMessage>` to continue to work as before.
70+
impl<S: Into<String>> From<S> for DiagnosticMessage {
71+
fn from(s: S) -> Self {
72+
DiagnosticMessage::Str(s.into())
2973
}
3074
}
3175

@@ -72,12 +116,8 @@ impl MultiSpan {
72116
MultiSpan { primary_spans: vec, span_labels: vec![] }
73117
}
74118

75-
pub fn push_span_label(&mut self, span: Span, label: String) {
76-
self.span_labels.push((span, DiagnosticMessage::Str(label)));
77-
}
78-
79-
pub fn push_span_message(&mut self, span: Span, message: DiagnosticMessage) {
80-
self.span_labels.push((span, message));
119+
pub fn push_span_label(&mut self, span: Span, label: impl Into<DiagnosticMessage>) {
120+
self.span_labels.push((span, label.into()));
81121
}
82122

83123
/// Selects the first primary span (if any).

0 commit comments

Comments
 (0)