| use std::borrow::Cow; | |
| pub use fluent_bundle::types::FluentType; | |
| pub use fluent_bundle::{self, FluentArgs, FluentError, FluentValue}; | |
| use rustc_macros::{Decodable, Encodable, StableHash}; | |
| use rustc_span::Span; | |
| pub use unic_langid::{LanguageIdentifier, langid}; | |
| mod diagnostic_impls; | |
| pub use diagnostic_impls::DiagArgFromDisplay; | |
| use rustc_data_structures::fx::FxIndexMap; | |
| pub fn register_functions<R, M>(bundle: &mut fluent_bundle::bundle::FluentBundle<R, M>) { | |
| bundle | |
| .add_function("STREQ", |positional, _named| match positional { | |
| [FluentValue::String(a), FluentValue::String(b)] => format!("{}", (a == b)).into(), | |
| _ => FluentValue::Error, | |
| }) | |
| .expect("Failed to add a function to the bundle."); | |
| } | |
| /// Abstraction over a message in a diagnostic to support both translatable and non-translatable | |
| /// diagnostic messages. | |
| /// | |
| /// Intended to be removed once diagnostics are entirely translatable. | |
| #[derive(Clone, Debug, PartialEq, Eq, Hash, Encodable, Decodable, StableHash)] | |
| pub enum DiagMessage { | |
| /// Non-translatable diagnostic message or a message that has been translated eagerly. | |
| /// | |
| /// Some diagnostics have repeated subdiagnostics where the same interpolated variables would | |
| /// be instantiated multiple times with different values. These subdiagnostics' messages | |
| /// are translated when they are added to the parent diagnostic. This is one of the ways | |
| /// this variant of `DiagMessage` is produced. | |
| Str(Cow<'static, str>), | |
| /// An inline Fluent message, containing the to be translated diagnostic message. | |
| Inline(Cow<'static, str>), | |
| } | |
| impl DiagMessage { | |
| pub fn as_str(&self) -> Option<&str> { | |
| match self { | |
| DiagMessage::Str(s) => Some(s), | |
| DiagMessage::Inline(_) => None, | |
| } | |
| } | |
| } | |
| impl From<String> for DiagMessage { | |
| fn from(s: String) -> Self { | |
| DiagMessage::Str(Cow::Owned(s)) | |
| } | |
| } | |
| impl From<&'static str> for DiagMessage { | |
| fn from(s: &'static str) -> Self { | |
| DiagMessage::Str(Cow::Borrowed(s)) | |
| } | |
| } | |
| impl From<Cow<'static, str>> for DiagMessage { | |
| fn from(s: Cow<'static, str>) -> Self { | |
| DiagMessage::Str(s) | |
| } | |
| } | |
| /// A span together with some additional data. | |
| #[derive(Clone, Debug)] | |
| pub struct SpanLabel { | |
| /// The span we are going to include in the final snippet. | |
| pub span: Span, | |
| /// Is this a primary span? This is the "locus" of the message, | |
| /// and is indicated with a `^^^^` underline, versus `----`. | |
| pub is_primary: bool, | |
| /// What label should we attach to this span (if any)? | |
| pub label: Option<DiagMessage>, | |
| } | |
| /// A collection of `Span`s. | |
| /// | |
| /// Spans have two orthogonal attributes: | |
| /// | |
| /// - They can be *primary spans*. In this case they are the locus of | |
| /// the error, and would be rendered with `^^^`. | |
| /// - They can have a *label*. In this case, the label is written next | |
| /// to the mark in the snippet when we render. | |
| #[derive(Clone, Debug, Hash, PartialEq, Eq, Encodable, Decodable, StableHash)] | |
| pub struct MultiSpan { | |
| primary_spans: Vec<Span>, | |
| span_labels: Vec<(Span, DiagMessage)>, | |
| } | |
| impl MultiSpan { | |
| #[inline] | |
| pub fn new() -> MultiSpan { | |
| MultiSpan { primary_spans: vec![], span_labels: vec![] } | |
| } | |
| pub fn from_span(primary_span: Span) -> MultiSpan { | |
| MultiSpan { primary_spans: vec![primary_span], span_labels: vec![] } | |
| } | |
| pub fn from_spans(mut vec: Vec<Span>) -> MultiSpan { | |
| vec.sort(); | |
| MultiSpan { primary_spans: vec, span_labels: vec![] } | |
| } | |
| pub fn push_primary_span(&mut self, primary_span: Span) { | |
| self.primary_spans.push(primary_span); | |
| } | |
| pub fn push_span_label(&mut self, span: Span, label: impl Into<DiagMessage>) { | |
| self.span_labels.push((span, label.into())); | |
| } | |
| pub fn push_span_diag(&mut self, span: Span, diag: DiagMessage) { | |
| self.span_labels.push((span, diag)); | |
| } | |
| /// Selects the first primary span (if any). | |
| pub fn primary_span(&self) -> Option<Span> { | |
| self.primary_spans.first().cloned() | |
| } | |
| /// Returns all primary spans. | |
| pub fn primary_spans(&self) -> &[Span] { | |
| &self.primary_spans | |
| } | |
| /// Returns `true` if any of the primary spans are displayable. | |
| pub fn has_primary_spans(&self) -> bool { | |
| !self.is_dummy() | |
| } | |
| /// Returns `true` if this contains only a dummy primary span with any hygienic context. | |
| pub fn is_dummy(&self) -> bool { | |
| self.primary_spans.iter().all(|sp| sp.is_dummy()) | |
| } | |
| /// Replaces all occurrences of one Span with another. Used to move `Span`s in areas that don't | |
| /// display well (like std macros). Returns whether replacements occurred. | |
| pub fn replace(&mut self, before: Span, after: Span) -> bool { | |
| let mut replacements_occurred = false; | |
| for primary_span in &mut self.primary_spans { | |
| if *primary_span == before { | |
| *primary_span = after; | |
| replacements_occurred = true; | |
| } | |
| } | |
| for span_label in &mut self.span_labels { | |
| if span_label.0 == before { | |
| span_label.0 = after; | |
| replacements_occurred = true; | |
| } | |
| } | |
| replacements_occurred | |
| } | |
| /// Returns the strings to highlight. We always ensure that there | |
| /// is an entry for each of the primary spans -- for each primary | |
| /// span `P`, if there is at least one label with span `P`, we return | |
| /// those labels (marked as primary). But otherwise we return | |
| /// `SpanLabel` instances with empty labels. | |
| pub fn span_labels(&self) -> Vec<SpanLabel> { | |
| let is_primary = |span| self.primary_spans.contains(&span); | |
| let mut span_labels = self | |
| .span_labels | |
| .iter() | |
| .map(|&(span, ref label)| SpanLabel { | |
| span, | |
| is_primary: is_primary(span), | |
| label: Some(label.clone()), | |
| }) | |
| .collect::<Vec<_>>(); | |
| for &span in &self.primary_spans { | |
| if !span_labels.iter().any(|sl| sl.span == span) { | |
| span_labels.push(SpanLabel { span, is_primary: true, label: None }); | |
| } | |
| } | |
| span_labels | |
| } | |
| /// Returns the span labels as contained by `MultiSpan`. | |
| pub fn span_labels_raw(&self) -> &[(Span, DiagMessage)] { | |
| &self.span_labels | |
| } | |
| /// Returns `true` if any of the span labels is displayable. | |
| pub fn has_span_labels(&self) -> bool { | |
| self.span_labels.iter().any(|(sp, _)| !sp.is_dummy()) | |
| } | |
| /// Clone this `MultiSpan` without keeping any of the span labels - sometimes a `MultiSpan` is | |
| /// to be re-used in another diagnostic, but includes `span_labels` which have translated | |
| /// messages. These translated messages would fail to translate without their diagnostic | |
| /// arguments which are unlikely to be cloned alongside the `Span`. | |
| pub fn clone_ignoring_labels(&self) -> Self { | |
| Self { primary_spans: self.primary_spans.clone(), ..MultiSpan::new() } | |
| } | |
| } | |
| impl From<Span> for MultiSpan { | |
| fn from(span: Span) -> MultiSpan { | |
| MultiSpan::from_span(span) | |
| } | |
| } | |
| impl From<Vec<Span>> for MultiSpan { | |
| fn from(spans: Vec<Span>) -> MultiSpan { | |
| MultiSpan::from_spans(spans) | |
| } | |
| } | |
| fn icu_locale_from_unic_langid(lang: LanguageIdentifier) -> Option<icu_locale_core::Locale> { | |
| icu_locale_core::Locale::try_from_str(&lang.to_string()).ok() | |
| } | |
| pub fn fluent_value_from_str_list_sep_by_and(l: Vec<Cow<'_, str>>) -> FluentValue<'_> { | |
| // Fluent requires 'static value here for its AnyEq usages. | |
| #[derive(Clone, PartialEq, Debug)] | |
| struct FluentStrListSepByAnd(Vec<String>); | |
| impl FluentType for FluentStrListSepByAnd { | |
| fn duplicate(&self) -> Box<dyn FluentType + Send> { | |
| Box::new(self.clone()) | |
| } | |
| fn as_string(&self, intls: &intl_memoizer::IntlLangMemoizer) -> Cow<'static, str> { | |
| let result = intls | |
| .with_try_get::<MemoizableListFormatter, _, _>((), |list_formatter| { | |
| list_formatter.format_to_string(self.0.iter()) | |
| }) | |
| .unwrap(); | |
| Cow::Owned(result) | |
| } | |
| fn as_string_threadsafe( | |
| &self, | |
| intls: &intl_memoizer::concurrent::IntlLangMemoizer, | |
| ) -> Cow<'static, str> { | |
| let result = intls | |
| .with_try_get::<MemoizableListFormatter, _, _>((), |list_formatter| { | |
| list_formatter.format_to_string(self.0.iter()) | |
| }) | |
| .unwrap(); | |
| Cow::Owned(result) | |
| } | |
| } | |
| struct MemoizableListFormatter(icu_list::ListFormatter); | |
| impl std::ops::Deref for MemoizableListFormatter { | |
| type Target = icu_list::ListFormatter; | |
| fn deref(&self) -> &Self::Target { | |
| &self.0 | |
| } | |
| } | |
| impl intl_memoizer::Memoizable for MemoizableListFormatter { | |
| type Args = (); | |
| type Error = (); | |
| fn construct(lang: LanguageIdentifier, _args: Self::Args) -> Result<Self, Self::Error> { | |
| let locale = icu_locale_from_unic_langid(lang) | |
| .unwrap_or_else(|| rustc_baked_icu_data::supported_locales::EN); | |
| let list_formatter = icu_list::ListFormatter::try_new_and_unstable( | |
| &rustc_baked_icu_data::BakedDataProvider, | |
| locale.into(), | |
| icu_list::options::ListFormatterOptions::default() | |
| .with_length(icu_list::options::ListLength::Wide), | |
| ) | |
| .expect("Failed to create list formatter"); | |
| Ok(MemoizableListFormatter(list_formatter)) | |
| } | |
| } | |
| let l = l.into_iter().map(|x| x.into_owned()).collect(); | |
| FluentValue::Custom(Box::new(FluentStrListSepByAnd(l))) | |
| } | |
| /// Simplified version of `FluentArg` that can implement `Encodable` and `Decodable`. Collection of | |
| /// `DiagArg` are converted to `FluentArgs` (consuming the collection) at the start of diagnostic | |
| /// emission. | |
| pub type DiagArg<'iter> = (&'iter DiagArgName, &'iter DiagArgValue); | |
| /// Name of a diagnostic argument. | |
| pub type DiagArgName = Cow<'static, str>; | |
| /// Simplified version of `FluentValue` that can implement `Encodable` and `Decodable`. Converted | |
| /// to a `FluentValue` by the emitter to be used in diagnostic translation. | |
| #[derive(Clone, Debug, PartialEq, Eq, Hash, Encodable, Decodable)] | |
| pub enum DiagArgValue { | |
| Str(Cow<'static, str>), | |
| // This gets converted to a `FluentNumber`, which is an `f64`. An `i32` | |
| // safely fits in an `f64`. Any integers bigger than that will be converted | |
| // to strings in `into_diag_arg` and stored using the `Str` variant. | |
| Number(i32), | |
| StrListSepByAnd(Vec<Cow<'static, str>>), | |
| } | |
| /// A mapping from diagnostic argument names to their values. | |
| /// This contains all the arguments necessary to format a diagnostic message. | |
| pub type DiagArgMap = FxIndexMap<DiagArgName, DiagArgValue>; | |
| /// Converts a value of a type into a `DiagArg` (typically a field of an `Diag` struct). | |
| /// Implemented as a custom trait rather than `From` so that it is implemented on the type being | |
| /// converted rather than on `DiagArgValue`, which enables types from other `rustc_*` crates to | |
| /// implement this. | |
| pub trait IntoDiagArg { | |
| /// Convert `Self` into a `DiagArgValue` suitable for rendering in a diagnostic. | |
| /// | |
| /// It takes a `path` where "long values" could be written to, if the `DiagArgValue` is too big | |
| /// for displaying on the terminal. This path comes from the `Diag` itself. When rendering | |
| /// values that come from `TyCtxt`, like `Ty<'_>`, they can use `TyCtxt::short_string`. If a | |
| /// value has no shortening logic that could be used, the argument can be safely ignored. | |
| fn into_diag_arg(self, path: &mut Option<std::path::PathBuf>) -> DiagArgValue; | |
| } | |
| impl IntoDiagArg for DiagArgValue { | |
| fn into_diag_arg(self, _: &mut Option<std::path::PathBuf>) -> DiagArgValue { | |
| self | |
| } | |
| } | |
| impl From<DiagArgValue> for FluentValue<'static> { | |
| fn from(val: DiagArgValue) -> Self { | |
| match val { | |
| DiagArgValue::Str(s) => From::from(s), | |
| DiagArgValue::Number(n) => From::from(n), | |
| DiagArgValue::StrListSepByAnd(l) => fluent_value_from_str_list_sep_by_and(l), | |
| } | |
| } | |
| } |