errors: implement sysroot/testing bundle loading

Extend loading of Fluent bundles so that bundles can be loaded from the
sysroot based on the language requested by the user, or using a nightly
flag.

Sysroot bundles are loaded from `$sysroot/share/locale/$locale/*.ftl`.

Signed-off-by: David Wood <david.wood@huawei.com>
This commit is contained in:
David Wood 2022-03-28 09:36:20 +01:00
parent 7f91697b50
commit d5119c5b9f
23 changed files with 322 additions and 46 deletions

View file

@ -1238,16 +1238,6 @@ dependencies = [
"miniz_oxide", "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]] [[package]]
name = "fluent-bundle" name = "fluent-bundle"
version = "0.15.2" version = "0.15.2"
@ -3719,7 +3709,8 @@ version = "0.0.0"
name = "rustc_error_messages" name = "rustc_error_messages"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"fluent", "fluent-bundle",
"fluent-syntax",
"rustc_data_structures", "rustc_data_structures",
"rustc_macros", "rustc_macros",
"rustc_serialize", "rustc_serialize",

View file

@ -1172,10 +1172,12 @@ static DEFAULT_HOOK: SyncLazy<Box<dyn Fn(&panic::PanicInfo<'_>) + Sync + Send +
/// When `install_ice_hook` is called, this function will be called as the panic /// When `install_ice_hook` is called, this function will be called as the panic
/// hook. /// hook.
pub fn report_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) { pub fn report_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) {
let fallback_bundle = rustc_errors::fallback_fluent_bundle(); let fallback_bundle =
rustc_errors::fallback_fluent_bundle().expect("failed to load fallback fluent bundle");
let emitter = Box::new(rustc_errors::emitter::EmitterWriter::stderr( let emitter = Box::new(rustc_errors::emitter::EmitterWriter::stderr(
rustc_errors::ColorConfig::Auto, rustc_errors::ColorConfig::Auto,
None, None,
None,
fallback_bundle, fallback_bundle,
false, false,
false, false,

View file

@ -7,7 +7,8 @@ edition = "2021"
doctest = false doctest = false
[dependencies] [dependencies]
fluent = "0.16.0" fluent-bundle = "0.15.2"
fluent-syntax = "0.11"
rustc_data_structures = { path = "../rustc_data_structures" } rustc_data_structures = { path = "../rustc_data_structures" }
rustc_serialize = { path = "../rustc_serialize" } rustc_serialize = { path = "../rustc_serialize" }
rustc_span = { path = "../rustc_span" } rustc_span = { path = "../rustc_span" }

View file

@ -1,24 +1,169 @@
#![feature(path_try_exists)]
use fluent_bundle::FluentResource;
use fluent_syntax::parser::ParserError;
use rustc_data_structures::sync::Lrc; use rustc_data_structures::sync::Lrc;
use rustc_macros::{Decodable, Encodable}; use rustc_macros::{Decodable, Encodable};
use rustc_span::Span; use rustc_span::Span;
use std::borrow::Cow; use std::borrow::Cow;
use tracing::debug; use std::error::Error;
use std::fmt;
use std::fs;
use std::io;
use std::path::Path;
use tracing::{instrument, trace};
pub use fluent::{FluentArgs, FluentValue}; 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"); static FALLBACK_FLUENT_RESOURCE: &'static str = include_str!("../locales/en-US/diagnostics.ftl");
pub type FluentBundle = fluent::FluentBundle<fluent::FluentResource>; pub type FluentBundle = fluent_bundle::FluentBundle<FluentResource>;
/// Return the default `FluentBundle` with standard en-US diagnostic messages. #[derive(Debug)]
pub fn fallback_fluent_bundle() -> Lrc<FluentBundle> { pub enum TranslationBundleError {
let fallback_resource = fluent::FluentResource::try_new(FALLBACK_FLUENT_RESOURCE.to_string()) /// Failed to read from `.ftl` file.
.expect("failed to parse ftl resource"); ReadFtl(io::Error),
debug!(?fallback_resource); /// Failed to parse contents of `.ftl` file.
let mut fallback_bundle = FluentBundle::new(vec![unic_langid::langid!("en-US")]); ParseFtl(ParserError),
fallback_bundle.add_resource(fallback_resource).expect("failed to add resource to bundle"); /// 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<ParserError>)> for TranslationBundleError {
fn from((_, mut errs): (FluentResource, Vec<ParserError>)) -> Self {
TranslationBundleError::ParseFtl(errs.pop().expect("failed ftl parse with no errors"))
}
}
impl From<Vec<FluentError>> for TranslationBundleError {
fn from(mut errs: Vec<FluentError>) -> 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<LanguageIdentifier>,
additional_ftl_path: Option<&Path>,
) -> Result<Option<Lrc<FluentBundle>>, TranslationBundleError> {
if requested_locale.is_none() && additional_ftl_path.is_none() {
return Ok(None);
}
// 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_else(|| langid!("en-US"));
trace!(?locale);
let mut bundle = FluentBundle::new(vec![locale]);
if 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() -> Result<Lrc<FluentBundle>, TranslationBundleError> {
let fallback_resource = FluentResource::try_new(FALLBACK_FLUENT_RESOURCE.to_string())
.map_err(TranslationBundleError::from)?;
trace!(?fallback_resource);
let mut fallback_bundle = FluentBundle::new(vec![langid!("en-US")]);
fallback_bundle.add_resource(fallback_resource).map_err(TranslationBundleError::from)?;
let fallback_bundle = Lrc::new(fallback_bundle); let fallback_bundle = Lrc::new(fallback_bundle);
fallback_bundle Ok(fallback_bundle)
} }
/// Identifier for the Fluent message/attribute corresponding to a diagnostic message. /// Identifier for the Fluent message/attribute corresponding to a diagnostic message.

View file

@ -21,6 +21,7 @@ use rustc_span::SourceFile;
/// Generates diagnostics using annotate-snippet /// Generates diagnostics using annotate-snippet
pub struct AnnotateSnippetEmitterWriter { pub struct AnnotateSnippetEmitterWriter {
source_map: Option<Lrc<SourceMap>>, source_map: Option<Lrc<SourceMap>>,
fluent_bundle: Option<Lrc<FluentBundle>>,
fallback_bundle: Lrc<FluentBundle>, fallback_bundle: Lrc<FluentBundle>,
/// If true, hides the longer explanation text /// If true, hides the longer explanation text
@ -63,7 +64,7 @@ impl Emitter for AnnotateSnippetEmitterWriter {
} }
fn fluent_bundle(&self) -> Option<&Lrc<FluentBundle>> { fn fluent_bundle(&self) -> Option<&Lrc<FluentBundle>> {
None self.fluent_bundle.as_ref()
} }
fn fallback_fluent_bundle(&self) -> &Lrc<FluentBundle> { fn fallback_fluent_bundle(&self) -> &Lrc<FluentBundle> {
@ -99,11 +100,19 @@ fn annotation_type_for_level(level: Level) -> AnnotationType {
impl AnnotateSnippetEmitterWriter { impl AnnotateSnippetEmitterWriter {
pub fn new( pub fn new(
source_map: Option<Lrc<SourceMap>>, source_map: Option<Lrc<SourceMap>>,
fluent_bundle: Option<Lrc<FluentBundle>>,
fallback_bundle: Lrc<FluentBundle>, fallback_bundle: Lrc<FluentBundle>,
short_message: bool, short_message: bool,
macro_backtrace: bool, macro_backtrace: bool,
) -> Self { ) -> Self {
Self { source_map, fallback_bundle, 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. /// Allows to modify `Self` to enable or disable the `ui_testing` flag.

View file

@ -59,6 +59,7 @@ impl HumanReadableErrorType {
self, self,
dst: Box<dyn Write + Send>, dst: Box<dyn Write + Send>,
source_map: Option<Lrc<SourceMap>>, source_map: Option<Lrc<SourceMap>>,
bundle: Option<Lrc<FluentBundle>>,
fallback_bundle: Lrc<FluentBundle>, fallback_bundle: Lrc<FluentBundle>,
teach: bool, teach: bool,
terminal_width: Option<usize>, terminal_width: Option<usize>,
@ -69,6 +70,7 @@ impl HumanReadableErrorType {
EmitterWriter::new( EmitterWriter::new(
dst, dst,
source_map, source_map,
bundle,
fallback_bundle, fallback_bundle,
short, short,
teach, teach,
@ -568,7 +570,7 @@ impl Emitter for EmitterWriter {
} }
fn fluent_bundle(&self) -> Option<&Lrc<FluentBundle>> { fn fluent_bundle(&self) -> Option<&Lrc<FluentBundle>> {
None self.fluent_bundle.as_ref()
} }
fn fallback_fluent_bundle(&self) -> &Lrc<FluentBundle> { fn fallback_fluent_bundle(&self) -> &Lrc<FluentBundle> {
@ -686,6 +688,7 @@ impl ColorConfig {
pub struct EmitterWriter { pub struct EmitterWriter {
dst: Destination, dst: Destination,
sm: Option<Lrc<SourceMap>>, sm: Option<Lrc<SourceMap>>,
fluent_bundle: Option<Lrc<FluentBundle>>,
fallback_bundle: Lrc<FluentBundle>, fallback_bundle: Lrc<FluentBundle>,
short_message: bool, short_message: bool,
teach: bool, teach: bool,
@ -706,6 +709,7 @@ impl EmitterWriter {
pub fn stderr( pub fn stderr(
color_config: ColorConfig, color_config: ColorConfig,
source_map: Option<Lrc<SourceMap>>, source_map: Option<Lrc<SourceMap>>,
fluent_bundle: Option<Lrc<FluentBundle>>,
fallback_bundle: Lrc<FluentBundle>, fallback_bundle: Lrc<FluentBundle>,
short_message: bool, short_message: bool,
teach: bool, teach: bool,
@ -716,6 +720,7 @@ impl EmitterWriter {
EmitterWriter { EmitterWriter {
dst, dst,
sm: source_map, sm: source_map,
fluent_bundle,
fallback_bundle, fallback_bundle,
short_message, short_message,
teach, teach,
@ -728,6 +733,7 @@ impl EmitterWriter {
pub fn new( pub fn new(
dst: Box<dyn Write + Send>, dst: Box<dyn Write + Send>,
source_map: Option<Lrc<SourceMap>>, source_map: Option<Lrc<SourceMap>>,
fluent_bundle: Option<Lrc<FluentBundle>>,
fallback_bundle: Lrc<FluentBundle>, fallback_bundle: Lrc<FluentBundle>,
short_message: bool, short_message: bool,
teach: bool, teach: bool,
@ -738,6 +744,7 @@ impl EmitterWriter {
EmitterWriter { EmitterWriter {
dst: Raw(dst, colored), dst: Raw(dst, colored),
sm: source_map, sm: source_map,
fluent_bundle,
fallback_bundle, fallback_bundle,
short_message, short_message,
teach, teach,

View file

@ -37,6 +37,7 @@ pub struct JsonEmitter {
dst: Box<dyn Write + Send>, dst: Box<dyn Write + Send>,
registry: Option<Registry>, registry: Option<Registry>,
sm: Lrc<SourceMap>, sm: Lrc<SourceMap>,
fluent_bundle: Option<Lrc<FluentBundle>>,
fallback_bundle: Lrc<FluentBundle>, fallback_bundle: Lrc<FluentBundle>,
pretty: bool, pretty: bool,
ui_testing: bool, ui_testing: bool,
@ -49,6 +50,7 @@ impl JsonEmitter {
pub fn stderr( pub fn stderr(
registry: Option<Registry>, registry: Option<Registry>,
source_map: Lrc<SourceMap>, source_map: Lrc<SourceMap>,
fluent_bundle: Option<Lrc<FluentBundle>>,
fallback_bundle: Lrc<FluentBundle>, fallback_bundle: Lrc<FluentBundle>,
pretty: bool, pretty: bool,
json_rendered: HumanReadableErrorType, json_rendered: HumanReadableErrorType,
@ -59,6 +61,7 @@ impl JsonEmitter {
dst: Box::new(io::BufWriter::new(io::stderr())), dst: Box::new(io::BufWriter::new(io::stderr())),
registry, registry,
sm: source_map, sm: source_map,
fluent_bundle,
fallback_bundle, fallback_bundle,
pretty, pretty,
ui_testing: false, ui_testing: false,
@ -71,6 +74,7 @@ impl JsonEmitter {
pub fn basic( pub fn basic(
pretty: bool, pretty: bool,
json_rendered: HumanReadableErrorType, json_rendered: HumanReadableErrorType,
fluent_bundle: Option<Lrc<FluentBundle>>,
fallback_bundle: Lrc<FluentBundle>, fallback_bundle: Lrc<FluentBundle>,
terminal_width: Option<usize>, terminal_width: Option<usize>,
macro_backtrace: bool, macro_backtrace: bool,
@ -79,6 +83,7 @@ impl JsonEmitter {
JsonEmitter::stderr( JsonEmitter::stderr(
None, None,
Lrc::new(SourceMap::new(file_path_mapping)), Lrc::new(SourceMap::new(file_path_mapping)),
fluent_bundle,
fallback_bundle, fallback_bundle,
pretty, pretty,
json_rendered, json_rendered,
@ -91,6 +96,7 @@ impl JsonEmitter {
dst: Box<dyn Write + Send>, dst: Box<dyn Write + Send>,
registry: Option<Registry>, registry: Option<Registry>,
source_map: Lrc<SourceMap>, source_map: Lrc<SourceMap>,
fluent_bundle: Option<Lrc<FluentBundle>>,
fallback_bundle: Lrc<FluentBundle>, fallback_bundle: Lrc<FluentBundle>,
pretty: bool, pretty: bool,
json_rendered: HumanReadableErrorType, json_rendered: HumanReadableErrorType,
@ -101,6 +107,7 @@ impl JsonEmitter {
dst, dst,
registry, registry,
sm: source_map, sm: source_map,
fluent_bundle,
fallback_bundle, fallback_bundle,
pretty, pretty,
ui_testing: false, ui_testing: false,
@ -182,7 +189,7 @@ impl Emitter for JsonEmitter {
} }
fn fluent_bundle(&self) -> Option<&Lrc<FluentBundle>> { fn fluent_bundle(&self) -> Option<&Lrc<FluentBundle>> {
None self.fluent_bundle.as_ref()
} }
fn fallback_fluent_bundle(&self) -> &Lrc<FluentBundle> { fn fallback_fluent_bundle(&self) -> &Lrc<FluentBundle> {
@ -395,6 +402,7 @@ impl Diagnostic {
.new_emitter( .new_emitter(
Box::new(buf), Box::new(buf),
Some(je.sm.clone()), Some(je.sm.clone()),
je.fluent_bundle.clone(),
je.fallback_bundle.clone(), je.fallback_bundle.clone(),
false, false,
je.terminal_width, je.terminal_width,

View file

@ -39,13 +39,15 @@ fn test_positions(code: &str, span: (u32, u32), expected_output: SpanTestData) {
rustc_span::create_default_session_globals_then(|| { rustc_span::create_default_session_globals_then(|| {
let sm = Lrc::new(SourceMap::new(FilePathMapping::empty())); let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
sm.new_source_file(Path::new("test.rs").to_owned().into(), code.to_owned()); sm.new_source_file(Path::new("test.rs").to_owned().into(), code.to_owned());
let fallback_bundle = crate::fallback_fluent_bundle(); let fallback_bundle =
crate::fallback_fluent_bundle().expect("failed to load fallback fluent bundle");
let output = Arc::new(Mutex::new(Vec::new())); let output = Arc::new(Mutex::new(Vec::new()));
let je = JsonEmitter::new( let je = JsonEmitter::new(
Box::new(Shared { data: output.clone() }), Box::new(Shared { data: output.clone() }),
None, None,
sm, sm,
None,
fallback_bundle, fallback_bundle,
true, true,
HumanReadableErrorType::Short(ColorConfig::Never), HumanReadableErrorType::Short(ColorConfig::Never),

View file

@ -32,7 +32,8 @@ use rustc_data_structures::stable_hasher::StableHasher;
use rustc_data_structures::sync::{self, Lock, Lrc}; use rustc_data_structures::sync::{self, Lock, Lrc};
use rustc_data_structures::AtomicRef; use rustc_data_structures::AtomicRef;
pub use rustc_error_messages::{ pub use rustc_error_messages::{
fallback_fluent_bundle, DiagnosticMessage, FluentBundle, MultiSpan, SpanLabel, fallback_fluent_bundle, fluent_bundle, DiagnosticMessage, FluentBundle, LanguageIdentifier,
MultiSpan, SpanLabel,
}; };
pub use rustc_lint_defs::{pluralize, Applicability}; pub use rustc_lint_defs::{pluralize, Applicability};
use rustc_serialize::json::Json; use rustc_serialize::json::Json;
@ -544,11 +545,13 @@ impl Handler {
can_emit_warnings: bool, can_emit_warnings: bool,
treat_err_as_bug: Option<NonZeroUsize>, treat_err_as_bug: Option<NonZeroUsize>,
sm: Option<Lrc<SourceMap>>, sm: Option<Lrc<SourceMap>>,
fluent_bundle: Option<Lrc<FluentBundle>>,
fallback_bundle: Lrc<FluentBundle>, fallback_bundle: Lrc<FluentBundle>,
) -> Self { ) -> Self {
Self::with_tty_emitter_and_flags( Self::with_tty_emitter_and_flags(
color_config, color_config,
sm, sm,
fluent_bundle,
fallback_bundle, fallback_bundle,
HandlerFlags { can_emit_warnings, treat_err_as_bug, ..Default::default() }, HandlerFlags { can_emit_warnings, treat_err_as_bug, ..Default::default() },
) )
@ -557,12 +560,14 @@ impl Handler {
pub fn with_tty_emitter_and_flags( pub fn with_tty_emitter_and_flags(
color_config: ColorConfig, color_config: ColorConfig,
sm: Option<Lrc<SourceMap>>, sm: Option<Lrc<SourceMap>>,
fluent_bundle: Option<Lrc<FluentBundle>>,
fallback_bundle: Lrc<FluentBundle>, fallback_bundle: Lrc<FluentBundle>,
flags: HandlerFlags, flags: HandlerFlags,
) -> Self { ) -> Self {
let emitter = Box::new(EmitterWriter::stderr( let emitter = Box::new(EmitterWriter::stderr(
color_config, color_config,
sm, sm,
fluent_bundle,
fallback_bundle, fallback_bundle,
false, false,
false, false,

View file

@ -127,7 +127,8 @@ fn test_harness(file_text: &str, span_labels: Vec<SpanLabel>, expected_output: &
create_default_session_if_not_set_then(|_| { create_default_session_if_not_set_then(|_| {
let output = Arc::new(Mutex::new(Vec::new())); let output = Arc::new(Mutex::new(Vec::new()));
let fallback_bundle = rustc_errors::fallback_fluent_bundle(); let fallback_bundle =
rustc_errors::fallback_fluent_bundle().expect("failed to load fallback fluent bundle");
let source_map = Lrc::new(SourceMap::new(FilePathMapping::empty())); let source_map = Lrc::new(SourceMap::new(FilePathMapping::empty()));
source_map.new_source_file(Path::new("test.rs").to_owned().into(), file_text.to_owned()); source_map.new_source_file(Path::new("test.rs").to_owned().into(), file_text.to_owned());
@ -143,6 +144,7 @@ fn test_harness(file_text: &str, span_labels: Vec<SpanLabel>, expected_output: &
let emitter = EmitterWriter::new( let emitter = EmitterWriter::new(
Box::new(Shared { data: output.clone() }), Box::new(Shared { data: output.clone() }),
Some(source_map.clone()), Some(source_map.clone()),
None,
fallback_bundle, fallback_bundle,
false, false,
false, false,

View file

@ -2856,6 +2856,7 @@ crate mod dep_tracking {
use crate::lint; use crate::lint;
use crate::options::WasiExecModel; use crate::options::WasiExecModel;
use crate::utils::{NativeLib, NativeLibKind}; use crate::utils::{NativeLib, NativeLibKind};
use rustc_errors::LanguageIdentifier;
use rustc_feature::UnstableFeatures; use rustc_feature::UnstableFeatures;
use rustc_span::edition::Edition; use rustc_span::edition::Edition;
use rustc_span::RealFileName; use rustc_span::RealFileName;
@ -2948,6 +2949,7 @@ crate mod dep_tracking {
LocationDetail, LocationDetail,
BranchProtection, BranchProtection,
OomStrategy, OomStrategy,
LanguageIdentifier,
); );
impl<T1, T2> DepTrackingHash for (T1, T2) impl<T1, T2> DepTrackingHash for (T1, T2)

View file

@ -4,6 +4,7 @@ use crate::early_error;
use crate::lint; use crate::lint;
use crate::search_paths::SearchPath; use crate::search_paths::SearchPath;
use crate::utils::NativeLib; use crate::utils::NativeLib;
use rustc_errors::LanguageIdentifier;
use rustc_target::spec::{CodeModel, LinkerFlavor, MergeFunctions, PanicStrategy, SanitizerSet}; use rustc_target::spec::{CodeModel, LinkerFlavor, MergeFunctions, PanicStrategy, SanitizerSet};
use rustc_target::spec::{ use rustc_target::spec::{
RelocModel, RelroLevel, SplitDebuginfo, StackProtector, TargetTriple, TlsModel, RelocModel, RelroLevel, SplitDebuginfo, StackProtector, TargetTriple, TlsModel,
@ -365,6 +366,7 @@ mod desc {
pub const parse_string: &str = "a string"; pub const parse_string: &str = "a string";
pub const parse_opt_string: &str = parse_string; pub const parse_opt_string: &str = parse_string;
pub const parse_string_push: &str = parse_string; pub const parse_string_push: &str = parse_string;
pub const parse_opt_langid: &str = "a language identifier";
pub const parse_opt_pathbuf: &str = "a path"; pub const parse_opt_pathbuf: &str = "a path";
pub const parse_list: &str = "a space-separated list of strings"; pub const parse_list: &str = "a space-separated list of strings";
pub const parse_opt_comma_list: &str = "a comma-separated list of strings"; pub const parse_opt_comma_list: &str = "a comma-separated list of strings";
@ -487,6 +489,17 @@ mod parse {
} }
} }
/// Parse an optional language identifier, e.g. `en-US` or `zh-CN`.
crate fn parse_opt_langid(slot: &mut Option<LanguageIdentifier>, v: Option<&str>) -> bool {
match v {
Some(s) => {
*slot = rustc_errors::LanguageIdentifier::from_str(s).ok();
true
}
None => false,
}
}
crate fn parse_opt_pathbuf(slot: &mut Option<PathBuf>, v: Option<&str>) -> bool { crate fn parse_opt_pathbuf(slot: &mut Option<PathBuf>, v: Option<&str>) -> bool {
match v { match v {
Some(s) => { Some(s) => {
@ -1462,6 +1475,13 @@ options! {
"the directory the intermediate files are written to"), "the directory the intermediate files are written to"),
terminal_width: Option<usize> = (None, parse_opt_number, [UNTRACKED], terminal_width: Option<usize> = (None, parse_opt_number, [UNTRACKED],
"set the current terminal width"), "set the current terminal width"),
// Diagnostics are considered side-effects of a query (see `QuerySideEffects`) and are saved
// alongside query results and changes to translation options can affect diagnostics - so
// translation options should be tracked.
translate_lang: Option<LanguageIdentifier> = (None, parse_opt_langid, [TRACKED],
"language identifier for diagnostic output"),
translate_additional_ftl: Option<PathBuf> = (None, parse_opt_pathbuf, [TRACKED],
"additional fluent translation to preferentially use (for testing translation)"),
tune_cpu: Option<String> = (None, parse_opt_string, [TRACKED], tune_cpu: Option<String> = (None, parse_opt_string, [TRACKED],
"select processor to schedule for (`rustc --print target-cpus` for details)"), "select processor to schedule for (`rustc --print target-cpus` for details)"),
thinlto: Option<bool> = (None, parse_opt_bool, [TRACKED], thinlto: Option<bool> = (None, parse_opt_bool, [TRACKED],

View file

@ -174,13 +174,15 @@ pub struct ParseSess {
impl ParseSess { impl ParseSess {
/// Used for testing. /// Used for testing.
pub fn new(file_path_mapping: FilePathMapping) -> Self { pub fn new(file_path_mapping: FilePathMapping) -> Self {
let fallback_bundle = fallback_fluent_bundle(); let fallback_bundle =
fallback_fluent_bundle().expect("failed to load fallback fluent bundle");
let sm = Lrc::new(SourceMap::new(file_path_mapping)); let sm = Lrc::new(SourceMap::new(file_path_mapping));
let handler = Handler::with_tty_emitter( let handler = Handler::with_tty_emitter(
ColorConfig::Auto, ColorConfig::Auto,
true, true,
None, None,
Some(sm.clone()), Some(sm.clone()),
None,
fallback_bundle, fallback_bundle,
); );
ParseSess::with_span_handler(handler, sm) ParseSess::with_span_handler(handler, sm)
@ -211,10 +213,11 @@ impl ParseSess {
} }
pub fn with_silent_emitter(fatal_note: Option<String>) -> Self { pub fn with_silent_emitter(fatal_note: Option<String>) -> Self {
let fallback_bundle = fallback_fluent_bundle(); let fallback_bundle =
fallback_fluent_bundle().expect("failed to load fallback fluent bundle");
let sm = Lrc::new(SourceMap::new(FilePathMapping::empty())); let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
let fatal_handler = let fatal_handler =
Handler::with_tty_emitter(ColorConfig::Auto, false, None, None, fallback_bundle); Handler::with_tty_emitter(ColorConfig::Auto, false, None, None, None, fallback_bundle);
let handler = Handler::with_emitter( let handler = Handler::with_emitter(
false, false,
None, None,

View file

@ -20,8 +20,8 @@ use rustc_errors::emitter::{Emitter, EmitterWriter, HumanReadableErrorType};
use rustc_errors::json::JsonEmitter; use rustc_errors::json::JsonEmitter;
use rustc_errors::registry::Registry; use rustc_errors::registry::Registry;
use rustc_errors::{ use rustc_errors::{
fallback_fluent_bundle, DiagnosticBuilder, DiagnosticId, DiagnosticMessage, ErrorGuaranteed, fallback_fluent_bundle, fluent_bundle, DiagnosticBuilder, DiagnosticId, DiagnosticMessage,
FluentBundle, MultiSpan, ErrorGuaranteed, FluentBundle, MultiSpan,
}; };
use rustc_macros::HashStable_Generic; use rustc_macros::HashStable_Generic;
pub use rustc_span::def_id::StableCrateId; pub use rustc_span::def_id::StableCrateId;
@ -1069,6 +1069,7 @@ fn default_emitter(
sopts: &config::Options, sopts: &config::Options,
registry: rustc_errors::registry::Registry, registry: rustc_errors::registry::Registry,
source_map: Lrc<SourceMap>, source_map: Lrc<SourceMap>,
bundle: Option<Lrc<FluentBundle>>,
fallback_bundle: Lrc<FluentBundle>, fallback_bundle: Lrc<FluentBundle>,
emitter_dest: Option<Box<dyn Write + Send>>, emitter_dest: Option<Box<dyn Write + Send>>,
) -> Box<dyn Emitter + sync::Send> { ) -> Box<dyn Emitter + sync::Send> {
@ -1080,6 +1081,7 @@ fn default_emitter(
if let HumanReadableErrorType::AnnotateSnippet(_) = kind { if let HumanReadableErrorType::AnnotateSnippet(_) = kind {
let emitter = AnnotateSnippetEmitterWriter::new( let emitter = AnnotateSnippetEmitterWriter::new(
Some(source_map), Some(source_map),
bundle,
fallback_bundle, fallback_bundle,
short, short,
macro_backtrace, macro_backtrace,
@ -1090,6 +1092,7 @@ fn default_emitter(
None => EmitterWriter::stderr( None => EmitterWriter::stderr(
color_config, color_config,
Some(source_map), Some(source_map),
bundle,
fallback_bundle, fallback_bundle,
short, short,
sopts.debugging_opts.teach, sopts.debugging_opts.teach,
@ -1099,6 +1102,7 @@ fn default_emitter(
Some(dst) => EmitterWriter::new( Some(dst) => EmitterWriter::new(
dst, dst,
Some(source_map), Some(source_map),
bundle,
fallback_bundle, fallback_bundle,
short, short,
false, // no teach messages when writing to a buffer false, // no teach messages when writing to a buffer
@ -1114,6 +1118,7 @@ fn default_emitter(
JsonEmitter::stderr( JsonEmitter::stderr(
Some(registry), Some(registry),
source_map, source_map,
bundle,
fallback_bundle, fallback_bundle,
pretty, pretty,
json_rendered, json_rendered,
@ -1127,6 +1132,7 @@ fn default_emitter(
dst, dst,
Some(registry), Some(registry),
source_map, source_map,
bundle,
fallback_bundle, fallback_bundle,
pretty, pretty,
json_rendered, json_rendered,
@ -1198,9 +1204,15 @@ pub fn build_session(
hash_kind, hash_kind,
)); ));
let fallback_bundle = fallback_fluent_bundle(); let bundle = fluent_bundle(
&sysroot,
sopts.debugging_opts.translate_lang.clone(),
sopts.debugging_opts.translate_additional_ftl.as_deref(),
)
.expect("failed to load fluent bundle");
let fallback_bundle = fallback_fluent_bundle().expect("failed to load fallback fluent bundle");
let emitter = let emitter =
default_emitter(&sopts, registry, source_map.clone(), fallback_bundle.clone(), write_dest); default_emitter(&sopts, registry, source_map.clone(), bundle, fallback_bundle, write_dest);
let span_diagnostic = rustc_errors::Handler::with_emitter_and_flags( let span_diagnostic = rustc_errors::Handler::with_emitter_and_flags(
emitter, emitter,
@ -1433,13 +1445,14 @@ pub enum IncrCompSession {
} }
fn early_error_handler(output: config::ErrorOutputType) -> rustc_errors::Handler { fn early_error_handler(output: config::ErrorOutputType) -> rustc_errors::Handler {
let fallback_bundle = fallback_fluent_bundle(); let fallback_bundle = fallback_fluent_bundle().expect("failed to load fallback fluent bundle");
let emitter: Box<dyn Emitter + sync::Send> = match output { let emitter: Box<dyn Emitter + sync::Send> = match output {
config::ErrorOutputType::HumanReadable(kind) => { config::ErrorOutputType::HumanReadable(kind) => {
let (short, color_config) = kind.unzip(); let (short, color_config) = kind.unzip();
Box::new(EmitterWriter::stderr( Box::new(EmitterWriter::stderr(
color_config, color_config,
None, None,
None,
fallback_bundle, fallback_bundle,
short, short,
false, false,
@ -1448,7 +1461,7 @@ fn early_error_handler(output: config::ErrorOutputType) -> rustc_errors::Handler
)) ))
} }
config::ErrorOutputType::Json { pretty, json_rendered } => { config::ErrorOutputType::Json { pretty, json_rendered } => {
Box::new(JsonEmitter::basic(pretty, json_rendered, fallback_bundle, None, false)) Box::new(JsonEmitter::basic(pretty, json_rendered, None, fallback_bundle, None, false))
} }
}; };
rustc_errors::Handler::with_emitter(true, None, emitter) rustc_errors::Handler::with_emitter(true, None, emitter)

View file

@ -143,7 +143,8 @@ crate fn new_handler(
source_map: Option<Lrc<source_map::SourceMap>>, source_map: Option<Lrc<source_map::SourceMap>>,
debugging_opts: &DebuggingOptions, debugging_opts: &DebuggingOptions,
) -> rustc_errors::Handler { ) -> rustc_errors::Handler {
let fallback_bundle = rustc_errors::fallback_fluent_bundle(); let fallback_bundle =
rustc_errors::fallback_fluent_bundle().expect("failed to load fallback fluent bundle");
let emitter: Box<dyn Emitter + sync::Send> = match error_format { let emitter: Box<dyn Emitter + sync::Send> = match error_format {
ErrorOutputType::HumanReadable(kind) => { ErrorOutputType::HumanReadable(kind) => {
let (short, color_config) = kind.unzip(); let (short, color_config) = kind.unzip();
@ -151,6 +152,7 @@ crate fn new_handler(
EmitterWriter::stderr( EmitterWriter::stderr(
color_config, color_config,
source_map.map(|sm| sm as _), source_map.map(|sm| sm as _),
None,
fallback_bundle, fallback_bundle,
short, short,
debugging_opts.teach, debugging_opts.teach,
@ -168,6 +170,7 @@ crate fn new_handler(
JsonEmitter::stderr( JsonEmitter::stderr(
None, None,
source_map, source_map,
None,
fallback_bundle, fallback_bundle,
pretty, pretty,
json_rendered, json_rendered,

View file

@ -537,10 +537,12 @@ crate fn make_test(
// Any errors in parsing should also appear when the doctest is compiled for real, so just // Any errors in parsing should also appear when the doctest is compiled for real, so just
// send all the errors that librustc_ast emits directly into a `Sink` instead of stderr. // send all the errors that librustc_ast emits directly into a `Sink` instead of stderr.
let sm = Lrc::new(SourceMap::new(FilePathMapping::empty())); let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
let fallback_bundle = rustc_errors::fallback_fluent_bundle(); let fallback_bundle = rustc_errors::fallback_fluent_bundle()
.expect("failed to load fallback fluent bundle");
supports_color = EmitterWriter::stderr( supports_color = EmitterWriter::stderr(
ColorConfig::Auto, ColorConfig::Auto,
None, None,
None,
fallback_bundle.clone(), fallback_bundle.clone(),
false, false,
false, false,
@ -552,6 +554,7 @@ crate fn make_test(
let emitter = EmitterWriter::new( let emitter = EmitterWriter::new(
box io::sink(), box io::sink(),
None, None,
None,
fallback_bundle, fallback_bundle,
false, false,
false, false,

View file

@ -32,7 +32,8 @@ struct SyntaxChecker<'a, 'tcx> {
impl<'a, 'tcx> SyntaxChecker<'a, 'tcx> { impl<'a, 'tcx> SyntaxChecker<'a, 'tcx> {
fn check_rust_syntax(&self, item: &clean::Item, dox: &str, code_block: RustCodeBlock) { fn check_rust_syntax(&self, item: &clean::Item, dox: &str, code_block: RustCodeBlock) {
let buffer = Lrc::new(Lock::new(Buffer::default())); let buffer = Lrc::new(Lock::new(Buffer::default()));
let fallback_bundle = rustc_errors::fallback_fluent_bundle(); let fallback_bundle =
rustc_errors::fallback_fluent_bundle().expect("failed to load fallback fluent bundle");
let emitter = BufferEmitter { buffer: Lrc::clone(&buffer), fallback_bundle }; let emitter = BufferEmitter { buffer: Lrc::clone(&buffer), fallback_bundle };
let sm = Lrc::new(SourceMap::new(FilePathMapping::empty())); let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));

View file

@ -0,0 +1,33 @@
include ../../run-make-fulldeps/tools.mk
# This test uses `ln -s` rather than copying to save testing time, but its
# usage doesn't work on Windows.
# ignore-windows
SYSROOT:=$(shell $(RUSTC) --print sysroot)
FAKEROOT=$(TMPDIR)/fakeroot
all: normal custom sysroot
normal: basic-translation.rs
$(RUSTC) $< 2>&1 | grep "struct literal body without path"
custom: basic-translation.rs basic-translation.ftl
$(RUSTC) $< -Ztranslate-additional-ftl=$(CURDIR)/basic-translation.ftl 2>&1 | grep "this is a test message"
# Make a local copy of the sysroot and add the custom locale to it.
sysroot: basic-translation.rs basic-translation.ftl
mkdir $(FAKEROOT)
ln -s $(SYSROOT)/* $(FAKEROOT)
rm -f $(FAKEROOT)/lib
mkdir $(FAKEROOT)/lib
ln -s $(SYSROOT)/lib/* $(FAKEROOT)/lib
rm -f $(FAKEROOT)/lib/rustlib
mkdir $(FAKEROOT)/lib/rustlib
ln -s $(SYSROOT)/lib/rustlib/* $(FAKEROOT)/lib/rustlib
rm -f $(FAKEROOT)/lib/rustlib/src
mkdir $(FAKEROOT)/lib/rustlib/src
ln -s $(SYSROOT)/lib/rustlib/src/* $(FAKEROOT)/lib/rustlib/src
mkdir -p $(FAKEROOT)/share/locale/zh-CN/
ln -s $(CURDIR)/basic-translation.ftl $(FAKEROOT)/share/locale/zh-CN/basic-translation.ftl
$(RUSTC) $< --sysroot $(FAKEROOT) -Ztranslate-lang=zh-CN 2>&1 | grep "this is a test message"

View file

@ -0,0 +1,2 @@
parser-struct-literal-body-without-path = this is a test message
.suggestion = this is a test suggestion

View file

@ -0,0 +1,18 @@
// Exact error being tested isn't relevant, it just needs to be known that it uses Fluent-backed
// diagnostics.
struct Foo {
val: (),
}
fn foo() -> Foo {
val: (),
}
fn main() {
let x = foo();
x.val == 42;
let x = {
val: (),
};
}

View file

@ -621,10 +621,12 @@ fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) {
let filename = FileName::anon_source_code(&code); let filename = FileName::anon_source_code(&code);
let sm = Lrc::new(SourceMap::new(FilePathMapping::empty())); let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
let fallback_bundle = rustc_errors::fallback_fluent_bundle(); let fallback_bundle = rustc_errors::fallback_fluent_bundle()
.expect("failed to load fallback fluent bundle");
let emitter = EmitterWriter::new( let emitter = EmitterWriter::new(
Box::new(io::sink()), Box::new(io::sink()),
None, None,
None,
fallback_bundle, fallback_bundle,
false, false,
false, false,

View file

@ -165,10 +165,12 @@ fn report_clippy_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) {
// Separate the output with an empty line // Separate the output with an empty line
eprintln!(); eprintln!();
let fallback_bundle = rustc_errors::fallback_fluent_bundle(); let fallback_bundle = rustc_errors::fallback_fluent_bundle()
.expect("failed to load fallback fluent bundle");
let emitter = Box::new(rustc_errors::emitter::EmitterWriter::stderr( let emitter = Box::new(rustc_errors::emitter::EmitterWriter::stderr(
rustc_errors::ColorConfig::Auto, rustc_errors::ColorConfig::Auto,
None, None,
None,
fallback_bundle, fallback_bundle,
false, false,
false, false,

View file

@ -114,10 +114,12 @@ fn default_handler(
let emitter = if hide_parse_errors { let emitter = if hide_parse_errors {
silent_emitter() silent_emitter()
} else { } else {
let fallback_bundle = rustc_errors::fallback_fluent_bundle(); let fallback_bundle = rustc_errors::fallback_fluent_bundle()
.expect("failed to load fallback fluent bundle");
Box::new(EmitterWriter::stderr( Box::new(EmitterWriter::stderr(
color_cfg, color_cfg,
Some(source_map.clone()), Some(source_map.clone()),
None,
fallback_bundle, fallback_bundle,
false, false,
false, false,