]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_error_messages/src/lib.rs
Rollup merge of #104523 - flba-eb:fix_nto_target_name, r=wesleywiser
[rust.git] / compiler / rustc_error_messages / src / lib.rs
1 #![feature(let_chains)]
2 #![feature(once_cell)]
3 #![feature(rustc_attrs)]
4 #![feature(type_alias_impl_trait)]
5 #![deny(rustc::untranslatable_diagnostic)]
6 #![deny(rustc::diagnostic_outside_of_impl)]
7
8 #[macro_use]
9 extern crate tracing;
10
11 use fluent_bundle::FluentResource;
12 use fluent_syntax::parser::ParserError;
13 use icu_provider_adapters::fallback::{LocaleFallbackProvider, LocaleFallbacker};
14 use rustc_data_structures::sync::Lrc;
15 use rustc_macros::{fluent_messages, Decodable, Encodable};
16 use rustc_span::Span;
17 use std::borrow::Cow;
18 use std::error::Error;
19 use std::fmt;
20 use std::fs;
21 use std::io;
22 use std::path::{Path, PathBuf};
23
24 #[cfg(not(parallel_compiler))]
25 use std::cell::LazyCell as Lazy;
26 #[cfg(parallel_compiler)]
27 use std::sync::LazyLock as Lazy;
28
29 #[cfg(parallel_compiler)]
30 use intl_memoizer::concurrent::IntlLangMemoizer;
31 #[cfg(not(parallel_compiler))]
32 use intl_memoizer::IntlLangMemoizer;
33
34 pub use fluent_bundle::{self, types::FluentType, FluentArgs, FluentError, FluentValue};
35 pub use unic_langid::{langid, LanguageIdentifier};
36
37 // Generates `DEFAULT_LOCALE_RESOURCES` static and `fluent_generated` module.
38 fluent_messages! {
39     // tidy-alphabetical-start
40     ast_lowering => "../locales/en-US/ast_lowering.ftl",
41     ast_passes => "../locales/en-US/ast_passes.ftl",
42     attr => "../locales/en-US/attr.ftl",
43     borrowck => "../locales/en-US/borrowck.ftl",
44     builtin_macros => "../locales/en-US/builtin_macros.ftl",
45     codegen_gcc => "../locales/en-US/codegen_gcc.ftl",
46     codegen_llvm => "../locales/en-US/codegen_llvm.ftl",
47     codegen_ssa => "../locales/en-US/codegen_ssa.ftl",
48     compiletest => "../locales/en-US/compiletest.ftl",
49     const_eval => "../locales/en-US/const_eval.ftl",
50     driver => "../locales/en-US/driver.ftl",
51     errors => "../locales/en-US/errors.ftl",
52     expand => "../locales/en-US/expand.ftl",
53     hir_analysis => "../locales/en-US/hir_analysis.ftl",
54     hir_typeck => "../locales/en-US/hir_typeck.ftl",
55     infer => "../locales/en-US/infer.ftl",
56     interface => "../locales/en-US/interface.ftl",
57     lint => "../locales/en-US/lint.ftl",
58     metadata => "../locales/en-US/metadata.ftl",
59     middle => "../locales/en-US/middle.ftl",
60     mir_dataflow => "../locales/en-US/mir_dataflow.ftl",
61     monomorphize => "../locales/en-US/monomorphize.ftl",
62     parse => "../locales/en-US/parse.ftl",
63     passes => "../locales/en-US/passes.ftl",
64     plugin_impl => "../locales/en-US/plugin_impl.ftl",
65     privacy => "../locales/en-US/privacy.ftl",
66     query_system => "../locales/en-US/query_system.ftl",
67     resolve => "../locales/en-US/resolve.ftl",
68     save_analysis => "../locales/en-US/save_analysis.ftl",
69     session => "../locales/en-US/session.ftl",
70     symbol_mangling => "../locales/en-US/symbol_mangling.ftl",
71     trait_selection => "../locales/en-US/trait_selection.ftl",
72     ty_utils => "../locales/en-US/ty_utils.ftl",
73     // tidy-alphabetical-end
74 }
75
76 pub use fluent_generated::{self as fluent, DEFAULT_LOCALE_RESOURCES};
77
78 pub type FluentBundle = fluent_bundle::bundle::FluentBundle<FluentResource, IntlLangMemoizer>;
79
80 #[cfg(parallel_compiler)]
81 fn new_bundle(locales: Vec<LanguageIdentifier>) -> FluentBundle {
82     FluentBundle::new_concurrent(locales)
83 }
84
85 #[cfg(not(parallel_compiler))]
86 fn new_bundle(locales: Vec<LanguageIdentifier>) -> FluentBundle {
87     FluentBundle::new(locales)
88 }
89
90 #[derive(Debug)]
91 pub enum TranslationBundleError {
92     /// Failed to read from `.ftl` file.
93     ReadFtl(io::Error),
94     /// Failed to parse contents of `.ftl` file.
95     ParseFtl(ParserError),
96     /// Failed to add `FluentResource` to `FluentBundle`.
97     AddResource(FluentError),
98     /// `$sysroot/share/locale/$locale` does not exist.
99     MissingLocale,
100     /// Cannot read directory entries of `$sysroot/share/locale/$locale`.
101     ReadLocalesDir(io::Error),
102     /// Cannot read directory entry of `$sysroot/share/locale/$locale`.
103     ReadLocalesDirEntry(io::Error),
104     /// `$sysroot/share/locale/$locale` is not a directory.
105     LocaleIsNotDir,
106 }
107
108 impl fmt::Display for TranslationBundleError {
109     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110         match self {
111             TranslationBundleError::ReadFtl(e) => write!(f, "could not read ftl file: {}", e),
112             TranslationBundleError::ParseFtl(e) => {
113                 write!(f, "could not parse ftl file: {}", e)
114             }
115             TranslationBundleError::AddResource(e) => write!(f, "failed to add resource: {}", e),
116             TranslationBundleError::MissingLocale => write!(f, "missing locale directory"),
117             TranslationBundleError::ReadLocalesDir(e) => {
118                 write!(f, "could not read locales dir: {}", e)
119             }
120             TranslationBundleError::ReadLocalesDirEntry(e) => {
121                 write!(f, "could not read locales dir entry: {}", e)
122             }
123             TranslationBundleError::LocaleIsNotDir => {
124                 write!(f, "`$sysroot/share/locales/$locale` is not a directory")
125             }
126         }
127     }
128 }
129
130 impl Error for TranslationBundleError {
131     fn source(&self) -> Option<&(dyn Error + 'static)> {
132         match self {
133             TranslationBundleError::ReadFtl(e) => Some(e),
134             TranslationBundleError::ParseFtl(e) => Some(e),
135             TranslationBundleError::AddResource(e) => Some(e),
136             TranslationBundleError::MissingLocale => None,
137             TranslationBundleError::ReadLocalesDir(e) => Some(e),
138             TranslationBundleError::ReadLocalesDirEntry(e) => Some(e),
139             TranslationBundleError::LocaleIsNotDir => None,
140         }
141     }
142 }
143
144 impl From<(FluentResource, Vec<ParserError>)> for TranslationBundleError {
145     fn from((_, mut errs): (FluentResource, Vec<ParserError>)) -> Self {
146         TranslationBundleError::ParseFtl(errs.pop().expect("failed ftl parse with no errors"))
147     }
148 }
149
150 impl From<Vec<FluentError>> for TranslationBundleError {
151     fn from(mut errs: Vec<FluentError>) -> Self {
152         TranslationBundleError::AddResource(
153             errs.pop().expect("failed adding resource to bundle with no errors"),
154         )
155     }
156 }
157
158 /// Returns Fluent bundle with the user's locale resources from
159 /// `$sysroot/share/locale/$requested_locale/*.ftl`.
160 ///
161 /// If `-Z additional-ftl-path` was provided, load that resource and add it  to the bundle
162 /// (overriding any conflicting messages).
163 #[instrument(level = "trace")]
164 pub fn fluent_bundle(
165     mut user_provided_sysroot: Option<PathBuf>,
166     mut sysroot_candidates: Vec<PathBuf>,
167     requested_locale: Option<LanguageIdentifier>,
168     additional_ftl_path: Option<&Path>,
169     with_directionality_markers: bool,
170 ) -> Result<Option<Lrc<FluentBundle>>, TranslationBundleError> {
171     if requested_locale.is_none() && additional_ftl_path.is_none() {
172         return Ok(None);
173     }
174
175     let fallback_locale = langid!("en-US");
176     let requested_fallback_locale = requested_locale.as_ref() == Some(&fallback_locale);
177
178     // If there is only `-Z additional-ftl-path`, assume locale is "en-US", otherwise use user
179     // provided locale.
180     let locale = requested_locale.clone().unwrap_or(fallback_locale);
181     trace!(?locale);
182     let mut bundle = new_bundle(vec![locale]);
183
184     // Fluent diagnostics can insert directionality isolation markers around interpolated variables
185     // indicating that there may be a shift from right-to-left to left-to-right text (or
186     // vice-versa). These are disabled because they are sometimes visible in the error output, but
187     // may be worth investigating in future (for example: if type names are left-to-right and the
188     // surrounding diagnostic messages are right-to-left, then these might be helpful).
189     bundle.set_use_isolating(with_directionality_markers);
190
191     // If the user requests the default locale then don't try to load anything.
192     if !requested_fallback_locale && let Some(requested_locale) = requested_locale {
193         let mut found_resources = false;
194         for sysroot in user_provided_sysroot.iter_mut().chain(sysroot_candidates.iter_mut()) {
195             sysroot.push("share");
196             sysroot.push("locale");
197             sysroot.push(requested_locale.to_string());
198             trace!(?sysroot);
199
200             if !sysroot.exists() {
201                 trace!("skipping");
202                 continue;
203             }
204
205             if !sysroot.is_dir() {
206                 return Err(TranslationBundleError::LocaleIsNotDir);
207             }
208
209             for entry in sysroot.read_dir().map_err(TranslationBundleError::ReadLocalesDir)? {
210                 let entry = entry.map_err(TranslationBundleError::ReadLocalesDirEntry)?;
211                 let path = entry.path();
212                 trace!(?path);
213                 if path.extension().and_then(|s| s.to_str()) != Some("ftl") {
214                     trace!("skipping");
215                     continue;
216                 }
217
218                 let resource_str =
219                     fs::read_to_string(path).map_err(TranslationBundleError::ReadFtl)?;
220                 let resource =
221                     FluentResource::try_new(resource_str).map_err(TranslationBundleError::from)?;
222                 trace!(?resource);
223                 bundle.add_resource(resource).map_err(TranslationBundleError::from)?;
224                 found_resources = true;
225             }
226         }
227
228         if !found_resources {
229             return Err(TranslationBundleError::MissingLocale);
230         }
231     }
232
233     if let Some(additional_ftl_path) = additional_ftl_path {
234         let resource_str =
235             fs::read_to_string(additional_ftl_path).map_err(TranslationBundleError::ReadFtl)?;
236         let resource =
237             FluentResource::try_new(resource_str).map_err(TranslationBundleError::from)?;
238         trace!(?resource);
239         bundle.add_resource_overriding(resource);
240     }
241
242     let bundle = Lrc::new(bundle);
243     Ok(Some(bundle))
244 }
245
246 /// Type alias for the result of `fallback_fluent_bundle` - a reference-counted pointer to a lazily
247 /// evaluated fluent bundle.
248 pub type LazyFallbackBundle = Lrc<Lazy<FluentBundle, impl FnOnce() -> FluentBundle>>;
249
250 /// Return the default `FluentBundle` with standard "en-US" diagnostic messages.
251 #[instrument(level = "trace")]
252 pub fn fallback_fluent_bundle(
253     resources: &'static [&'static str],
254     with_directionality_markers: bool,
255 ) -> LazyFallbackBundle {
256     Lrc::new(Lazy::new(move || {
257         let mut fallback_bundle = new_bundle(vec![langid!("en-US")]);
258         // See comment in `fluent_bundle`.
259         fallback_bundle.set_use_isolating(with_directionality_markers);
260
261         for resource in resources {
262             let resource = FluentResource::try_new(resource.to_string())
263                 .expect("failed to parse fallback fluent resource");
264             trace!(?resource);
265             fallback_bundle.add_resource_overriding(resource);
266         }
267
268         fallback_bundle
269     }))
270 }
271
272 /// Identifier for the Fluent message/attribute corresponding to a diagnostic message.
273 type FluentId = Cow<'static, str>;
274
275 /// Abstraction over a message in a subdiagnostic (i.e. label, note, help, etc) to support both
276 /// translatable and non-translatable diagnostic messages.
277 ///
278 /// Translatable messages for subdiagnostics are typically attributes attached to a larger Fluent
279 /// message so messages of this type must be combined with a `DiagnosticMessage` (using
280 /// `DiagnosticMessage::with_subdiagnostic_message`) before rendering. However, subdiagnostics from
281 /// the `Subdiagnostic` derive refer to Fluent identifiers directly.
282 #[rustc_diagnostic_item = "SubdiagnosticMessage"]
283 pub enum SubdiagnosticMessage {
284     /// Non-translatable diagnostic message.
285     // FIXME(davidtwco): can a `Cow<'static, str>` be used here?
286     Str(String),
287     /// Translatable message which has already been translated eagerly.
288     ///
289     /// Some diagnostics have repeated subdiagnostics where the same interpolated variables would
290     /// be instantiated multiple times with different values. As translation normally happens
291     /// immediately prior to emission, after the diagnostic and subdiagnostic derive logic has run,
292     /// the setting of diagnostic arguments in the derived code will overwrite previous variable
293     /// values and only the final value will be set when translation occurs - resulting in
294     /// incorrect diagnostics. Eager translation results in translation for a subdiagnostic
295     /// happening immediately after the subdiagnostic derive's logic has been run. This variant
296     /// stores messages which have been translated eagerly.
297     // FIXME(#100717): can a `Cow<'static, str>` be used here?
298     Eager(String),
299     /// Identifier of a Fluent message. Instances of this variant are generated by the
300     /// `Subdiagnostic` derive.
301     FluentIdentifier(FluentId),
302     /// Attribute of a Fluent message. Needs to be combined with a Fluent identifier to produce an
303     /// actual translated message. Instances of this variant are generated by the `fluent_messages`
304     /// macro.
305     ///
306     /// <https://projectfluent.org/fluent/guide/attributes.html>
307     FluentAttr(FluentId),
308 }
309
310 /// `From` impl that enables existing diagnostic calls to functions which now take
311 /// `impl Into<SubdiagnosticMessage>` to continue to work as before.
312 impl<S: Into<String>> From<S> for SubdiagnosticMessage {
313     fn from(s: S) -> Self {
314         SubdiagnosticMessage::Str(s.into())
315     }
316 }
317
318 /// Abstraction over a message in a diagnostic to support both translatable and non-translatable
319 /// diagnostic messages.
320 ///
321 /// Intended to be removed once diagnostics are entirely translatable.
322 #[derive(Clone, Debug, PartialEq, Eq, Hash, Encodable, Decodable)]
323 #[rustc_diagnostic_item = "DiagnosticMessage"]
324 pub enum DiagnosticMessage {
325     /// Non-translatable diagnostic message.
326     // FIXME(#100717): can a `Cow<'static, str>` be used here?
327     Str(String),
328     /// Translatable message which has already been translated eagerly.
329     ///
330     /// Some diagnostics have repeated subdiagnostics where the same interpolated variables would
331     /// be instantiated multiple times with different values. As translation normally happens
332     /// immediately prior to emission, after the diagnostic and subdiagnostic derive logic has run,
333     /// the setting of diagnostic arguments in the derived code will overwrite previous variable
334     /// values and only the final value will be set when translation occurs - resulting in
335     /// incorrect diagnostics. Eager translation results in translation for a subdiagnostic
336     /// happening immediately after the subdiagnostic derive's logic has been run. This variant
337     /// stores messages which have been translated eagerly.
338     // FIXME(#100717): can a `Cow<'static, str>` be used here?
339     Eager(String),
340     /// Identifier for a Fluent message (with optional attribute) corresponding to the diagnostic
341     /// message.
342     ///
343     /// <https://projectfluent.org/fluent/guide/hello.html>
344     /// <https://projectfluent.org/fluent/guide/attributes.html>
345     FluentIdentifier(FluentId, Option<FluentId>),
346 }
347
348 impl DiagnosticMessage {
349     /// Given a `SubdiagnosticMessage` which may contain a Fluent attribute, create a new
350     /// `DiagnosticMessage` that combines that attribute with the Fluent identifier of `self`.
351     ///
352     /// - If the `SubdiagnosticMessage` is non-translatable then return the message as a
353     /// `DiagnosticMessage`.
354     /// - If `self` is non-translatable then return `self`'s message.
355     pub fn with_subdiagnostic_message(&self, sub: SubdiagnosticMessage) -> Self {
356         let attr = match sub {
357             SubdiagnosticMessage::Str(s) => return DiagnosticMessage::Str(s),
358             SubdiagnosticMessage::Eager(s) => return DiagnosticMessage::Eager(s),
359             SubdiagnosticMessage::FluentIdentifier(id) => {
360                 return DiagnosticMessage::FluentIdentifier(id, None);
361             }
362             SubdiagnosticMessage::FluentAttr(attr) => attr,
363         };
364
365         match self {
366             DiagnosticMessage::Str(s) => DiagnosticMessage::Str(s.clone()),
367             DiagnosticMessage::Eager(s) => DiagnosticMessage::Eager(s.clone()),
368             DiagnosticMessage::FluentIdentifier(id, _) => {
369                 DiagnosticMessage::FluentIdentifier(id.clone(), Some(attr))
370             }
371         }
372     }
373 }
374
375 /// `From` impl that enables existing diagnostic calls to functions which now take
376 /// `impl Into<DiagnosticMessage>` to continue to work as before.
377 impl<S: Into<String>> From<S> for DiagnosticMessage {
378     fn from(s: S) -> Self {
379         DiagnosticMessage::Str(s.into())
380     }
381 }
382
383 /// A workaround for "good path" ICEs when formatting types in disables lints.
384 ///
385 /// Delays formatting until `.into(): DiagnosticMessage` is used.
386 pub struct DelayDm<F>(pub F);
387
388 impl<F: FnOnce() -> String> From<DelayDm<F>> for DiagnosticMessage {
389     fn from(DelayDm(f): DelayDm<F>) -> Self {
390         DiagnosticMessage::from(f())
391     }
392 }
393
394 /// Translating *into* a subdiagnostic message from a diagnostic message is a little strange - but
395 /// the subdiagnostic functions (e.g. `span_label`) take a `SubdiagnosticMessage` and the
396 /// subdiagnostic derive refers to typed identifiers that are `DiagnosticMessage`s, so need to be
397 /// able to convert between these, as much as they'll be converted back into `DiagnosticMessage`
398 /// using `with_subdiagnostic_message` eventually. Don't use this other than for the derive.
399 impl Into<SubdiagnosticMessage> for DiagnosticMessage {
400     fn into(self) -> SubdiagnosticMessage {
401         match self {
402             DiagnosticMessage::Str(s) => SubdiagnosticMessage::Str(s),
403             DiagnosticMessage::Eager(s) => SubdiagnosticMessage::Eager(s),
404             DiagnosticMessage::FluentIdentifier(id, None) => {
405                 SubdiagnosticMessage::FluentIdentifier(id)
406             }
407             // There isn't really a sensible behaviour for this because it loses information but
408             // this is the most sensible of the behaviours.
409             DiagnosticMessage::FluentIdentifier(_, Some(attr)) => {
410                 SubdiagnosticMessage::FluentAttr(attr)
411             }
412         }
413     }
414 }
415
416 /// A span together with some additional data.
417 #[derive(Clone, Debug)]
418 pub struct SpanLabel {
419     /// The span we are going to include in the final snippet.
420     pub span: Span,
421
422     /// Is this a primary span? This is the "locus" of the message,
423     /// and is indicated with a `^^^^` underline, versus `----`.
424     pub is_primary: bool,
425
426     /// What label should we attach to this span (if any)?
427     pub label: Option<DiagnosticMessage>,
428 }
429
430 /// A collection of `Span`s.
431 ///
432 /// Spans have two orthogonal attributes:
433 ///
434 /// - They can be *primary spans*. In this case they are the locus of
435 ///   the error, and would be rendered with `^^^`.
436 /// - They can have a *label*. In this case, the label is written next
437 ///   to the mark in the snippet when we render.
438 #[derive(Clone, Debug, Hash, PartialEq, Eq, Encodable, Decodable)]
439 pub struct MultiSpan {
440     primary_spans: Vec<Span>,
441     span_labels: Vec<(Span, DiagnosticMessage)>,
442 }
443
444 impl MultiSpan {
445     #[inline]
446     pub fn new() -> MultiSpan {
447         MultiSpan { primary_spans: vec![], span_labels: vec![] }
448     }
449
450     pub fn from_span(primary_span: Span) -> MultiSpan {
451         MultiSpan { primary_spans: vec![primary_span], span_labels: vec![] }
452     }
453
454     pub fn from_spans(mut vec: Vec<Span>) -> MultiSpan {
455         vec.sort();
456         MultiSpan { primary_spans: vec, span_labels: vec![] }
457     }
458
459     pub fn push_span_label(&mut self, span: Span, label: impl Into<DiagnosticMessage>) {
460         self.span_labels.push((span, label.into()));
461     }
462
463     /// Selects the first primary span (if any).
464     pub fn primary_span(&self) -> Option<Span> {
465         self.primary_spans.first().cloned()
466     }
467
468     /// Returns all primary spans.
469     pub fn primary_spans(&self) -> &[Span] {
470         &self.primary_spans
471     }
472
473     /// Returns `true` if any of the primary spans are displayable.
474     pub fn has_primary_spans(&self) -> bool {
475         !self.is_dummy()
476     }
477
478     /// Returns `true` if this contains only a dummy primary span with any hygienic context.
479     pub fn is_dummy(&self) -> bool {
480         self.primary_spans.iter().all(|sp| sp.is_dummy())
481     }
482
483     /// Replaces all occurrences of one Span with another. Used to move `Span`s in areas that don't
484     /// display well (like std macros). Returns whether replacements occurred.
485     pub fn replace(&mut self, before: Span, after: Span) -> bool {
486         let mut replacements_occurred = false;
487         for primary_span in &mut self.primary_spans {
488             if *primary_span == before {
489                 *primary_span = after;
490                 replacements_occurred = true;
491             }
492         }
493         for span_label in &mut self.span_labels {
494             if span_label.0 == before {
495                 span_label.0 = after;
496                 replacements_occurred = true;
497             }
498         }
499         replacements_occurred
500     }
501
502     /// Returns the strings to highlight. We always ensure that there
503     /// is an entry for each of the primary spans -- for each primary
504     /// span `P`, if there is at least one label with span `P`, we return
505     /// those labels (marked as primary). But otherwise we return
506     /// `SpanLabel` instances with empty labels.
507     pub fn span_labels(&self) -> Vec<SpanLabel> {
508         let is_primary = |span| self.primary_spans.contains(&span);
509
510         let mut span_labels = self
511             .span_labels
512             .iter()
513             .map(|&(span, ref label)| SpanLabel {
514                 span,
515                 is_primary: is_primary(span),
516                 label: Some(label.clone()),
517             })
518             .collect::<Vec<_>>();
519
520         for &span in &self.primary_spans {
521             if !span_labels.iter().any(|sl| sl.span == span) {
522                 span_labels.push(SpanLabel { span, is_primary: true, label: None });
523             }
524         }
525
526         span_labels
527     }
528
529     /// Returns `true` if any of the span labels is displayable.
530     pub fn has_span_labels(&self) -> bool {
531         self.span_labels.iter().any(|(sp, _)| !sp.is_dummy())
532     }
533 }
534
535 impl From<Span> for MultiSpan {
536     fn from(span: Span) -> MultiSpan {
537         MultiSpan::from_span(span)
538     }
539 }
540
541 impl From<Vec<Span>> for MultiSpan {
542     fn from(spans: Vec<Span>) -> MultiSpan {
543         MultiSpan::from_spans(spans)
544     }
545 }
546
547 fn icu_locale_from_unic_langid(lang: LanguageIdentifier) -> Option<icu_locid::Locale> {
548     icu_locid::Locale::try_from_bytes(lang.to_string().as_bytes()).ok()
549 }
550
551 pub fn fluent_value_from_str_list_sep_by_and<'source>(
552     l: Vec<Cow<'source, str>>,
553 ) -> FluentValue<'source> {
554     // Fluent requires 'static value here for its AnyEq usages.
555     #[derive(Clone, PartialEq, Debug)]
556     struct FluentStrListSepByAnd(Vec<String>);
557
558     impl FluentType for FluentStrListSepByAnd {
559         fn duplicate(&self) -> Box<dyn FluentType + Send> {
560             Box::new(self.clone())
561         }
562
563         fn as_string(&self, intls: &intl_memoizer::IntlLangMemoizer) -> Cow<'static, str> {
564             let result = intls
565                 .with_try_get::<MemoizableListFormatter, _, _>((), |list_formatter| {
566                     list_formatter.format_to_string(self.0.iter())
567                 })
568                 .unwrap();
569             Cow::Owned(result)
570         }
571
572         #[cfg(not(parallel_compiler))]
573         fn as_string_threadsafe(
574             &self,
575             _intls: &intl_memoizer::concurrent::IntlLangMemoizer,
576         ) -> Cow<'static, str> {
577             unreachable!("`as_string_threadsafe` is not used in non-parallel rustc")
578         }
579
580         #[cfg(parallel_compiler)]
581         fn as_string_threadsafe(
582             &self,
583             intls: &intl_memoizer::concurrent::IntlLangMemoizer,
584         ) -> Cow<'static, str> {
585             let result = intls
586                 .with_try_get::<MemoizableListFormatter, _, _>((), |list_formatter| {
587                     list_formatter.format_to_string(self.0.iter())
588                 })
589                 .unwrap();
590             Cow::Owned(result)
591         }
592     }
593
594     struct MemoizableListFormatter(icu_list::ListFormatter);
595
596     impl std::ops::Deref for MemoizableListFormatter {
597         type Target = icu_list::ListFormatter;
598         fn deref(&self) -> &Self::Target {
599             &self.0
600         }
601     }
602
603     impl intl_memoizer::Memoizable for MemoizableListFormatter {
604         type Args = ();
605         type Error = ();
606
607         fn construct(lang: LanguageIdentifier, _args: Self::Args) -> Result<Self, Self::Error>
608         where
609             Self: Sized,
610         {
611             let baked_data_provider = rustc_baked_icu_data::baked_data_provider();
612             let locale_fallbacker =
613                 LocaleFallbacker::try_new_with_any_provider(&baked_data_provider)
614                     .expect("Failed to create fallback provider");
615             let data_provider =
616                 LocaleFallbackProvider::new_with_fallbacker(baked_data_provider, locale_fallbacker);
617             let locale = icu_locale_from_unic_langid(lang)
618                 .unwrap_or_else(|| rustc_baked_icu_data::supported_locales::EN);
619             let list_formatter =
620                 icu_list::ListFormatter::try_new_and_with_length_with_any_provider(
621                     &data_provider,
622                     &locale.into(),
623                     icu_list::ListLength::Wide,
624                 )
625                 .expect("Failed to create list formatter");
626
627             Ok(MemoizableListFormatter(list_formatter))
628         }
629     }
630
631     let l = l.into_iter().map(|x| x.into_owned()).collect();
632
633     FluentValue::Custom(Box::new(FluentStrListSepByAnd(l)))
634 }