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