]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_errors/src/translation.rs
Auto merge of #90291 - geeklint:loosen_weak_debug_bound, r=dtolnay
[rust.git] / compiler / rustc_errors / src / translation.rs
1 use crate::snippet::Style;
2 use crate::{DiagnosticArg, DiagnosticMessage, FluentBundle};
3 use rustc_data_structures::sync::Lrc;
4 use rustc_error_messages::{
5     fluent_bundle::resolver::errors::{ReferenceKind, ResolverError},
6     FluentArgs, FluentError,
7 };
8 use std::borrow::Cow;
9
10 /// Convert diagnostic arguments (a rustc internal type that exists to implement
11 /// `Encodable`/`Decodable`) into `FluentArgs` which is necessary to perform translation.
12 ///
13 /// Typically performed once for each diagnostic at the start of `emit_diagnostic` and then
14 /// passed around as a reference thereafter.
15 pub fn to_fluent_args<'iter, 'arg: 'iter>(
16     iter: impl Iterator<Item = DiagnosticArg<'iter, 'arg>>,
17 ) -> FluentArgs<'arg> {
18     let mut args = if let Some(size) = iter.size_hint().1 {
19         FluentArgs::with_capacity(size)
20     } else {
21         FluentArgs::new()
22     };
23
24     for (k, v) in iter {
25         args.set(k.clone(), v.clone());
26     }
27
28     args
29 }
30
31 pub trait Translate {
32     /// Return `FluentBundle` with localized diagnostics for the locale requested by the user. If no
33     /// language was requested by the user then this will be `None` and `fallback_fluent_bundle`
34     /// should be used.
35     fn fluent_bundle(&self) -> Option<&Lrc<FluentBundle>>;
36
37     /// Return `FluentBundle` with localized diagnostics for the default locale of the compiler.
38     /// Used when the user has not requested a specific language or when a localized diagnostic is
39     /// unavailable for the requested locale.
40     fn fallback_fluent_bundle(&self) -> &FluentBundle;
41
42     /// Convert `DiagnosticMessage`s to a string, performing translation if necessary.
43     fn translate_messages(
44         &self,
45         messages: &[(DiagnosticMessage, Style)],
46         args: &FluentArgs<'_>,
47     ) -> Cow<'_, str> {
48         Cow::Owned(
49             messages.iter().map(|(m, _)| self.translate_message(m, args)).collect::<String>(),
50         )
51     }
52
53     /// Convert a `DiagnosticMessage` to a string, performing translation if necessary.
54     fn translate_message<'a>(
55         &'a self,
56         message: &'a DiagnosticMessage,
57         args: &'a FluentArgs<'_>,
58     ) -> Cow<'_, str> {
59         trace!(?message, ?args);
60         let (identifier, attr) = match message {
61             DiagnosticMessage::Str(msg) | DiagnosticMessage::Eager(msg) => {
62                 return Cow::Borrowed(msg);
63             }
64             DiagnosticMessage::FluentIdentifier(identifier, attr) => (identifier, attr),
65         };
66
67         let translate_with_bundle = |bundle: &'a FluentBundle| -> Option<(Cow<'_, str>, Vec<_>)> {
68             let message = bundle.get_message(identifier)?;
69             let value = match attr {
70                 Some(attr) => message.get_attribute(attr)?.value(),
71                 None => message.value()?,
72             };
73             debug!(?message, ?value);
74
75             let mut errs = vec![];
76             let translated = bundle.format_pattern(value, Some(args), &mut errs);
77             debug!(?translated, ?errs);
78             Some((translated, errs))
79         };
80
81         self.fluent_bundle()
82             .and_then(|bundle| translate_with_bundle(bundle))
83             // If `translate_with_bundle` returns `None` with the primary bundle, this is likely
84             // just that the primary bundle doesn't contain the message being translated, so
85             // proceed to the fallback bundle.
86             //
87             // However, when errors are produced from translation, then that means the translation
88             // is broken (e.g. `{$foo}` exists in a translation but `foo` isn't provided).
89             //
90             // In debug builds, assert so that compiler devs can spot the broken translation and
91             // fix it..
92             .inspect(|(_, errs)| {
93                 debug_assert!(
94                     errs.is_empty(),
95                     "identifier: {:?}, attr: {:?}, args: {:?}, errors: {:?}",
96                     identifier,
97                     attr,
98                     args,
99                     errs
100                 );
101             })
102             // ..otherwise, for end users, an error about this wouldn't be useful or actionable, so
103             // just hide it and try with the fallback bundle.
104             .filter(|(_, errs)| errs.is_empty())
105             .or_else(|| translate_with_bundle(self.fallback_fluent_bundle()))
106             .map(|(translated, errs)| {
107                 // Always bail out for errors with the fallback bundle.
108
109                 let mut help_messages = vec![];
110
111                 if !errs.is_empty() {
112                     for error in &errs {
113                         match error {
114                             FluentError::ResolverError(ResolverError::Reference(
115                                 ReferenceKind::Message { id, .. },
116                             )) if args.iter().any(|(arg_id, _)| arg_id == id) => {
117                                 help_messages.push(format!("Argument `{id}` exists but was not referenced correctly. Try using `{{${id}}}` instead"));
118                             }
119                             _ => {}
120                         }
121                     }
122
123                     panic!(
124                         "Encountered errors while formatting message for `{identifier}`\n\
125                         help: {}\n\
126                         attr: `{attr:?}`\n\
127                         args: `{args:?}`\n\
128                         errors: `{errs:?}`",
129                         help_messages.join("\nhelp: ")
130                     );
131                 }
132
133                 translated
134             })
135             .expect("failed to find message in primary or fallback fluent bundles")
136     }
137 }