diff --git a/Cargo.lock b/Cargo.lock index 80f0a0b8b5ba3..d8cb1133c73a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1238,6 +1238,40 @@ dependencies = [ "miniz_oxide", ] +[[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 +1816,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 +2866,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" @@ -3645,6 +3705,21 @@ dependencies = [ name = "rustc_error_codes" version = "0.0.0" +[[package]] +name = "rustc_error_messages" +version = "0.0.0" +dependencies = [ + "fluent-bundle", + "fluent-syntax", + "intl-memoizer", + "rustc_data_structures", + "rustc_macros", + "rustc_serialize", + "rustc_span", + "tracing", + "unic-langid", +] + [[package]] name = "rustc_errors" version = "0.0.0" @@ -3652,6 +3727,7 @@ dependencies = [ "annotate-snippets", "atty", "rustc_data_structures", + "rustc_error_messages", "rustc_lint_defs", "rustc_macros", "rustc_serialize", @@ -3708,6 +3784,7 @@ dependencies = [ "odht", "rustc_ast", "rustc_data_structures", + "rustc_error_messages", "rustc_feature", "rustc_index", "rustc_macros", @@ -3864,6 +3941,7 @@ version = "0.0.0" dependencies = [ "rustc_ast", "rustc_data_structures", + "rustc_error_messages", "rustc_hir", "rustc_macros", "rustc_serialize", @@ -4573,6 +4651,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" @@ -5104,6 +5188,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" @@ -5262,6 +5352,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" @@ -5316,6 +5415,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/borrowck_errors.rs b/compiler/rustc_borrowck/src/borrowck_errors.rs index 21b582ef3345b..70f7f1e493e07 100644 --- a/compiler/rustc_borrowck/src/borrowck_errors.rs +++ b/compiler/rustc_borrowck/src/borrowck_errors.rs @@ -1,6 +1,6 @@ -use rustc_errors::{struct_span_err, DiagnosticBuilder, DiagnosticId, ErrorGuaranteed}; +use rustc_errors::{struct_span_err, DiagnosticBuilder, DiagnosticId, ErrorGuaranteed, MultiSpan}; use rustc_middle::ty::{self, Ty, TyCtxt}; -use rustc_span::{MultiSpan, Span}; +use rustc_span::Span; impl<'cx, 'tcx> crate::MirBorrowckCtxt<'cx, 'tcx> { crate fn cannot_move_when_borrowed( diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs index 883f711b201a9..aa2ddada3502a 100644 --- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs @@ -1,7 +1,7 @@ use either::Either; use rustc_const_eval::util::CallKind; use rustc_data_structures::fx::FxHashSet; -use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed}; +use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, MultiSpan}; use rustc_hir as hir; use rustc_hir::def_id::DefId; use rustc_hir::{AsyncGeneratorKind, GeneratorKind}; @@ -15,7 +15,7 @@ use rustc_middle::mir::{ use rustc_middle::ty::{self, subst::Subst, suggest_constraining_type_params, PredicateKind, Ty}; use rustc_mir_dataflow::move_paths::{InitKind, MoveOutIndex, MovePathIndex}; use rustc_span::symbol::sym; -use rustc_span::{BytePos, MultiSpan, Span}; +use rustc_span::{BytePos, Span}; use rustc_trait_selection::infer::InferCtxtExt; use rustc_trait_selection::traits::TraitEngineExt as _; diff --git a/compiler/rustc_borrowck/src/diagnostics/region_name.rs b/compiler/rustc_borrowck/src/diagnostics/region_name.rs index c9395492c9e06..723adb8da1b17 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_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs index 31213412d45f2..138e1fa017603 100644 --- a/compiler/rustc_builtin_macros/src/format.rs +++ b/compiler/rustc_builtin_macros/src/format.rs @@ -7,11 +7,11 @@ use rustc_ast::tokenstream::TokenStream; use rustc_ast::visit::{self, Visitor}; use rustc_ast::{token, BlockCheckMode, UnsafeSource}; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; -use rustc_errors::{pluralize, Applicability, PResult}; +use rustc_errors::{pluralize, Applicability, MultiSpan, PResult}; use rustc_expand::base::{self, *}; use rustc_parse_format as parse; use rustc_span::symbol::{sym, Ident, Symbol}; -use rustc_span::{InnerSpan, MultiSpan, Span}; +use rustc_span::{InnerSpan, Span}; use smallvec::SmallVec; use std::borrow::Cow; @@ -446,7 +446,9 @@ impl<'a, 'b> Context<'a, 'b> { .iter() .filter(|fmt| fmt.precision_span.is_some()) .count(); - e.span_label(span, &format!( + e.span_label( + span, + &format!( "this precision flag adds an extra required argument at position {}, \ which is why there {} expected", pos, @@ -455,7 +457,8 @@ impl<'a, 'b> Context<'a, 'b> { } else { format!("are {} arguments", count) }, - )); + ), + ); if let Some(arg) = self.args.get(pos) { e.span_label( arg.span, diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index 9417874ffb40b..92c4ab7eb8627 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(), + 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(), + 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 69f96d07f905d..febdd0ed74675 100644 --- a/compiler/rustc_driver/src/lib.rs +++ b/compiler/rustc_driver/src/lib.rs @@ -1172,9 +1172,13 @@ 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(false).expect("failed to load fallback fluent bundle"); let emitter = Box::new(rustc_errors::emitter::EmitterWriter::stderr( rustc_errors::ColorConfig::Auto, None, + None, + fallback_bundle, false, false, None, @@ -1209,7 +1213,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 new file mode 100644 index 0000000000000..fc84c7c8665b4 --- /dev/null +++ b/compiler/rustc_error_messages/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "rustc_error_messages" +version = "0.0.0" +edition = "2021" + +[lib] +doctest = false + +[dependencies] +fluent-bundle = "0.15.2" +fluent-syntax = "0.11" +intl-memoizer = "0.5.1" +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 0000000000000..336e7a6685708 --- /dev/null +++ b/compiler/rustc_error_messages/locales/en-US/diagnostics.ftl @@ -0,0 +1,87 @@ +parser-struct-literal-body-without-path = + struct literal body without path + .suggestion = you might have forgotten to add the struct literal inside the block + +typeck-field-multiply-specified-in-initializer = + field `{$ident}` specified more than once + .label = used more than once + .previous-use-label = first use of `{$ident}` + +typeck-unrecognized-atomic-operation = + unrecognized atomic operation function: `{$op}` + .label = unrecognized atomic operation + +typeck-wrong-number-of-generic-arguments-to-intrinsic = + intrinsic has wrong number of {$descr} parameters: found {$found}, expected {$expected} + .label = expected {$expected} {$descr} {$expected -> + [one] parameter + *[other] parameters + } + +typeck-unrecognized-intrinsic-function = + unrecognized intrinsic function: `{$name}` + .label = unrecognized intrinsic + +typeck-lifetimes-or-bounds-mismatch-on-trait = + lifetime parameters or bounds on {$item_kind} `{$ident}` do not match the trait declaration + .label = lifetimes do not match {$item_kind} in trait + .generics-label = lifetimes in impl do not match this {$item_kind} in trait + +typeck-drop-impl-on-wrong-item = + the `Drop` trait may only be implemented for structs, enums, and unions + .label = must be a struct, enum, or union + +typeck-field-already-declared = + field `{$field_name}` is already declared + .label = field already declared + .previous-decl-label = `{$field_name}` first declared here + +typeck-copy-impl-on-type-with-dtor = + the trait `Copy` may not be implemented for this type; the type has a destructor + .label = `Copy` not allowed on types with destructors + +typeck-multiple-relaxed-default-bounds = + type parameter has more than one relaxed default bound, only one is supported + +typeck-copy-impl-on-non-adt = + the trait `Copy` may not be implemented for this type + .label = type is not a structure or enumeration + +typeck-trait-object-declared-with-no-traits = + at least one trait is required for an object type + +typeck-ambiguous-lifetime-bound = + ambiguous lifetime bound, explicit lifetime bound required + +typeck-assoc-type-binding-not-allowed = + associated type bindings are not allowed here + .label = associated type not allowed here + +typeck-functional-record-update-on-non-struct = + functional record update syntax requires a struct + +typeck-typeof-reserved-keyword-used = + `typeof` is a reserved keyword but unimplemented + .label = reserved keyword + +typeck-return-stmt-outside-of-fn-body = + return statement outside of function body + .encl-body-label = the return is part of this body... + .encl-fn-label = ...not the enclosing function body + +typeck-yield-expr-outside-of-generator = + yield expression outside of generator literal + +typeck-struct-expr-non-exhaustive = + cannot create non-exhaustive {$what} using struct expression + +typeck-method-call-on-unknown-type = + the type of this value must be known to call a method on a raw pointer on it + +typeck-value-of-associated-struct-already-specified = + the value of the associated type `{$item_name}` (from trait `{$def_path}`) is already specified + .label = re-bound here + .previous-bound-label = `{$item_name}` bound here first + +typeck-address-of-temporary-taken = cannot take address of a temporary + .label = temporary value diff --git a/compiler/rustc_error_messages/src/lib.rs b/compiler/rustc_error_messages/src/lib.rs new file mode 100644 index 0000000000000..88f3b547605a2 --- /dev/null +++ b/compiler/rustc_error_messages/src/lib.rs @@ -0,0 +1,390 @@ +#![feature(let_chains)] +#![feature(path_try_exists)] + +use fluent_bundle::FluentResource; +use fluent_syntax::parser::ParserError; +use rustc_data_structures::sync::Lrc; +use rustc_macros::{Decodable, Encodable}; +use rustc_span::Span; +use std::borrow::Cow; +use std::error::Error; +use std::fmt; +use std::fs; +use std::io; +use std::path::Path; +use tracing::{instrument, trace}; + +#[cfg(parallel_compiler)] +use intl_memoizer::concurrent::IntlLangMemoizer; +#[cfg(not(parallel_compiler))] +use intl_memoizer::IntlLangMemoizer; + +pub use fluent_bundle::{FluentArgs, FluentError, FluentValue}; +pub use unic_langid::{langid, LanguageIdentifier}; + +static FALLBACK_FLUENT_RESOURCE: &'static str = include_str!("../locales/en-US/diagnostics.ftl"); + +pub type FluentBundle = fluent_bundle::bundle::FluentBundle; + +#[cfg(parallel_compiler)] +fn new_bundle(locales: Vec) -> FluentBundle { + FluentBundle::new_concurrent(locales) +} + +#[cfg(not(parallel_compiler))] +fn new_bundle(locales: Vec) -> FluentBundle { + FluentBundle::new(locales) +} + +#[derive(Debug)] +pub enum TranslationBundleError { + /// Failed to read from `.ftl` file. + ReadFtl(io::Error), + /// Failed to parse contents of `.ftl` file. + ParseFtl(ParserError), + /// Failed to add `FluentResource` to `FluentBundle`. + AddResource(FluentError), + /// `$sysroot/share/locale/$locale` does not exist. + MissingLocale(io::Error), + /// Cannot read directory entries of `$sysroot/share/locale/$locale`. + ReadLocalesDir(io::Error), + /// Cannot read directory entry of `$sysroot/share/locale/$locale`. + ReadLocalesDirEntry(io::Error), + /// `$sysroot/share/locale/$locale` is not a directory. + LocaleIsNotDir, +} + +impl fmt::Display for TranslationBundleError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TranslationBundleError::ReadFtl(e) => write!(f, "could not read ftl file: {}", e), + TranslationBundleError::ParseFtl(e) => { + write!(f, "could not parse ftl file: {}", e) + } + TranslationBundleError::AddResource(e) => write!(f, "failed to add resource: {}", e), + TranslationBundleError::MissingLocale(e) => { + write!(f, "missing locale directory: {}", e) + } + TranslationBundleError::ReadLocalesDir(e) => { + write!(f, "could not read locales dir: {}", e) + } + TranslationBundleError::ReadLocalesDirEntry(e) => { + write!(f, "could not read locales dir entry: {}", e) + } + TranslationBundleError::LocaleIsNotDir => { + write!(f, "`$sysroot/share/locales/$locale` is not a directory") + } + } + } +} + +impl Error for TranslationBundleError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + TranslationBundleError::ReadFtl(e) => Some(e), + TranslationBundleError::ParseFtl(e) => Some(e), + TranslationBundleError::AddResource(e) => Some(e), + TranslationBundleError::MissingLocale(e) => Some(e), + TranslationBundleError::ReadLocalesDir(e) => Some(e), + TranslationBundleError::ReadLocalesDirEntry(e) => Some(e), + TranslationBundleError::LocaleIsNotDir => None, + } + } +} + +impl From<(FluentResource, Vec)> for TranslationBundleError { + fn from((_, mut errs): (FluentResource, Vec)) -> Self { + TranslationBundleError::ParseFtl(errs.pop().expect("failed ftl parse with no errors")) + } +} + +impl From> for TranslationBundleError { + fn from(mut errs: Vec) -> Self { + TranslationBundleError::AddResource( + errs.pop().expect("failed adding resource to bundle with no errors"), + ) + } +} + +/// Returns Fluent bundle with the user's locale resources from +/// `$sysroot/share/locale/$requested_locale/*.ftl`. +/// +/// If `-Z additional-ftl-path` was provided, load that resource and add it to the bundle +/// (overriding any conflicting messages). +#[instrument(level = "trace")] +pub fn fluent_bundle( + sysroot: &Path, + requested_locale: Option, + additional_ftl_path: Option<&Path>, + with_directionality_markers: bool, +) -> Result>, TranslationBundleError> { + if requested_locale.is_none() && additional_ftl_path.is_none() { + return Ok(None); + } + + let fallback_locale = langid!("en-US"); + let requested_fallback_locale = requested_locale.as_ref() == Some(&fallback_locale); + + // If there is only `-Z additional-ftl-path`, assume locale is "en-US", otherwise use user + // provided locale. + let locale = requested_locale.clone().unwrap_or(fallback_locale); + trace!(?locale); + let mut bundle = new_bundle(vec![locale]); + + // Fluent diagnostics can insert directionality isolation markers around interpolated variables + // indicating that there may be a shift from right-to-left to left-to-right text (or + // vice-versa). These are disabled because they are sometimes visible in the error output, but + // may be worth investigating in future (for example: if type names are left-to-right and the + // surrounding diagnostic messages are right-to-left, then these might be helpful). + bundle.set_use_isolating(with_directionality_markers); + + // If the user requests the default locale then don't try to load anything. + if !requested_fallback_locale && let Some(requested_locale) = requested_locale { + let mut sysroot = sysroot.to_path_buf(); + sysroot.push("share"); + sysroot.push("locale"); + sysroot.push(requested_locale.to_string()); + trace!(?sysroot); + + let _ = sysroot.try_exists().map_err(TranslationBundleError::MissingLocale)?; + + if !sysroot.is_dir() { + return Err(TranslationBundleError::LocaleIsNotDir); + } + + for entry in sysroot.read_dir().map_err(TranslationBundleError::ReadLocalesDir)? { + let entry = entry.map_err(TranslationBundleError::ReadLocalesDirEntry)?; + let path = entry.path(); + trace!(?path); + if path.extension().and_then(|s| s.to_str()) != Some("ftl") { + trace!("skipping"); + continue; + } + + let resource_str = + fs::read_to_string(path).map_err(TranslationBundleError::ReadFtl)?; + let resource = + FluentResource::try_new(resource_str).map_err(TranslationBundleError::from)?; + trace!(?resource); + bundle.add_resource(resource).map_err(TranslationBundleError::from)?; + } + } + + if let Some(additional_ftl_path) = additional_ftl_path { + let resource_str = + fs::read_to_string(additional_ftl_path).map_err(TranslationBundleError::ReadFtl)?; + let resource = + FluentResource::try_new(resource_str).map_err(TranslationBundleError::from)?; + trace!(?resource); + bundle.add_resource_overriding(resource); + } + + let bundle = Lrc::new(bundle); + Ok(Some(bundle)) +} + +/// Return the default `FluentBundle` with standard "en-US" diagnostic messages. +#[instrument(level = "trace")] +pub fn fallback_fluent_bundle( + with_directionality_markers: bool, +) -> Result, TranslationBundleError> { + let fallback_resource = FluentResource::try_new(FALLBACK_FLUENT_RESOURCE.to_string()) + .map_err(TranslationBundleError::from)?; + trace!(?fallback_resource); + let mut fallback_bundle = new_bundle(vec![langid!("en-US")]); + // See comment in `fluent_bundle`. + fallback_bundle.set_use_isolating(with_directionality_markers); + fallback_bundle.add_resource(fallback_resource).map_err(TranslationBundleError::from)?; + let fallback_bundle = Lrc::new(fallback_bundle); + Ok(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 (with optional attribute) corresponding to the diagnostic + /// message. + /// + /// + /// + FluentIdentifier(FluentId, Option), +} + +impl DiagnosticMessage { + /// 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(s) => s, + _ => panic!("expected non-translatable diagnostic message"), + } + } + + /// 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()) + } +} + +/// 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, +} + +/// 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)] +pub struct MultiSpan { + primary_spans: Vec, + span_labels: Vec<(Span, DiagnosticMessage)>, +} + +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) -> MultiSpan { + vec.sort(); + MultiSpan { primary_spans: vec, span_labels: vec![] } + } + + 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). + pub fn primary_span(&self) -> Option { + 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.primary_spans.iter().any(|sp| !sp.is_dummy()) + } + + /// Returns `true` if this contains only a dummy primary span with any hygienic context. + pub fn is_dummy(&self) -> bool { + let mut is_dummy = true; + for span in &self.primary_spans { + if !span.is_dummy() { + is_dummy = false; + } + } + 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 { + 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::>(); + + 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 `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()) + } +} + +impl From for MultiSpan { + fn from(span: Span) -> MultiSpan { + MultiSpan::from_span(span) + } +} + +impl From> for MultiSpan { + fn from(spans: Vec) -> MultiSpan { + MultiSpan::from_spans(spans) + } +} diff --git a/compiler/rustc_errors/Cargo.toml b/compiler/rustc_errors/Cargo.toml index 4846dc43605f8..5f919982890e0 100644 --- a/compiler/rustc_errors/Cargo.toml +++ b/compiler/rustc_errors/Cargo.toml @@ -8,6 +8,7 @@ doctest = false [dependencies] tracing = "0.1" +rustc_error_messages = { path = "../rustc_error_messages" } rustc_serialize = { path = "../rustc_serialize" } rustc_span = { path = "../rustc_span" } rustc_macros = { path = "../rustc_macros" } diff --git a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs index 5f59eba23f8e9..003fd1eea3ace 100644 --- a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs +++ b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs @@ -7,16 +7,23 @@ use crate::emitter::FileWithAnnotatedLines; use crate::snippet::Line; -use crate::{CodeSuggestion, Diagnostic, DiagnosticId, Emitter, Level, 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::{MultiSpan, SourceFile}; +use rustc_span::SourceFile; /// Generates diagnostics using annotate-snippet pub struct AnnotateSnippetEmitterWriter { source_map: Option>, + fluent_bundle: 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 +35,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 +50,8 @@ impl Emitter for AnnotateSnippetEmitterWriter { self.emit_messages_default( &diag.level, - diag.message(), + &diag.message, + &fluent_args, &diag.code, &primary_span, &children, @@ -53,6 +63,14 @@ impl Emitter for AnnotateSnippetEmitterWriter { self.source_map.as_ref() } + fn fluent_bundle(&self) -> Option<&Lrc> { + self.fluent_bundle.as_ref() + } + + fn fallback_fluent_bundle(&self) -> &Lrc { + &self.fallback_bundle + } + fn should_show_explain(&self) -> bool { !self.short_message } @@ -82,10 +100,19 @@ fn annotation_type_for_level(level: Level) -> AnnotationType { impl AnnotateSnippetEmitterWriter { pub fn new( source_map: Option>, + fluent_bundle: Option>, + fallback_bundle: Lrc, short_message: bool, macro_backtrace: bool, ) -> Self { - Self { source_map, short_message, ui_testing: false, macro_backtrace } + Self { + source_map, + fluent_bundle, + fallback_bundle, + short_message, + ui_testing: false, + macro_backtrace, + } } /// Allows to modify `Self` to enable or disable the `ui_testing` flag. @@ -99,12 +126,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 +149,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 32c52a6a8a6d9..ecb3cdd627cec 100644 --- a/compiler/rustc_errors/src/diagnostic.rs +++ b/compiler/rustc_errors/src/diagnostic.rs @@ -1,15 +1,16 @@ use crate::snippet::Style; -use crate::CodeSuggestion; -use crate::Level; -use crate::Substitution; -use crate::SubstitutionPart; -use crate::SuggestionStyle; -use crate::ToolMetadata; +use crate::{ + CodeSuggestion, DiagnosticMessage, Level, MultiSpan, Substitution, SubstitutionPart, + 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::{MultiSpan, Span, DUMMY_SP}; +use rustc_span::symbol::{Ident, Symbol}; +use rustc_span::{Span, DUMMY_SP}; +use std::borrow::Cow; use std::fmt; use std::hash::{Hash, Hasher}; @@ -18,6 +19,66 @@ 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), +} + +/// Converts a value of a type into a `DiagnosticArg` (typically a field of a `SessionDiagnostic` +/// struct). Implemented as a custom trait rather than `From` so that it is implemented on the type +/// being converted rather than on `DiagnosticArgValue`, which enables types from other `rustc_*` +/// crates to implement this. +pub trait IntoDiagnosticArg { + fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static>; +} + +impl IntoDiagnosticArg for String { + fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { + DiagnosticArgValue::Str(Cow::Owned(self)) + } +} + +impl IntoDiagnosticArg for Symbol { + fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { + self.to_ident_string().into_diagnostic_arg() + } +} + +impl IntoDiagnosticArg for Ident { + fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { + self.to_string().into_diagnostic_arg() + } +} + +impl<'a> IntoDiagnosticArg for &'a str { + fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { + self.to_string().into_diagnostic_arg() + } +} + +impl IntoDiagnosticArg for usize { + fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { + DiagnosticArgValue::Number(self) + } +} + +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 { @@ -25,11 +86,12 @@ pub struct Diagnostic { // outside of what methods in this crate themselves allow. crate level: Level, - pub message: Vec<(String, Style)>, + pub message: Vec<(DiagnosticMessage, Style)>, pub code: Option, 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 @@ -52,7 +114,7 @@ pub enum DiagnosticId { #[derive(Clone, Debug, PartialEq, Hash, Encodable, Decodable)] pub struct SubDiagnostic { pub level: Level, - pub message: Vec<(String, Style)>, + pub message: Vec<(DiagnosticMessage, Style)>, pub span: MultiSpan, pub render_span: Option, } @@ -105,18 +167,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![(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, } @@ -210,7 +277,7 @@ impl Diagnostic { /// /// This span is *not* considered a ["primary span"][`MultiSpan`]; only /// the `Span` supplied when creating the diagnostic is primary. - pub fn span_label>(&mut self, span: Span, label: T) -> &mut Self { + pub fn span_label(&mut self, span: Span, label: impl Into) -> &mut Self { self.span.push_span_label(span, label.into()); self } @@ -234,7 +301,7 @@ impl Diagnostic { self.set_span(after); for span_label in before.span_labels() { if let Some(label) = span_label.label { - self.span_label(after, label); + self.span.push_span_label(after, label); } } self @@ -328,52 +395,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 } @@ -386,7 +468,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 } @@ -422,7 +508,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 { @@ -438,7 +524,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 { @@ -452,7 +538,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, @@ -465,7 +551,7 @@ impl Diagnostic { .map(|(span, snippet)| SubstitutionPart { snippet, span }) .collect(), }], - msg: msg.to_owned(), + msg: msg.into(), style, applicability, tool_metadata: Default::default(), @@ -481,7 +567,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 { @@ -493,7 +579,7 @@ impl Diagnostic { .map(|(span, snippet)| SubstitutionPart { snippet, span }) .collect(), }], - msg: msg.to_owned(), + msg: msg.into(), style: SuggestionStyle::CompletelyHidden, applicability, tool_metadata: Default::default(), @@ -521,7 +607,7 @@ impl Diagnostic { pub fn span_suggestion( &mut self, sp: Span, - msg: &str, + msg: impl Into, suggestion: String, applicability: Applicability, ) -> &mut Self { @@ -539,7 +625,7 @@ impl Diagnostic { pub fn span_suggestion_with_style( &mut self, sp: Span, - msg: &str, + msg: impl Into, suggestion: String, applicability: Applicability, style: SuggestionStyle, @@ -548,7 +634,7 @@ impl Diagnostic { substitutions: vec![Substitution { parts: vec![SubstitutionPart { snippet: suggestion, span: sp }], }], - msg: msg.to_owned(), + msg: msg.into(), style, applicability, tool_metadata: Default::default(), @@ -560,7 +646,7 @@ impl Diagnostic { pub fn span_suggestion_verbose( &mut self, sp: Span, - msg: &str, + msg: impl Into, suggestion: String, applicability: Applicability, ) -> &mut Self { @@ -579,7 +665,7 @@ impl Diagnostic { pub fn span_suggestions( &mut self, sp: Span, - msg: &str, + msg: impl Into, suggestions: impl Iterator, applicability: Applicability, ) -> &mut Self { @@ -591,7 +677,7 @@ impl Diagnostic { .collect(); self.push_suggestion(CodeSuggestion { substitutions, - msg: msg.to_owned(), + msg: msg.into(), style: SuggestionStyle::ShowCode, applicability, tool_metadata: Default::default(), @@ -603,7 +689,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 { @@ -616,7 +702,7 @@ impl Diagnostic { .collect(), }) .collect(), - msg: msg.to_owned(), + msg: msg.into(), style: SuggestionStyle::ShowCode, applicability, tool_metadata: Default::default(), @@ -630,7 +716,7 @@ impl Diagnostic { pub fn span_suggestion_short( &mut self, sp: Span, - msg: &str, + msg: impl Into, suggestion: String, applicability: Applicability, ) -> &mut Self { @@ -653,7 +739,7 @@ impl Diagnostic { pub fn span_suggestion_hidden( &mut self, sp: Span, - msg: &str, + msg: impl Into, suggestion: String, applicability: Applicability, ) -> &mut Self { @@ -674,7 +760,7 @@ impl Diagnostic { pub fn tool_only_span_suggestion( &mut self, sp: Span, - msg: &str, + msg: impl Into, suggestion: String, applicability: Applicability, ) -> &mut Self { @@ -692,13 +778,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: msg.to_owned(), + msg: msg.into(), style: SuggestionStyle::CompletelyHidden, applicability, tool_metadata: ToolMetadata::new(tool_metadata), @@ -732,16 +818,25 @@ impl Diagnostic { self.code.clone() } - pub fn set_primary_message>(&mut self, msg: M) -> &mut Self { + pub fn set_primary_message(&mut self, msg: impl Into) -> &mut Self { self.message[0] = (msg.into(), Style::NoStyle); self } - pub fn message(&self) -> String { - self.message.iter().map(|i| i.0.as_str()).collect::() + pub fn args(&self) -> &[DiagnosticArg<'static>] { + &self.args + } + + pub fn set_arg( + &mut self, + name: impl Into>, + arg: DiagnosticArgValue<'static>, + ) -> &mut Self { + self.args.push((name.into(), arg.into())); + self } - pub fn styled_message(&self) -> &Vec<(String, Style)> { + pub fn styled_message(&self) -> &Vec<(DiagnosticMessage, Style)> { &self.message } @@ -752,13 +847,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![(message.to_owned(), Style::NoStyle)], + message: vec![(message.into(), Style::NoStyle)], span, render_span, }; @@ -767,13 +862,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, - message: Vec<(String, Style)>, + mut message: Vec<(M, Style)>, span: MultiSpan, render_span: Option, ) { + let message = message.drain(..).map(|m| (m.0.into(), m.1)).collect(); let sub = SubDiagnostic { level, message, span, render_span }; self.children.push(sub); } @@ -783,7 +879,7 @@ impl Diagnostic { &self, ) -> ( &Level, - &Vec<(String, Style)>, + &Vec<(DiagnosticMessage, Style)>, &Option, &MultiSpan, &Result, SuggestionsDisabled>, @@ -814,13 +910,3 @@ impl PartialEq for Diagnostic { self.keys() == other.keys() } } - -impl SubDiagnostic { - pub fn message(&self) -> String { - self.message.iter().map(|i| i.0.as_str()).collect::() - } - - pub fn styled_message(&self) -> &Vec<(String, Style)> { - &self.message - } -} diff --git a/compiler/rustc_errors/src/diagnostic_builder.rs b/compiler/rustc_errors/src/diagnostic_builder.rs index 853243ef3f06d..74e0f74294613 100644 --- a/compiler/rustc_errors/src/diagnostic_builder.rs +++ b/compiler/rustc_errors/src/diagnostic_builder.rs @@ -1,8 +1,10 @@ -use crate::{Diagnostic, DiagnosticId, DiagnosticStyledString, ErrorGuaranteed}; -use crate::{Handler, Level, StashKey}; +use crate::diagnostic::DiagnosticArgValue; +use crate::{Diagnostic, DiagnosticId, DiagnosticMessage, DiagnosticStyledString, ErrorGuaranteed}; +use crate::{Handler, Level, MultiSpan, StashKey}; use rustc_lint_defs::Applicability; -use rustc_span::{MultiSpan, Span}; +use rustc_span::Span; +use std::borrow::Cow; use std::fmt::{self, Debug}; use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; @@ -99,7 +101,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 +168,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 +210,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 +355,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`. @@ -399,7 +408,7 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> { /// the diagnostic was constructed. However, the label span is *not* considered a /// ["primary span"][`MultiSpan`]; only the `Span` supplied when creating the diagnostic is /// primary. - pub fn span_label(&mut self, span: Span, label: impl Into) -> &mut Self); + pub fn span_label(&mut self, span: Span, label: impl Into) -> &mut Self); forward!( /// Labels all the given spans with the provided label. @@ -434,25 +443,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 +470,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); @@ -529,6 +538,11 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> { forward!(pub fn set_primary_message(&mut self, msg: impl Into) -> &mut Self); forward!(pub fn set_span(&mut self, sp: impl Into) -> &mut Self); forward!(pub fn code(&mut self, s: DiagnosticId) -> &mut Self); + forward!(pub fn set_arg( + &mut self, + name: impl Into>, + arg: DiagnosticArgValue<'static>, + ) -> &mut Self); } impl Debug for DiagnosticBuilder<'_, G> { @@ -547,7 +561,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 93b7201023a49..6a763d4d1404b 100644 --- a/compiler/rustc_errors/src/emitter.rs +++ b/compiler/rustc_errors/src/emitter.rs @@ -10,19 +10,20 @@ use Destination::*; use rustc_span::source_map::SourceMap; -use rustc_span::{MultiSpan, SourceFile, Span}; +use rustc_span::{SourceFile, Span}; use crate::snippet::{Annotation, AnnotationType, Line, MultilineAnnotation, Style, StyledString}; use crate::styled_buffer::StyledBuffer; use crate::{ - CodeSuggestion, Diagnostic, DiagnosticId, Handler, Level, 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,25 @@ impl HumanReadableErrorType { self, dst: Box, source_map: Option>, + bundle: 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, + bundle, + fallback_bundle, + short, + teach, + color, + terminal_width, + macro_backtrace, + ) } } @@ -212,6 +225,74 @@ 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, ?args); + 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(), + "identifier: {:?}, args: {:?}, errors: {:?}", + identifier, + args, + err + ); + translated + } + /// Formats the substitutions of the primary_span /// /// There are a lot of conditions to this method, but in short: @@ -225,10 +306,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 +319,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.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 +335,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) + format!("help: {}", &msg) } else { // Show the default suggestion text with the substitution format!( "help: {}{}: `{}`", - sugg.msg, + &msg, if self .source_map() .map(|sm| is_case_difference( @@ -333,7 +416,7 @@ pub trait Emitter { children.push(SubDiagnostic { level: Level::Note, - message: vec![(msg, Style::NoStyle)], + message: vec![(DiagnosticMessage::Str(msg), Style::NoStyle)], span: MultiSpan::new(), render_span: None, }); @@ -492,9 +575,19 @@ impl Emitter for EmitterWriter { self.sm.as_ref() } + fn fluent_bundle(&self) -> Option<&Lrc> { + self.fluent_bundle.as_ref() + } + + 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 +600,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 +630,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 +694,8 @@ impl ColorConfig { pub struct EmitterWriter { dst: Destination, sm: Option>, + fluent_bundle: Option>, + fallback_bundle: Lrc, short_message: bool, teach: bool, ui_testing: bool, @@ -610,6 +715,8 @@ impl EmitterWriter { pub fn stderr( color_config: ColorConfig, source_map: Option>, + fluent_bundle: Option>, + fallback_bundle: Lrc, short_message: bool, teach: bool, terminal_width: Option, @@ -619,6 +726,8 @@ impl EmitterWriter { EmitterWriter { dst, sm: source_map, + fluent_bundle, + fallback_bundle, short_message, teach, ui_testing: false, @@ -630,6 +739,8 @@ impl EmitterWriter { pub fn new( dst: Box, source_map: Option>, + fluent_bundle: Option>, + fallback_bundle: Lrc, short_message: bool, teach: bool, colored: bool, @@ -639,6 +750,8 @@ impl EmitterWriter { EmitterWriter { dst: Raw(dst, colored), sm: source_map, + fluent_bundle, + fallback_bundle, short_message, teach, ui_testing: false, @@ -1176,7 +1289,8 @@ impl EmitterWriter { fn msg_to_buffer( &self, buffer: &mut StyledBuffer, - msg: &[(String, Style)], + msg: &[(DiagnosticMessage, Style)], + args: &FluentArgs<'_>, padding: usize, label: &str, override_style: Option