]>
Commit | Line | Data |
---|---|---|
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] | |
9 | extern crate tracing; | |
04454e1e FG |
10 | |
11 | use fluent_bundle::FluentResource; | |
12 | use fluent_syntax::parser::ParserError; | |
487cf647 | 13 | use icu_provider_adapters::fallback::{LocaleFallbackProvider, LocaleFallbacker}; |
04454e1e | 14 | use rustc_data_structures::sync::Lrc; |
923072b8 | 15 | use rustc_macros::{fluent_messages, Decodable, Encodable}; |
04454e1e FG |
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}; | |
04454e1e FG |
23 | |
24 | #[cfg(not(parallel_compiler))] | |
923072b8 | 25 | use std::cell::LazyCell as Lazy; |
04454e1e | 26 | #[cfg(parallel_compiler)] |
923072b8 | 27 | use std::sync::LazyLock as Lazy; |
04454e1e FG |
28 | |
29 | #[cfg(parallel_compiler)] | |
30 | use intl_memoizer::concurrent::IntlLangMemoizer; | |
31 | #[cfg(not(parallel_compiler))] | |
32 | use intl_memoizer::IntlLangMemoizer; | |
33 | ||
487cf647 | 34 | pub use fluent_bundle::{self, types::FluentType, FluentArgs, FluentError, FluentValue}; |
04454e1e FG |
35 | pub use unic_langid::{langid, LanguageIdentifier}; |
36 | ||
923072b8 FG |
37 | // Generates `DEFAULT_LOCALE_RESOURCES` static and `fluent_generated` module. |
38 | fluent_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 | ||
77 | pub use fluent_generated::{self as fluent, DEFAULT_LOCALE_RESOURCES}; | |
04454e1e FG |
78 | |
79 | pub type FluentBundle = fluent_bundle::bundle::FluentBundle<FluentResource, IntlLangMemoizer>; | |
80 | ||
81 | #[cfg(parallel_compiler)] | |
82 | fn new_bundle(locales: Vec<LanguageIdentifier>) -> FluentBundle { | |
83 | FluentBundle::new_concurrent(locales) | |
84 | } | |
85 | ||
86 | #[cfg(not(parallel_compiler))] | |
87 | fn new_bundle(locales: Vec<LanguageIdentifier>) -> FluentBundle { | |
88 | FluentBundle::new(locales) | |
89 | } | |
90 | ||
91 | #[derive(Debug)] | |
92 | pub 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 | ||
109 | impl 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 | ||
131 | impl 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 | ||
145 | impl 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 | ||
151 | impl 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")] | |
165 | pub 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. | |
249 | pub type LazyFallbackBundle = Lrc<Lazy<FluentBundle, impl FnOnce() -> FluentBundle>>; | |
250 | ||
251 | /// Return the default `FluentBundle` with standard "en-US" diagnostic messages. | |
252 | #[instrument(level = "trace")] | |
253 | pub 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. | |
274 | type 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"] |
284 | pub 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. | |
313 | impl<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 |
325 | pub 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 | ||
349 | impl 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. | |
378 | impl<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. | |
387 | pub struct DelayDm<F>(pub F); | |
388 | ||
389 | impl<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. | |
400 | impl 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)] | |
419 | pub 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)] | |
440 | pub struct MultiSpan { | |
441 | primary_spans: Vec<Span>, | |
442 | span_labels: Vec<(Span, DiagnosticMessage)>, | |
443 | } | |
444 | ||
445 | impl 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 | ||
536 | impl From<Span> for MultiSpan { | |
537 | fn from(span: Span) -> MultiSpan { | |
538 | MultiSpan::from_span(span) | |
539 | } | |
540 | } | |
541 | ||
542 | impl From<Vec<Span>> for MultiSpan { | |
543 | fn from(spans: Vec<Span>) -> MultiSpan { | |
544 | MultiSpan::from_spans(spans) | |
545 | } | |
546 | } | |
487cf647 FG |
547 | |
548 | fn 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 | 552 | pub 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 | } |