Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New attribute macros format for diagnostic structs without fluent slug #117867

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1324,6 +1324,16 @@ dependencies = [
"miniz_oxide",
]

[[package]]
name = "fluent"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61f69378194459db76abd2ce3952b790db103ceb003008d3d50d97c41ff847a7"
dependencies = [
"fluent-bundle",
"unic-langid",
]

[[package]]
name = "fluent-bundle"
version = "0.15.2"
Expand Down Expand Up @@ -2318,10 +2328,11 @@ dependencies = [

[[package]]
name = "md-5"
version = "0.10.5"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca"
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
dependencies = [
"cfg-if",
"digest",
]

Expand Down Expand Up @@ -3772,6 +3783,8 @@ version = "0.0.0"
dependencies = [
"annotate-snippets",
"derive_setters",
"fluent",
"md-5",
"rustc_ast",
"rustc_ast_pretty",
"rustc_data_structures",
Expand All @@ -3789,6 +3802,7 @@ dependencies = [
"termcolor",
"termize",
"tracing",
"unic-langid",
"unicode-width",
"windows",
]
Expand Down Expand Up @@ -4113,6 +4127,7 @@ version = "0.0.0"
dependencies = [
"proc-macro2",
"quote",
"regex",
"syn 2.0.32",
"synstructure",
]
Expand Down
1 change: 0 additions & 1 deletion compiler/rustc_driver_impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,6 @@ pub static DEFAULT_LOCALE_RESOURCES: &[&str] = &[
rustc_mir_dataflow::DEFAULT_LOCALE_RESOURCE,
rustc_mir_transform::DEFAULT_LOCALE_RESOURCE,
rustc_monomorphize::DEFAULT_LOCALE_RESOURCE,
rustc_parse::DEFAULT_LOCALE_RESOURCE,
rustc_passes::DEFAULT_LOCALE_RESOURCE,
rustc_pattern_analysis::DEFAULT_LOCALE_RESOURCE,
rustc_privacy::DEFAULT_LOCALE_RESOURCE,
Expand Down
18 changes: 17 additions & 1 deletion compiler/rustc_error_messages/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,8 @@ type FluentId = Cow<'static, str>;
pub enum SubdiagnosticMessage {
/// Non-translatable diagnostic message.
Str(Cow<'static, str>),
/// Translatable diagnostic message in Fluent raw format.
FluentRaw(Cow<'static, str>),
/// Translatable message which has already been translated eagerly.
///
/// Some diagnostics have repeated subdiagnostics where the same interpolated variables would
Expand Down Expand Up @@ -310,6 +312,8 @@ impl From<Cow<'static, str>> for SubdiagnosticMessage {
pub enum DiagnosticMessage {
/// Non-translatable diagnostic message.
Str(Cow<'static, str>),
/// Translatable diagnostic message in Fluent raw format.
FluentRaw(Cow<'static, str>),
/// Translatable message which has already been translated eagerly.
///
/// Some diagnostics have repeated subdiagnostics where the same interpolated variables would
Expand Down Expand Up @@ -339,6 +343,7 @@ impl DiagnosticMessage {
pub fn with_subdiagnostic_message(&self, sub: SubdiagnosticMessage) -> Self {
let attr = match sub {
SubdiagnosticMessage::Str(s) => return DiagnosticMessage::Str(s),
SubdiagnosticMessage::FluentRaw(s) => return DiagnosticMessage::FluentRaw(s),
SubdiagnosticMessage::Eager(s) => return DiagnosticMessage::Eager(s),
SubdiagnosticMessage::FluentIdentifier(id) => {
return DiagnosticMessage::FluentIdentifier(id, None);
Expand All @@ -348,6 +353,7 @@ impl DiagnosticMessage {

match self {
DiagnosticMessage::Str(s) => DiagnosticMessage::Str(s.clone()),
DiagnosticMessage::FluentRaw(s) => DiagnosticMessage::FluentRaw(s.clone()),
DiagnosticMessage::Eager(s) => DiagnosticMessage::Eager(s.clone()),
DiagnosticMessage::FluentIdentifier(id, _) => {
DiagnosticMessage::FluentIdentifier(id.clone(), Some(attr))
Expand All @@ -357,7 +363,9 @@ impl DiagnosticMessage {

pub fn as_str(&self) -> Option<&str> {
match self {
DiagnosticMessage::Eager(s) | DiagnosticMessage::Str(s) => Some(s),
DiagnosticMessage::Eager(s)
| DiagnosticMessage::Str(s)
| DiagnosticMessage::FluentRaw(s) => Some(s),
DiagnosticMessage::FluentIdentifier(_, _) => None,
}
}
Expand All @@ -379,6 +387,13 @@ impl From<Cow<'static, str>> for DiagnosticMessage {
}
}

#[macro_export]
macro_rules! fluent_raw {
($str:expr) => {
DiagnosticMessage::FluentRaw(Cow::Borrowed($str))
};
}

/// A workaround for "good path" ICEs when formatting types in disabled lints.
///
/// Delays formatting until `.into(): DiagnosticMessage` is used.
Expand All @@ -399,6 +414,7 @@ impl Into<SubdiagnosticMessage> for DiagnosticMessage {
fn into(self) -> SubdiagnosticMessage {
match self {
DiagnosticMessage::Str(s) => SubdiagnosticMessage::Str(s),
DiagnosticMessage::FluentRaw(s) => SubdiagnosticMessage::FluentRaw(s),
DiagnosticMessage::Eager(s) => SubdiagnosticMessage::Eager(s),
DiagnosticMessage::FluentIdentifier(id, None) => {
SubdiagnosticMessage::FluentIdentifier(id)
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_errors/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ edition = "2021"
# tidy-alphabetical-start
annotate-snippets = "0.9"
derive_setters = "0.1.6"
fluent = "0.16.0"
md-5 = "0.10.6"
rustc_ast = { path = "../rustc_ast" }
rustc_ast_pretty = { path = "../rustc_ast_pretty" }
rustc_data_structures = { path = "../rustc_data_structures" }
Expand All @@ -24,6 +26,7 @@ serde_json = "1.0.59"
termcolor = "1.2.0"
termize = "0.1.1"
tracing = "0.1"
unic-langid = {version = "0.9.1", features = ["unic-langid-macros"]}
unicode-width = "0.1.4"
# tidy-alphabetical-end

Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_errors/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ pub use diagnostic_impls::{
};
pub use emitter::ColorConfig;
pub use rustc_error_messages::{
fallback_fluent_bundle, fluent_bundle, DelayDm, DiagnosticMessage, FluentBundle,
fallback_fluent_bundle, fluent_bundle, fluent_raw, DelayDm, DiagnosticMessage, FluentBundle,
LanguageIdentifier, LazyFallbackBundle, MultiSpan, SpanLabel, SubdiagnosticMessage,
};
pub use rustc_lint_defs::{pluralize, Applicability};
Expand Down
66 changes: 66 additions & 0 deletions compiler/rustc_errors/src/translation.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
use crate::error::{TranslateError, TranslateErrorKind};
use crate::fluent_bundle::FluentResource;
use crate::snippet::Style;
use crate::{DiagnosticArg, DiagnosticMessage, FluentBundle};
use fluent::FluentBundle as RawFluentBundle;
use md5::{Digest, Md5};
use rustc_data_structures::sync::Lrc;
use rustc_error_messages::FluentArgs;
use std::borrow::Cow;
use std::env;
use std::error::Report;
use unic_langid::langid;

/// Convert diagnostic arguments (a rustc internal type that exists to implement
/// `Encodable`/`Decodable`) into `FluentArgs` which is necessary to perform translation.
Expand Down Expand Up @@ -64,8 +68,70 @@ pub trait Translate {
DiagnosticMessage::Str(msg) | DiagnosticMessage::Eager(msg) => {
return Ok(Cow::Borrowed(msg));
}
DiagnosticMessage::FluentRaw(msg) => {
// FIXME(yukang): calculate the `slug` from the raw fluent content,
// The fluent resources are generated by a simple standalone visitor:
// https://github.com/chenyukang/fluent-utils/blob/main/src/visitor.rs#L13-L97
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I still prefer some solution where the compiler is able to emit the reference ftl rather than needing another tool to extract it. I know that we tried to do this by writing static variables into a section, but we might also be able to do it from the proc macro by writing to the filesystem (which isn't ideal) based on an environment variable.

// we may need to add fluent-utils into the tools directory of rustc
let mut hasher = Md5::new();
hasher.update(msg.to_string());
let digest = hasher.finalize();
let id = format!("{:x}", digest);
let identifier = format!("slug-{}", id[0..8].to_string());
let id = Cow::Borrowed(identifier.as_str());

let translate_with_bundle =
|bundle: &'a FluentBundle| -> Result<Cow<'_, str>, TranslateError<'_>> {
let message = bundle
.get_message(&identifier)
.ok_or(TranslateError::message(&id, args))?;
let value = message.value().ok_or(TranslateError::value(&id, args))?;
debug!(?message, ?value);

let mut errs = vec![];
let translated = bundle.format_pattern(value, Some(args), &mut errs);
debug!(?translated, ?errs);
if errs.is_empty() {
Ok(translated)
} else {
Err(TranslateError::fluent(&id, args, errs))
}
};

return {
match self.fluent_bundle().map(|b| translate_with_bundle(b)) {
// The primary bundle was present and translation succeeded
Some(Ok(t)) => {
// eprintln!("translated id OK: {} => {}", identifier, t);
Ok(t)
}

// If `translate_with_bundle` returns `Err` with the primary bundle, this is likely
// just that the primary bundle doesn't contain the message being translated, so
// proceed to the fallback bundle.
_ => {
// fallback to en-US, we don't need fluent bundle for raw fluent content in English
// here we just interpret the variables in the fluent content.
let fluent_text = format!("dummy = {}", msg);
if let Ok(resource) = FluentResource::try_new(fluent_text) {
let mut bundle = RawFluentBundle::new(vec![langid!("en-US")]);
bundle.add_resource(resource).unwrap();
let mut errors = vec![];
let pattern = bundle.get_message("dummy").unwrap().value().unwrap();
let res = bundle.format_pattern(&pattern, Some(args), &mut errors);
Ok(Cow::Owned(
res.to_string().replace("\u{2068}", "").replace("\u{2069}", ""),
))
} else {
Ok(Cow::Owned(msg.to_string()))
}
}
}
};
}
DiagnosticMessage::FluentIdentifier(identifier, attr) => (identifier, attr),
};
// FIXME(yukang): remove this part for fluent resource id after all diagnostics are migrated to Fluent
let translate_with_bundle =
|bundle: &'a FluentBundle| -> Result<Cow<'_, str>, TranslateError<'_>> {
let message = bundle
Expand Down
5 changes: 1 addition & 4 deletions compiler/rustc_expand/src/parse/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@ use rustc_span::{BytePos, FileName, Pos, Span};
use std::path::PathBuf;

fn sess() -> ParseSess {
ParseSess::new(
vec![crate::DEFAULT_LOCALE_RESOURCE, rustc_parse::DEFAULT_LOCALE_RESOURCE],
FilePathMapping::empty(),
)
ParseSess::new(vec![crate::DEFAULT_LOCALE_RESOURCE], FilePathMapping::empty())
}

/// Parses an item.
Expand Down
16 changes: 4 additions & 12 deletions compiler/rustc_expand/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,8 @@ fn string_to_parser(ps: &ParseSess, source_str: String) -> Parser<'_> {
fn create_test_handler() -> (DiagCtxt, Lrc<SourceMap>, Arc<Mutex<Vec<u8>>>) {
let output = Arc::new(Mutex::new(Vec::new()));
let source_map = Lrc::new(SourceMap::new(FilePathMapping::empty()));
let fallback_bundle = rustc_errors::fallback_fluent_bundle(
vec![crate::DEFAULT_LOCALE_RESOURCE, rustc_parse::DEFAULT_LOCALE_RESOURCE],
false,
);
let fallback_bundle =
rustc_errors::fallback_fluent_bundle(vec![crate::DEFAULT_LOCALE_RESOURCE], false);
let emitter = EmitterWriter::new(Box::new(Shared { data: output.clone() }), fallback_bundle)
.sm(Some(source_map.clone()))
.diagnostic_width(Some(140));
Expand Down Expand Up @@ -72,10 +70,7 @@ where

/// Maps a string to tts, using a made-up filename.
pub(crate) fn string_to_stream(source_str: String) -> TokenStream {
let ps = ParseSess::new(
vec![crate::DEFAULT_LOCALE_RESOURCE, rustc_parse::DEFAULT_LOCALE_RESOURCE],
FilePathMapping::empty(),
);
let ps = ParseSess::new(vec![crate::DEFAULT_LOCALE_RESOURCE], FilePathMapping::empty());
source_file_to_stream(
&ps,
ps.source_map().new_source_file(PathBuf::from("bogofile").into(), source_str),
Expand All @@ -85,10 +80,7 @@ pub(crate) fn string_to_stream(source_str: String) -> TokenStream {

/// Parses a string, returns a crate.
pub(crate) fn string_to_crate(source_str: String) -> ast::Crate {
let ps = ParseSess::new(
vec![crate::DEFAULT_LOCALE_RESOURCE, rustc_parse::DEFAULT_LOCALE_RESOURCE],
FilePathMapping::empty(),
);
let ps = ParseSess::new(vec![crate::DEFAULT_LOCALE_RESOURCE], FilePathMapping::empty());
with_error_checking_parse(source_str, &ps, |p| p.parse_crate_mod())
}

Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ proc-macro = true
# tidy-alphabetical-start
proc-macro2 = "1"
quote = "1"
regex = "1.3.3"
syn = { version = "2.0.9", features = ["full"] }
synstructure = "0.13.0"
# tidy-alphabetical-end
25 changes: 19 additions & 6 deletions compiler/rustc_macros/src/diagnostics/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,17 @@ impl<'a> DiagnosticDerive<'a> {
let preamble = builder.preamble(variant);
let body = builder.body(variant);

let init = match builder.slug.value_ref() {
None => {
span_err(builder.span, "diagnostic slug not specified")
let init = match (builder.slug.value_ref(), builder.label.value_ref()) {
(None, None) => {
span_err(builder.span, "diagnostic slug or label is not specified")
.help(
"specify the slug as the first argument to the `#[diag(...)]` \
attribute, such as `#[diag(hir_analysis_example_error)]`",
attribute, such as `#[diag(hir_analysis_example_error)]`, or use format #[diag(label = \"the message ..\")]",
)
.emit();
return DiagnosticDeriveError::ErrorHandled.to_compile_error();
}
Some(slug)
(Some(slug), None)
if let Some(Mismatch { slug_name, crate_name, slug_prefix }) =
Mismatch::check(slug) =>
{
Expand All @@ -48,7 +48,7 @@ impl<'a> DiagnosticDerive<'a> {
.emit();
return DiagnosticDeriveError::ErrorHandled.to_compile_error();
}
Some(slug) => {
(Some(slug), None) => {
slugs.borrow_mut().push(slug.clone());
quote! {
let mut diag = rustc_errors::DiagnosticBuilder::new(
Expand All @@ -58,6 +58,18 @@ impl<'a> DiagnosticDerive<'a> {
);
}
}
(None, Some(raw_label)) => {
quote! {
let mut diag = rustc_errors::DiagnosticBuilder::new(
dcx,
level,
DiagnosticMessage::FluentRaw(#raw_label.into())
);
}
}
(Some(_slug), Some(_raw_label)) => {
unreachable!("BUG: slug and raw label specified");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be an error from the proc macro rather than a unreachable!.

}
};

let formatting_init = &builder.formatting_init;
Expand Down Expand Up @@ -88,6 +100,7 @@ impl<'a> DiagnosticDerive<'a> {
}
}
});

for test in slugs.borrow().iter().map(|s| generate_test(s, &structure)) {
imp.extend(test);
}
Expand Down
Loading
Loading