rustc_error_messages/
lib.rs

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