diff --git a/Cargo.lock b/Cargo.lock index 34b200af522..7983b79d5d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1238,6 +1238,50 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fluent" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61f69378194459db76abd2ce3952b790db103ceb003008d3d50d97c41ff847a7" +dependencies = [ + "fluent-bundle", + "unic-langid", +] + +[[package]] +name = "fluent-bundle" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e242c601dec9711505f6d5bbff5bedd4b61b2469f2e8bb8e57ee7c9747a87ffd" +dependencies = [ + "fluent-langneg", + "fluent-syntax", + "intl-memoizer", + "intl_pluralrules", + "rustc-hash", + "self_cell", + "smallvec", + "unic-langid", +] + +[[package]] +name = "fluent-langneg" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94" +dependencies = [ + "unic-langid", +] + +[[package]] +name = "fluent-syntax" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0abed97648395c902868fee9026de96483933faa54ea3b40d652f7dfe61ca78" +dependencies = [ + "thiserror", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1782,6 +1826,26 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "intl-memoizer" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c310433e4a310918d6ed9243542a6b83ec1183df95dff8f23f87bb88a264a66f" +dependencies = [ + "type-map", + "unic-langid", +] + +[[package]] +name = "intl_pluralrules" +version = "7.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b18f988384267d7066cc2be425e6faf352900652c046b6971d2e228d3b1c5ecf" +dependencies = [ + "tinystr", + "unic-langid", +] + [[package]] name = "itertools" version = "0.10.1" @@ -2812,6 +2876,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + [[package]] name = "proc-macro2" version = "1.0.30" @@ -3649,9 +3719,13 @@ version = "0.0.0" name = "rustc_error_messages" version = "0.0.0" dependencies = [ + "fluent", + "rustc_data_structures", "rustc_macros", "rustc_serialize", "rustc_span", + "tracing", + "unic-langid", ] [[package]] @@ -4585,6 +4659,12 @@ dependencies = [ "libc", ] +[[package]] +name = "self_cell" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ef965a420fe14fdac7dd018862966a4c14094f900e1650bbc71ddd7d580c8af" + [[package]] name = "semver" version = "1.0.3" @@ -5116,6 +5196,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "tinystr" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29738eedb4388d9ea620eeab9384884fc3f06f586a2eddb56bedc5885126c7c1" + [[package]] name = "tinyvec" version = "0.3.4" @@ -5274,6 +5360,15 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "type-map" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d3364c5e96cb2ad1603037ab253ddd34d7fb72a58bdddf4b7350760fc69a46" +dependencies = [ + "rustc-hash", +] + [[package]] name = "typenum" version = "1.12.0" @@ -5328,6 +5423,49 @@ dependencies = [ "unic-ucd-version", ] +[[package]] +name = "unic-langid" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73328fcd730a030bdb19ddf23e192187a6b01cd98be6d3140622a89129459ce5" +dependencies = [ + "unic-langid-impl", + "unic-langid-macros", +] + +[[package]] +name = "unic-langid-impl" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a4a8eeaf0494862c1404c95ec2f4c33a2acff5076f64314b465e3ddae1b934d" +dependencies = [ + "tinystr", +] + +[[package]] +name = "unic-langid-macros" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18f980d6d87e8805f2836d64b4138cc95aa7986fa63b1f51f67d5fbff64dd6e5" +dependencies = [ + "proc-macro-hack", + "tinystr", + "unic-langid-impl", + "unic-langid-macros-impl", +] + +[[package]] +name = "unic-langid-macros-impl" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29396ffd97e27574c3e01368b1a64267d3064969e4848e2e130ff668be9daa9f" +dependencies = [ + "proc-macro-hack", + "quote", + "syn", + "unic-langid-impl", +] + [[package]] name = "unic-ucd-version" version = "0.9.0" diff --git a/compiler/rustc_borrowck/src/diagnostics/region_name.rs b/compiler/rustc_borrowck/src/diagnostics/region_name.rs index c9395492c9e..723adb8da1b 100644 --- a/compiler/rustc_borrowck/src/diagnostics/region_name.rs +++ b/compiler/rustc_borrowck/src/diagnostics/region_name.rs @@ -109,7 +109,7 @@ impl RegionName { *span, format!("lifetime `{}` represents this closure's body", self), ); - diag.note(¬e); + diag.note(note); } RegionNameSource::AnonRegionFromArgument(RegionNameHighlight::CannotMatchHirTy( span, diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index 8aa18b8e37c..92c4ab7eb86 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -1707,23 +1707,33 @@ impl SharedEmitter { impl Emitter for SharedEmitter { fn emit_diagnostic(&mut self, diag: &rustc_errors::Diagnostic) { + let fluent_args = self.to_fluent_args(diag.args()); drop(self.sender.send(SharedEmitterMessage::Diagnostic(Diagnostic { - msg: diag.message().to_string(), + msg: self.translate_messages(&diag.message, &fluent_args).to_string(), code: diag.code.clone(), lvl: diag.level(), }))); for child in &diag.children { drop(self.sender.send(SharedEmitterMessage::Diagnostic(Diagnostic { - msg: child.message().to_string(), + msg: self.translate_messages(&child.message, &fluent_args).to_string(), code: None, lvl: child.level, }))); } drop(self.sender.send(SharedEmitterMessage::AbortIfErrors)); } + fn source_map(&self) -> Option<&Lrc> { None } + + fn fluent_bundle(&self) -> Option<&Lrc> { + None + } + + fn fallback_fluent_bundle(&self) -> &Lrc { + panic!("shared emitter attempted to translate a diagnostic"); + } } impl SharedEmitterMain { @@ -1754,9 +1764,9 @@ impl SharedEmitterMain { let msg = msg.strip_prefix("error: ").unwrap_or(&msg); let mut err = match level { - Level::Error { lint: false } => sess.struct_err(&msg).forget_guarantee(), - Level::Warning => sess.struct_warn(&msg), - Level::Note => sess.struct_note_without_error(&msg), + Level::Error { lint: false } => sess.struct_err(msg).forget_guarantee(), + Level::Warning => sess.struct_warn(msg), + Level::Note => sess.struct_note_without_error(msg), _ => bug!("Invalid inline asm diagnostic level"), }; diff --git a/compiler/rustc_driver/src/lib.rs b/compiler/rustc_driver/src/lib.rs index 69f96d07f90..c95c59904d1 100644 --- a/compiler/rustc_driver/src/lib.rs +++ b/compiler/rustc_driver/src/lib.rs @@ -1172,9 +1172,11 @@ static DEFAULT_HOOK: SyncLazy) + Sync + Send + /// When `install_ice_hook` is called, this function will be called as the panic /// hook. pub fn report_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) { + let fallback_bundle = rustc_errors::fallback_fluent_bundle(); let emitter = Box::new(rustc_errors::emitter::EmitterWriter::stderr( rustc_errors::ColorConfig::Auto, None, + fallback_bundle, false, false, None, @@ -1209,7 +1211,7 @@ pub fn report_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) { } for note in &xs { - handler.note_without_error(note); + handler.note_without_error(note.as_ref()); } // If backtraces are enabled, also print the query stack diff --git a/compiler/rustc_error_messages/Cargo.toml b/compiler/rustc_error_messages/Cargo.toml index 194a11dd3ea..abd565cd529 100644 --- a/compiler/rustc_error_messages/Cargo.toml +++ b/compiler/rustc_error_messages/Cargo.toml @@ -7,6 +7,10 @@ edition = "2021" doctest = false [dependencies] +fluent = "0.16.0" +rustc_data_structures = { path = "../rustc_data_structures" } rustc_serialize = { path = "../rustc_serialize" } rustc_span = { path = "../rustc_span" } rustc_macros = { path = "../rustc_macros" } +tracing = "0.1" +unic-langid = { version = "0.9.0", features = ["macros"] } diff --git a/compiler/rustc_error_messages/locales/en-US/diagnostics.ftl b/compiler/rustc_error_messages/locales/en-US/diagnostics.ftl new file mode 100644 index 00000000000..e89fa586d2a --- /dev/null +++ b/compiler/rustc_error_messages/locales/en-US/diagnostics.ftl @@ -0,0 +1,2 @@ +parser-struct-literal-body-without-path = struct literal body without path + .suggestion = you might have forgotten to add the struct literal inside the block diff --git a/compiler/rustc_error_messages/src/lib.rs b/compiler/rustc_error_messages/src/lib.rs index 57d4a4c1f5e..105bf1413de 100644 --- a/compiler/rustc_error_messages/src/lib.rs +++ b/compiler/rustc_error_messages/src/lib.rs @@ -1,31 +1,75 @@ +use rustc_data_structures::sync::Lrc; use rustc_macros::{Decodable, Encodable}; use rustc_span::Span; +use std::borrow::Cow; +use tracing::debug; + +pub use fluent::{FluentArgs, FluentValue}; + +static FALLBACK_FLUENT_RESOURCE: &'static str = include_str!("../locales/en-US/diagnostics.ftl"); + +pub type FluentBundle = fluent::FluentBundle; + +/// Return the default `FluentBundle` with standard en-US diagnostic messages. +pub fn fallback_fluent_bundle() -> Lrc { + let fallback_resource = fluent::FluentResource::try_new(FALLBACK_FLUENT_RESOURCE.to_string()) + .expect("failed to parse ftl resource"); + debug!(?fallback_resource); + let mut fallback_bundle = FluentBundle::new(vec![unic_langid::langid!("en-US")]); + fallback_bundle.add_resource(fallback_resource).expect("failed to add resource to bundle"); + let fallback_bundle = Lrc::new(fallback_bundle); + fallback_bundle +} + +/// Identifier for the Fluent message/attribute corresponding to a diagnostic message. +type FluentId = Cow<'static, str>; /// 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)] pub enum DiagnosticMessage { /// Non-translatable diagnostic message. + // FIXME(davidtwco): can a `Cow<'static, str>` be used here? Str(String), /// Identifier for a Fluent message corresponding to the diagnostic message. - FluentIdentifier(String), + FluentIdentifier(FluentId, Option), } impl DiagnosticMessage { - /// Convert `DiagnosticMessage` to a `&str`. - pub fn as_str(&self) -> &str { + /// Returns the `String` contained within the `DiagnosticMessage::Str` variant, assuming that + /// this diagnostic message is of the legacy, non-translatable variety. Panics if this + /// assumption does not hold. + /// + /// Don't use this - it exists to support some places that do comparison with diagnostic + /// strings. + pub fn expect_str(&self) -> &str { match self { - DiagnosticMessage::Str(msg) => msg, - DiagnosticMessage::FluentIdentifier(..) => unimplemented!(), + DiagnosticMessage::Str(s) => s, + _ => panic!("expected non-translatable diagnostic message"), } } - /// Convert `DiagnosticMessage` to an owned `String`. - pub fn to_string(self) -> String { - match self { - DiagnosticMessage::Str(msg) => msg, - DiagnosticMessage::FluentIdentifier(..) => unimplemented!(), - } + /// Create a `DiagnosticMessage` for the provided Fluent identifier. + pub fn fluent(id: impl Into>) -> Self { + DiagnosticMessage::FluentIdentifier(id.into(), None) + } + + /// Create a `DiagnosticMessage` for the provided Fluent identifier and attribute. + pub fn fluent_attr( + id: impl Into>, + attr: impl Into>, + ) -> Self { + DiagnosticMessage::FluentIdentifier(id.into(), Some(attr.into())) + } +} + +/// `From` impl that enables existing diagnostic calls to functions which now take +/// `impl Into` to continue to work as before. +impl> From for DiagnosticMessage { + fn from(s: S) -> Self { + DiagnosticMessage::Str(s.into()) } } @@ -72,12 +116,8 @@ impl MultiSpan { MultiSpan { primary_spans: vec, span_labels: vec![] } } - pub fn push_span_label(&mut self, span: Span, label: String) { - self.span_labels.push((span, DiagnosticMessage::Str(label))); - } - - pub fn push_span_message(&mut self, span: Span, message: DiagnosticMessage) { - self.span_labels.push((span, message)); + pub fn push_span_label(&mut self, span: Span, label: impl Into) { + self.span_labels.push((span, label.into())); } /// Selects the first primary span (if any). diff --git a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs index 330c3d218fc..4dc99b8dd0f 100644 --- a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs +++ b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs @@ -7,16 +7,22 @@ use crate::emitter::FileWithAnnotatedLines; use crate::snippet::Line; -use crate::{CodeSuggestion, Diagnostic, DiagnosticId, Emitter, Level, MultiSpan, SubDiagnostic}; +use crate::{ + CodeSuggestion, Diagnostic, DiagnosticId, DiagnosticMessage, Emitter, FluentBundle, Level, + MultiSpan, Style, SubDiagnostic, +}; use annotate_snippets::display_list::{DisplayList, FormatOptions}; use annotate_snippets::snippet::*; use rustc_data_structures::sync::Lrc; +use rustc_error_messages::FluentArgs; use rustc_span::source_map::SourceMap; use rustc_span::SourceFile; /// Generates diagnostics using annotate-snippet pub struct AnnotateSnippetEmitterWriter { source_map: Option>, + fallback_bundle: Lrc, + /// If true, hides the longer explanation text short_message: bool, /// If true, will normalize line numbers with `LL` to prevent noise in UI test diffs. @@ -28,8 +34,10 @@ pub struct AnnotateSnippetEmitterWriter { impl Emitter for AnnotateSnippetEmitterWriter { /// The entry point for the diagnostics generation fn emit_diagnostic(&mut self, diag: &Diagnostic) { + let fluent_args = self.to_fluent_args(diag.args()); + let mut children = diag.children.clone(); - let (mut primary_span, suggestions) = self.primary_span_formatted(&diag); + let (mut primary_span, suggestions) = self.primary_span_formatted(&diag, &fluent_args); self.fix_multispans_in_extern_macros_and_render_macro_backtrace( &self.source_map, @@ -41,7 +49,8 @@ impl Emitter for AnnotateSnippetEmitterWriter { self.emit_messages_default( &diag.level, - diag.message().to_string(), + &diag.message, + &fluent_args, &diag.code, &primary_span, &children, @@ -53,6 +62,14 @@ impl Emitter for AnnotateSnippetEmitterWriter { self.source_map.as_ref() } + fn fluent_bundle(&self) -> Option<&Lrc> { + None + } + + fn fallback_fluent_bundle(&self) -> &Lrc { + &self.fallback_bundle + } + fn should_show_explain(&self) -> bool { !self.short_message } @@ -82,10 +99,11 @@ fn annotation_type_for_level(level: Level) -> AnnotationType { impl AnnotateSnippetEmitterWriter { pub fn new( source_map: Option>, + fallback_bundle: Lrc, short_message: bool, macro_backtrace: bool, ) -> Self { - Self { source_map, short_message, ui_testing: false, macro_backtrace } + Self { source_map, fallback_bundle, short_message, ui_testing: false, macro_backtrace } } /// Allows to modify `Self` to enable or disable the `ui_testing` flag. @@ -99,12 +117,14 @@ impl AnnotateSnippetEmitterWriter { fn emit_messages_default( &mut self, level: &Level, - message: String, + messages: &[(DiagnosticMessage, Style)], + args: &FluentArgs<'_>, code: &Option, msp: &MultiSpan, _children: &[SubDiagnostic], _suggestions: &[CodeSuggestion], ) { + let message = self.translate_messages(messages, args); if let Some(source_map) = &self.source_map { // Make sure our primary file comes first let primary_lo = if let Some(ref primary_span) = msp.primary_span().as_ref() { @@ -120,8 +140,7 @@ impl AnnotateSnippetEmitterWriter { // should be done if it happens return; }; - let mut annotated_files = - FileWithAnnotatedLines::collect_annotations(msp, &self.source_map); + let mut annotated_files = FileWithAnnotatedLines::collect_annotations(self, args, msp); if let Ok(pos) = annotated_files.binary_search_by(|x| x.file.name.cmp(&primary_lo.file.name)) { diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs index f2c0e2701b3..4a140eaf0b5 100644 --- a/compiler/rustc_errors/src/diagnostic.rs +++ b/compiler/rustc_errors/src/diagnostic.rs @@ -4,10 +4,12 @@ use crate::{ SuggestionStyle, ToolMetadata, }; use rustc_data_structures::stable_map::FxHashMap; +use rustc_error_messages::FluentValue; use rustc_lint_defs::{Applicability, LintExpectationId}; use rustc_serialize::json::Json; use rustc_span::edition::LATEST_STABLE_EDITION; use rustc_span::{Span, DUMMY_SP}; +use std::borrow::Cow; use std::fmt; use std::hash::{Hash, Hasher}; @@ -16,6 +18,28 @@ use std::hash::{Hash, Hasher}; #[derive(Clone, Debug, PartialEq, Eq, Hash, Encodable, Decodable)] pub struct SuggestionsDisabled; +/// Simplified version of `FluentArg` that can implement `Encodable` and `Decodable`. Collection of +/// `DiagnosticArg` are converted to `FluentArgs` (consuming the collection) at the start of +/// diagnostic emission. +pub type DiagnosticArg<'source> = (Cow<'source, str>, DiagnosticArgValue<'source>); + +/// 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 DiagnosticArgValue<'source> { + Str(Cow<'source, str>), + Number(usize), +} + +impl<'source> Into> for DiagnosticArgValue<'source> { + fn into(self) -> FluentValue<'source> { + match self { + DiagnosticArgValue::Str(s) => From::from(s), + DiagnosticArgValue::Number(n) => From::from(n), + } + } +} + #[must_use] #[derive(Clone, Debug, Encodable, Decodable)] pub struct Diagnostic { @@ -28,6 +52,7 @@ pub struct Diagnostic { pub span: MultiSpan, pub children: Vec, pub suggestions: Result, SuggestionsDisabled>, + args: Vec>, /// This is not used for highlighting or rendering any error message. Rather, it can be used /// as a sort key to sort a buffer of diagnostics. By default, it is the primary span of @@ -103,18 +128,23 @@ impl StringPart { } impl Diagnostic { - pub fn new(level: Level, message: &str) -> Self { + pub fn new>(level: Level, message: M) -> Self { Diagnostic::new_with_code(level, None, message) } - pub fn new_with_code(level: Level, code: Option, message: &str) -> Self { + pub fn new_with_code>( + level: Level, + code: Option, + message: M, + ) -> Self { Diagnostic { level, - message: vec![(DiagnosticMessage::Str(message.to_owned()), Style::NoStyle)], + message: vec![(message.into(), Style::NoStyle)], code, span: MultiSpan::new(), children: vec![], suggestions: Ok(vec![]), + args: vec![], sort_span: DUMMY_SP, is_lint: false, } @@ -232,7 +262,7 @@ impl Diagnostic { self.set_span(after); for span_label in before.span_labels() { if let Some(label) = span_label.label { - self.span.push_span_message(after, label); + self.span.push_span_label(after, label); } } self @@ -326,52 +356,67 @@ impl Diagnostic { } /// Add a note attached to this diagnostic. - pub fn note(&mut self, msg: &str) -> &mut Self { + pub fn note(&mut self, msg: impl Into) -> &mut Self { self.sub(Level::Note, msg, MultiSpan::new(), None); self } - pub fn highlighted_note(&mut self, msg: Vec<(String, Style)>) -> &mut Self { + pub fn highlighted_note>( + &mut self, + msg: Vec<(M, Style)>, + ) -> &mut Self { self.sub_with_highlights(Level::Note, msg, MultiSpan::new(), None); self } /// Prints the span with a note above it. /// This is like [`Diagnostic::note()`], but it gets its own span. - pub fn note_once(&mut self, msg: &str) -> &mut Self { + pub fn note_once(&mut self, msg: impl Into) -> &mut Self { self.sub(Level::OnceNote, msg, MultiSpan::new(), None); self } /// Prints the span with a note above it. /// This is like [`Diagnostic::note()`], but it gets its own span. - pub fn span_note>(&mut self, sp: S, msg: &str) -> &mut Self { + pub fn span_note>( + &mut self, + sp: S, + msg: impl Into, + ) -> &mut Self { self.sub(Level::Note, msg, sp.into(), None); self } /// Prints the span with a note above it. /// This is like [`Diagnostic::note()`], but it gets its own span. - pub fn span_note_once>(&mut self, sp: S, msg: &str) -> &mut Self { + pub fn span_note_once>( + &mut self, + sp: S, + msg: impl Into, + ) -> &mut Self { self.sub(Level::OnceNote, msg, sp.into(), None); self } /// Add a warning attached to this diagnostic. - pub fn warn(&mut self, msg: &str) -> &mut Self { + pub fn warn(&mut self, msg: impl Into) -> &mut Self { self.sub(Level::Warning, msg, MultiSpan::new(), None); self } /// Prints the span with a warning above it. /// This is like [`Diagnostic::warn()`], but it gets its own span. - pub fn span_warn>(&mut self, sp: S, msg: &str) -> &mut Self { + pub fn span_warn>( + &mut self, + sp: S, + msg: impl Into, + ) -> &mut Self { self.sub(Level::Warning, msg, sp.into(), None); self } /// Add a help message attached to this diagnostic. - pub fn help(&mut self, msg: &str) -> &mut Self { + pub fn help(&mut self, msg: impl Into) -> &mut Self { self.sub(Level::Help, msg, MultiSpan::new(), None); self } @@ -384,7 +429,11 @@ impl Diagnostic { /// Prints the span with some help above it. /// This is like [`Diagnostic::help()`], but it gets its own span. - pub fn span_help>(&mut self, sp: S, msg: &str) -> &mut Self { + pub fn span_help>( + &mut self, + sp: S, + msg: impl Into, + ) -> &mut Self { self.sub(Level::Help, msg, sp.into(), None); self } @@ -420,7 +469,7 @@ impl Diagnostic { /// In other words, multiple changes need to be applied as part of this suggestion. pub fn multipart_suggestion( &mut self, - msg: &str, + msg: impl Into, suggestion: Vec<(Span, String)>, applicability: Applicability, ) -> &mut Self { @@ -436,7 +485,7 @@ impl Diagnostic { /// In other words, multiple changes need to be applied as part of this suggestion. pub fn multipart_suggestion_verbose( &mut self, - msg: &str, + msg: impl Into, suggestion: Vec<(Span, String)>, applicability: Applicability, ) -> &mut Self { @@ -450,7 +499,7 @@ impl Diagnostic { /// [`Diagnostic::multipart_suggestion()`] but you can set the [`SuggestionStyle`]. pub fn multipart_suggestion_with_style( &mut self, - msg: &str, + msg: impl Into, suggestion: Vec<(Span, String)>, applicability: Applicability, style: SuggestionStyle, @@ -463,7 +512,7 @@ impl Diagnostic { .map(|(span, snippet)| SubstitutionPart { snippet, span }) .collect(), }], - msg: DiagnosticMessage::Str(msg.to_owned()), + msg: msg.into(), style, applicability, tool_metadata: Default::default(), @@ -479,7 +528,7 @@ impl Diagnostic { /// improve understandability. pub fn tool_only_multipart_suggestion( &mut self, - msg: &str, + msg: impl Into, suggestion: Vec<(Span, String)>, applicability: Applicability, ) -> &mut Self { @@ -491,7 +540,7 @@ impl Diagnostic { .map(|(span, snippet)| SubstitutionPart { snippet, span }) .collect(), }], - msg: DiagnosticMessage::Str(msg.to_owned()), + msg: msg.into(), style: SuggestionStyle::CompletelyHidden, applicability, tool_metadata: Default::default(), @@ -519,7 +568,7 @@ impl Diagnostic { pub fn span_suggestion( &mut self, sp: Span, - msg: &str, + msg: impl Into, suggestion: String, applicability: Applicability, ) -> &mut Self { @@ -537,7 +586,7 @@ impl Diagnostic { pub fn span_suggestion_with_style( &mut self, sp: Span, - msg: &str, + msg: impl Into, suggestion: String, applicability: Applicability, style: SuggestionStyle, @@ -546,7 +595,7 @@ impl Diagnostic { substitutions: vec![Substitution { parts: vec![SubstitutionPart { snippet: suggestion, span: sp }], }], - msg: DiagnosticMessage::Str(msg.to_owned()), + msg: msg.into(), style, applicability, tool_metadata: Default::default(), @@ -558,7 +607,7 @@ impl Diagnostic { pub fn span_suggestion_verbose( &mut self, sp: Span, - msg: &str, + msg: impl Into, suggestion: String, applicability: Applicability, ) -> &mut Self { @@ -577,7 +626,7 @@ impl Diagnostic { pub fn span_suggestions( &mut self, sp: Span, - msg: &str, + msg: impl Into, suggestions: impl Iterator, applicability: Applicability, ) -> &mut Self { @@ -589,7 +638,7 @@ impl Diagnostic { .collect(); self.push_suggestion(CodeSuggestion { substitutions, - msg: DiagnosticMessage::Str(msg.to_owned()), + msg: msg.into(), style: SuggestionStyle::ShowCode, applicability, tool_metadata: Default::default(), @@ -601,7 +650,7 @@ impl Diagnostic { /// See also [`Diagnostic::span_suggestion()`]. pub fn multipart_suggestions( &mut self, - msg: &str, + msg: impl Into, suggestions: impl Iterator>, applicability: Applicability, ) -> &mut Self { @@ -614,7 +663,7 @@ impl Diagnostic { .collect(), }) .collect(), - msg: DiagnosticMessage::Str(msg.to_owned()), + msg: msg.into(), style: SuggestionStyle::ShowCode, applicability, tool_metadata: Default::default(), @@ -628,7 +677,7 @@ impl Diagnostic { pub fn span_suggestion_short( &mut self, sp: Span, - msg: &str, + msg: impl Into, suggestion: String, applicability: Applicability, ) -> &mut Self { @@ -651,7 +700,7 @@ impl Diagnostic { pub fn span_suggestion_hidden( &mut self, sp: Span, - msg: &str, + msg: impl Into, suggestion: String, applicability: Applicability, ) -> &mut Self { @@ -672,7 +721,7 @@ impl Diagnostic { pub fn tool_only_span_suggestion( &mut self, sp: Span, - msg: &str, + msg: impl Into, suggestion: String, applicability: Applicability, ) -> &mut Self { @@ -690,13 +739,13 @@ impl Diagnostic { /// the suggestion in a tool-specific way, as it may not even directly involve Rust code. pub fn tool_only_suggestion_with_metadata( &mut self, - msg: &str, + msg: impl Into, applicability: Applicability, tool_metadata: Json, ) { self.push_suggestion(CodeSuggestion { substitutions: vec![], - msg: DiagnosticMessage::Str(msg.to_owned()), + msg: msg.into(), style: SuggestionStyle::CompletelyHidden, applicability, tool_metadata: ToolMetadata::new(tool_metadata), @@ -730,13 +779,13 @@ impl Diagnostic { self.code.clone() } - pub fn set_primary_message>(&mut self, msg: M) -> &mut Self { - self.message[0] = (DiagnosticMessage::Str(msg.into()), Style::NoStyle); + pub fn set_primary_message(&mut self, msg: impl Into) -> &mut Self { + self.message[0] = (msg.into(), Style::NoStyle); self } - pub fn message(&self) -> DiagnosticMessage { - DiagnosticMessage::Str(self.message.iter().map(|i| i.0.as_str()).collect::()) + pub fn args(&self) -> &[DiagnosticArg<'static>] { + &self.args } pub fn styled_message(&self) -> &Vec<(DiagnosticMessage, Style)> { @@ -750,13 +799,13 @@ impl Diagnostic { pub fn sub( &mut self, level: Level, - message: &str, + message: impl Into, span: MultiSpan, render_span: Option, ) { let sub = SubDiagnostic { level, - message: vec![(DiagnosticMessage::Str(message.to_owned()), Style::NoStyle)], + message: vec![(message.into(), Style::NoStyle)], span, render_span, }; @@ -765,14 +814,14 @@ impl Diagnostic { /// Convenience function for internal use, clients should use one of the /// public methods above. - fn sub_with_highlights( + fn sub_with_highlights>( &mut self, level: Level, - mut message: Vec<(String, Style)>, + mut message: Vec<(M, Style)>, span: MultiSpan, render_span: Option, ) { - let message = message.drain(..).map(|m| (DiagnosticMessage::Str(m.0), m.1)).collect(); + let message = message.drain(..).map(|m| (m.0.into(), m.1)).collect(); let sub = SubDiagnostic { level, message, span, render_span }; self.children.push(sub); } @@ -813,13 +862,3 @@ impl PartialEq for Diagnostic { self.keys() == other.keys() } } - -impl SubDiagnostic { - pub fn message(&self) -> DiagnosticMessage { - DiagnosticMessage::Str(self.message.iter().map(|i| i.0.as_str()).collect::()) - } - - pub fn styled_message(&self) -> &Vec<(DiagnosticMessage, Style)> { - &self.message - } -} diff --git a/compiler/rustc_errors/src/diagnostic_builder.rs b/compiler/rustc_errors/src/diagnostic_builder.rs index 4a7e252edb2..9669941fd08 100644 --- a/compiler/rustc_errors/src/diagnostic_builder.rs +++ b/compiler/rustc_errors/src/diagnostic_builder.rs @@ -1,4 +1,4 @@ -use crate::{Diagnostic, DiagnosticId, DiagnosticStyledString, ErrorGuaranteed}; +use crate::{Diagnostic, DiagnosticId, DiagnosticMessage, DiagnosticStyledString, ErrorGuaranteed}; use crate::{Handler, Level, MultiSpan, StashKey}; use rustc_lint_defs::Applicability; @@ -99,7 +99,10 @@ mod sealed_level_is_error { impl<'a> DiagnosticBuilder<'a, ErrorGuaranteed> { /// Convenience function for internal use, clients should use one of the /// `struct_*` methods on [`Handler`]. - crate fn new_guaranteeing_error(handler: &'a Handler, message: &str) -> Self + crate fn new_guaranteeing_error, const L: Level>( + handler: &'a Handler, + message: M, + ) -> Self where (): sealed_level_is_error::IsError, { @@ -163,7 +166,11 @@ impl EmissionGuarantee for ErrorGuaranteed { impl<'a> DiagnosticBuilder<'a, ()> { /// Convenience function for internal use, clients should use one of the /// `struct_*` methods on [`Handler`]. - crate fn new(handler: &'a Handler, level: Level, message: &str) -> Self { + crate fn new>( + handler: &'a Handler, + level: Level, + message: M, + ) -> Self { let diagnostic = Diagnostic::new_with_code(level, None, message); Self::new_diagnostic(handler, diagnostic) } @@ -201,7 +208,7 @@ impl EmissionGuarantee for () { impl<'a> DiagnosticBuilder<'a, !> { /// Convenience function for internal use, clients should use one of the /// `struct_*` methods on [`Handler`]. - crate fn new_fatal(handler: &'a Handler, message: &str) -> Self { + crate fn new_fatal(handler: &'a Handler, message: impl Into) -> Self { let diagnostic = Diagnostic::new_with_code(Level::Fatal, None, message); Self::new_diagnostic_fatal(handler, diagnostic) } @@ -346,7 +353,7 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> { } // Take the `Diagnostic` by replacing it with a dummy. - let dummy = Diagnostic::new(Level::Allow, ""); + let dummy = Diagnostic::new(Level::Allow, DiagnosticMessage::Str("".to_string())); let diagnostic = std::mem::replace(&mut *self.inner.diagnostic, dummy); // Disable the ICE on `Drop`. @@ -434,25 +441,25 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> { found: DiagnosticStyledString, ) -> &mut Self); - forward!(pub fn note(&mut self, msg: &str) -> &mut Self); - forward!(pub fn note_once(&mut self, msg: &str) -> &mut Self); + forward!(pub fn note(&mut self, msg: impl Into) -> &mut Self); + forward!(pub fn note_once(&mut self, msg: impl Into) -> &mut Self); forward!(pub fn span_note( &mut self, sp: impl Into, - msg: &str, + msg: impl Into, ) -> &mut Self); forward!(pub fn span_note_once( &mut self, sp: impl Into, - msg: &str, + msg: impl Into, ) -> &mut Self); - forward!(pub fn warn(&mut self, msg: &str) -> &mut Self); + forward!(pub fn warn(&mut self, msg: impl Into) -> &mut Self); forward!(pub fn span_warn(&mut self, sp: impl Into, msg: &str) -> &mut Self); - forward!(pub fn help(&mut self, msg: &str) -> &mut Self); + forward!(pub fn help(&mut self, msg: impl Into) -> &mut Self); forward!(pub fn span_help( &mut self, sp: impl Into, - msg: &str, + msg: impl Into, ) -> &mut Self); forward!(pub fn help_use_latest_edition(&mut self,) -> &mut Self); forward!(pub fn set_is_lint(&mut self,) -> &mut Self); @@ -461,67 +468,67 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> { forward!(pub fn multipart_suggestion( &mut self, - msg: &str, + msg: impl Into, suggestion: Vec<(Span, String)>, applicability: Applicability, ) -> &mut Self); forward!(pub fn multipart_suggestion_verbose( &mut self, - msg: &str, + msg: impl Into, suggestion: Vec<(Span, String)>, applicability: Applicability, ) -> &mut Self); forward!(pub fn tool_only_multipart_suggestion( &mut self, - msg: &str, + msg: impl Into, suggestion: Vec<(Span, String)>, applicability: Applicability, ) -> &mut Self); forward!(pub fn span_suggestion( &mut self, sp: Span, - msg: &str, + msg: impl Into, suggestion: String, applicability: Applicability, ) -> &mut Self); forward!(pub fn span_suggestions( &mut self, sp: Span, - msg: &str, + msg: impl Into, suggestions: impl Iterator, applicability: Applicability, ) -> &mut Self); forward!(pub fn multipart_suggestions( &mut self, - msg: &str, + msg: impl Into, suggestions: impl Iterator>, applicability: Applicability, ) -> &mut Self); forward!(pub fn span_suggestion_short( &mut self, sp: Span, - msg: &str, + msg: impl Into, suggestion: String, applicability: Applicability, ) -> &mut Self); forward!(pub fn span_suggestion_verbose( &mut self, sp: Span, - msg: &str, + msg: impl Into, suggestion: String, applicability: Applicability, ) -> &mut Self); forward!(pub fn span_suggestion_hidden( &mut self, sp: Span, - msg: &str, + msg: impl Into, suggestion: String, applicability: Applicability, ) -> &mut Self); forward!(pub fn tool_only_span_suggestion( &mut self, sp: Span, - msg: &str, + msg: impl Into, suggestion: String, applicability: Applicability, ) -> &mut Self); @@ -547,7 +554,9 @@ impl Drop for DiagnosticBuilderInner<'_> { if !panicking() { handler.emit_diagnostic(&mut Diagnostic::new( Level::Bug, - "the following error was constructed but not emitted", + DiagnosticMessage::Str( + "the following error was constructed but not emitted".to_string(), + ), )); handler.emit_diagnostic(&mut self.diagnostic); panic!(); diff --git a/compiler/rustc_errors/src/emitter.rs b/compiler/rustc_errors/src/emitter.rs index 881938ec85f..43bcaa646f3 100644 --- a/compiler/rustc_errors/src/emitter.rs +++ b/compiler/rustc_errors/src/emitter.rs @@ -15,14 +15,15 @@ use rustc_span::{SourceFile, Span}; use crate::snippet::{Annotation, AnnotationType, Line, MultilineAnnotation, Style, StyledString}; use crate::styled_buffer::StyledBuffer; use crate::{ - CodeSuggestion, Diagnostic, DiagnosticId, DiagnosticMessage, Handler, Level, MultiSpan, - SubDiagnostic, SubstitutionHighlight, SuggestionStyle, + CodeSuggestion, Diagnostic, DiagnosticArg, DiagnosticId, DiagnosticMessage, FluentBundle, + Handler, Level, MultiSpan, SubDiagnostic, SubstitutionHighlight, SuggestionStyle, }; use rustc_lint_defs::pluralize; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::sync::Lrc; +use rustc_error_messages::FluentArgs; use rustc_span::hygiene::{ExpnKind, MacroKind}; use std::borrow::Cow; use std::cmp::{max, min, Reverse}; @@ -58,13 +59,23 @@ impl HumanReadableErrorType { self, dst: Box, source_map: Option>, + fallback_bundle: Lrc, teach: bool, terminal_width: Option, macro_backtrace: bool, ) -> EmitterWriter { let (short, color_config) = self.unzip(); let color = color_config.suggests_using_colors(); - EmitterWriter::new(dst, source_map, short, teach, color, terminal_width, macro_backtrace) + EmitterWriter::new( + dst, + source_map, + fallback_bundle, + short, + teach, + color, + terminal_width, + macro_backtrace, + ) } } @@ -212,6 +223,68 @@ pub trait Emitter { fn source_map(&self) -> Option<&Lrc>; + /// Return `FluentBundle` with localized diagnostics for the locale requested by the user. If no + /// language was requested by the user then this will be `None` and `fallback_fluent_bundle` + /// should be used. + fn fluent_bundle(&self) -> Option<&Lrc>; + + /// Return `FluentBundle` with localized diagnostics for the default locale of the compiler. + /// Used when the user has not requested a specific language or when a localized diagnostic is + /// unavailable for the requested locale. + fn fallback_fluent_bundle(&self) -> &Lrc; + + /// Convert diagnostic arguments (a rustc internal type that exists to implement + /// `Encodable`/`Decodable`) into `FluentArgs` which is necessary to perform translation. + /// + /// Typically performed once for each diagnostic at the start of `emit_diagnostic` and then + /// passed around as a reference thereafter. + fn to_fluent_args<'arg>(&self, args: &[DiagnosticArg<'arg>]) -> FluentArgs<'arg> { + FromIterator::from_iter(args.to_vec().drain(..)) + } + + /// Convert `DiagnosticMessage`s to a string, performing translation if necessary. + fn translate_messages( + &self, + messages: &[(DiagnosticMessage, Style)], + args: &FluentArgs<'_>, + ) -> Cow<'_, str> { + Cow::Owned( + messages.iter().map(|(m, _)| self.translate_message(m, args)).collect::(), + ) + } + + /// Convert a `DiagnosticMessage` to a string, performing translation if necessary. + fn translate_message<'a>( + &'a self, + message: &'a DiagnosticMessage, + args: &'a FluentArgs<'_>, + ) -> Cow<'_, str> { + trace!(?message); + let (identifier, attr) = match message { + DiagnosticMessage::Str(msg) => return Cow::Borrowed(&msg), + DiagnosticMessage::FluentIdentifier(identifier, attr) => (identifier, attr), + }; + + let bundle = match self.fluent_bundle() { + Some(bundle) if bundle.has_message(&identifier) => bundle, + _ => self.fallback_fluent_bundle(), + }; + + let message = bundle.get_message(&identifier).expect("missing diagnostic in fluent bundle"); + let value = match attr { + Some(attr) => { + message.get_attribute(attr).expect("missing attribute in fluent message").value() + } + None => message.value().expect("missing value in fluent message"), + }; + + let mut err = vec![]; + let translated = bundle.format_pattern(value, Some(&args), &mut err); + trace!(?translated, ?err); + debug_assert!(err.is_empty()); + translated + } + /// Formats the substitutions of the primary_span /// /// There are a lot of conditions to this method, but in short: @@ -225,10 +298,12 @@ pub trait Emitter { fn primary_span_formatted<'a>( &mut self, diag: &'a Diagnostic, + fluent_args: &FluentArgs<'_>, ) -> (MultiSpan, &'a [CodeSuggestion]) { let mut primary_span = diag.span.clone(); let suggestions = diag.suggestions.as_ref().map_or(&[][..], |suggestions| &suggestions[..]); if let Some((sugg, rest)) = suggestions.split_first() { + let msg = self.translate_message(&sugg.msg, fluent_args); if rest.is_empty() && // ^ if there is only one suggestion // don't display multi-suggestions as labels @@ -236,7 +311,7 @@ pub trait Emitter { // don't display multipart suggestions as labels sugg.substitutions[0].parts.len() == 1 && // don't display long messages as labels - sugg.msg.as_str().split_whitespace().count() < 10 && + msg.split_whitespace().count() < 10 && // don't display multiline suggestions as labels !sugg.substitutions[0].parts[0].snippet.contains('\n') && ![ @@ -252,12 +327,12 @@ pub trait Emitter { let msg = if substitution.is_empty() || sugg.style.hide_inline() { // This substitution is only removal OR we explicitly don't want to show the // code inline (`hide_inline`). Therefore, we don't show the substitution. - format!("help: {}", sugg.msg.as_str()) + format!("help: {}", &msg) } else { // Show the default suggestion text with the substitution format!( "help: {}{}: `{}`", - sugg.msg.as_str(), + &msg, if self .source_map() .map(|sm| is_case_difference( @@ -492,9 +567,19 @@ impl Emitter for EmitterWriter { self.sm.as_ref() } + fn fluent_bundle(&self) -> Option<&Lrc> { + None + } + + fn fallback_fluent_bundle(&self) -> &Lrc { + &self.fallback_bundle + } + fn emit_diagnostic(&mut self, diag: &Diagnostic) { + let fluent_args = self.to_fluent_args(diag.args()); + let mut children = diag.children.clone(); - let (mut primary_span, suggestions) = self.primary_span_formatted(&diag); + let (mut primary_span, suggestions) = self.primary_span_formatted(&diag, &fluent_args); debug!("emit_diagnostic: suggestions={:?}", suggestions); self.fix_multispans_in_extern_macros_and_render_macro_backtrace( @@ -507,7 +592,8 @@ impl Emitter for EmitterWriter { self.emit_messages_default( &diag.level, - &diag.styled_message(), + &diag.message, + &fluent_args, &diag.code, &primary_span, &children, @@ -536,6 +622,15 @@ impl Emitter for SilentEmitter { fn source_map(&self) -> Option<&Lrc> { None } + + fn fluent_bundle(&self) -> Option<&Lrc> { + None + } + + fn fallback_fluent_bundle(&self) -> &Lrc { + panic!("silent emitter attempted to translate message") + } + fn emit_diagnostic(&mut self, d: &Diagnostic) { if d.level == Level::Fatal { let mut d = d.clone(); @@ -591,6 +686,7 @@ impl ColorConfig { pub struct EmitterWriter { dst: Destination, sm: Option>, + fallback_bundle: Lrc, short_message: bool, teach: bool, ui_testing: bool, @@ -610,6 +706,7 @@ impl EmitterWriter { pub fn stderr( color_config: ColorConfig, source_map: Option>, + fallback_bundle: Lrc, short_message: bool, teach: bool, terminal_width: Option, @@ -619,6 +716,7 @@ impl EmitterWriter { EmitterWriter { dst, sm: source_map, + fallback_bundle, short_message, teach, ui_testing: false, @@ -630,6 +728,7 @@ impl EmitterWriter { pub fn new( dst: Box, source_map: Option>, + fallback_bundle: Lrc, short_message: bool, teach: bool, colored: bool, @@ -639,6 +738,7 @@ impl EmitterWriter { EmitterWriter { dst: Raw(dst, colored), sm: source_map, + fallback_bundle, short_message, teach, ui_testing: false, @@ -1177,6 +1277,7 @@ impl EmitterWriter { &self, buffer: &mut StyledBuffer, msg: &[(DiagnosticMessage, Style)], + args: &FluentArgs<'_>, padding: usize, label: &str, override_style: Option