Skip to content
Merged
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
2 changes: 2 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4229,6 +4229,8 @@ dependencies = [
name = "rustc_macros"
version = "0.0.0"
dependencies = [
"fluent-bundle",
"fluent-syntax",
"proc-macro2",
"quote",
"syn 2.0.110",
Expand Down
11 changes: 9 additions & 2 deletions compiler/rustc_error_messages/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,9 @@ pub enum SubdiagMessage {
/// Identifier of a Fluent message. Instances of this variant are generated by the
/// `Subdiagnostic` derive.
FluentIdentifier(FluentId),
/// An inline Fluent message. Instances of this variant are generated by the
/// `Subdiagnostic` derive.
Inline(Cow<'static, str>),
/// Attribute of a Fluent message. Needs to be combined with a Fluent identifier to produce an
/// actual translated message. Instances of this variant are generated by the `fluent_messages`
/// macro.
Expand Down Expand Up @@ -291,6 +294,8 @@ pub enum DiagMessage {
/// <https://projectfluent.org/fluent/guide/hello.html>
/// <https://projectfluent.org/fluent/guide/attributes.html>
FluentIdentifier(FluentId, Option<FluentId>),
/// An inline Fluent message, containing the to be translated diagnostic message.
Inline(Cow<'static, str>),
}

impl DiagMessage {
Expand All @@ -305,21 +310,22 @@ impl DiagMessage {
SubdiagMessage::FluentIdentifier(id) => {
return DiagMessage::FluentIdentifier(id, None);
}
SubdiagMessage::Inline(s) => return DiagMessage::Inline(s),
SubdiagMessage::FluentAttr(attr) => attr,
};

match self {
DiagMessage::Str(s) => DiagMessage::Str(s.clone()),
DiagMessage::FluentIdentifier(id, _) => {
DiagMessage::FluentIdentifier(id.clone(), Some(attr))
}
_ => panic!("Tried to add a subdiagnostic to a message without a fluent identifier"),
}
}

pub fn as_str(&self) -> Option<&str> {
match self {
DiagMessage::Str(s) => Some(s),
DiagMessage::FluentIdentifier(_, _) => None,
DiagMessage::FluentIdentifier(_, _) | DiagMessage::Inline(_) => None,
}
}
}
Expand Down Expand Up @@ -353,6 +359,7 @@ impl From<DiagMessage> for SubdiagMessage {
// There isn't really a sensible behaviour for this because it loses information but
// this is the most sensible of the behaviours.
DiagMessage::FluentIdentifier(_, Some(attr)) => SubdiagMessage::FluentAttr(attr),
DiagMessage::Inline(s) => SubdiagMessage::Inline(s),
}
}
}
Expand Down
26 changes: 25 additions & 1 deletion compiler/rustc_errors/src/translation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ use std::env;
use std::error::Report;
use std::sync::Arc;

use rustc_error_messages::langid;
pub use rustc_error_messages::{FluentArgs, LazyFallbackBundle};
use tracing::{debug, trace};

use crate::error::{TranslateError, TranslateErrorKind};
use crate::{DiagArg, DiagMessage, FluentBundle, Style};
use crate::fluent_bundle::FluentResource;
use crate::{DiagArg, DiagMessage, FluentBundle, Style, fluent_bundle};

/// 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 @@ -79,6 +81,28 @@ impl Translator {
return Ok(Cow::Borrowed(msg));
}
DiagMessage::FluentIdentifier(identifier, attr) => (identifier, attr),
// This translates an inline fluent diagnostic message
// It does this by creating a new `FluentBundle` with only one message,
// and then translating using this bundle.
DiagMessage::Inline(msg) => {
const GENERATED_MSG_ID: &str = "generated_msg";
let resource =
FluentResource::try_new(format!("{GENERATED_MSG_ID} = {msg}\n")).unwrap();
let mut bundle = fluent_bundle::FluentBundle::new(vec![langid!("en-US")]);
bundle.set_use_isolating(false);
bundle.add_resource(resource).unwrap();
let message = bundle.get_message(GENERATED_MSG_ID).unwrap();
let value = message.value().unwrap();

let mut errs = vec![];
let translated = bundle.format_pattern(value, Some(args), &mut errs).to_string();
debug!(?translated, ?errs);
return if errs.is_empty() {
Ok(Cow::Owned(translated))
} else {
Err(TranslateError::fluent(&Cow::Borrowed(GENERATED_MSG_ID), args, errs))
};
}
};
let translate_with_bundle =
|bundle: &'a FluentBundle| -> Result<Cow<'_, str>, TranslateError<'_>> {
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ proc-macro = true

[dependencies]
# tidy-alphabetical-start
fluent-bundle = "0.16"
fluent-syntax = "0.12"
proc-macro2 = "1"
quote = "1"
syn = { version = "2.0.9", features = ["full"] }
Expand Down
60 changes: 13 additions & 47 deletions compiler/rustc_macros/src/diagnostics/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,22 @@ impl<'a> DiagnosticDerive<'a> {
pub(crate) fn into_tokens(self) -> TokenStream {
let DiagnosticDerive { mut structure } = self;
let kind = DiagnosticDeriveKind::Diagnostic;
let slugs = RefCell::new(Vec::new());
let messages = RefCell::new(Vec::new());
let implementation = kind.each_variant(&mut structure, |mut builder, variant| {
let preamble = builder.preamble(variant);
let body = builder.body(variant);

let Some(slug) = builder.primary_message() else {
let Some(message) = builder.primary_message() else {
return DiagnosticDeriveError::ErrorHandled.to_compile_error();
};
slugs.borrow_mut().push(slug.clone());
messages.borrow_mut().push(message.clone());
let message = message.diag_message(variant);

let init = quote! {
let mut diag = rustc_errors::Diag::new(
dcx,
level,
crate::fluent_generated::#slug
#message
);
};

Expand Down Expand Up @@ -66,7 +68,7 @@ impl<'a> DiagnosticDerive<'a> {
}
}
});
for test in slugs.borrow().iter().map(|s| generate_test(s, &structure)) {
for test in messages.borrow().iter().map(|s| s.generate_test(&structure)) {
imp.extend(test);
}
imp
Expand All @@ -86,17 +88,18 @@ impl<'a> LintDiagnosticDerive<'a> {
pub(crate) fn into_tokens(self) -> TokenStream {
let LintDiagnosticDerive { mut structure } = self;
let kind = DiagnosticDeriveKind::LintDiagnostic;
let slugs = RefCell::new(Vec::new());
let messages = RefCell::new(Vec::new());
let implementation = kind.each_variant(&mut structure, |mut builder, variant| {
let preamble = builder.preamble(variant);
let body = builder.body(variant);

let Some(slug) = builder.primary_message() else {
let Some(message) = builder.primary_message() else {
return DiagnosticDeriveError::ErrorHandled.to_compile_error();
};
slugs.borrow_mut().push(slug.clone());
messages.borrow_mut().push(message.clone());
let message = message.diag_message(variant);
let primary_message = quote! {
diag.primary_message(crate::fluent_generated::#slug);
diag.primary_message(#message);
};

let formatting_init = &builder.formatting_init;
Expand All @@ -122,47 +125,10 @@ impl<'a> LintDiagnosticDerive<'a> {
}
}
});
for test in slugs.borrow().iter().map(|s| generate_test(s, &structure)) {
for test in messages.borrow().iter().map(|s| s.generate_test(&structure)) {
imp.extend(test);
}

imp
}
}

/// Generates a `#[test]` that verifies that all referenced variables
/// exist on this structure.
fn generate_test(slug: &syn::Path, structure: &Structure<'_>) -> TokenStream {
// FIXME: We can't identify variables in a subdiagnostic
for field in structure.variants().iter().flat_map(|v| v.ast().fields.iter()) {
for attr_name in field.attrs.iter().filter_map(|at| at.path().get_ident()) {
if attr_name == "subdiagnostic" {
return quote!();
}
}
}
use std::sync::atomic::{AtomicUsize, Ordering};
// We need to make sure that the same diagnostic slug can be used multiple times without
// causing an error, so just have a global counter here.
static COUNTER: AtomicUsize = AtomicUsize::new(0);
let slug = slug.get_ident().unwrap();
let ident = quote::format_ident!("verify_{slug}_{}", COUNTER.fetch_add(1, Ordering::Relaxed));
let ref_slug = quote::format_ident!("{slug}_refs");
let struct_name = &structure.ast().ident;
let variables: Vec<_> = structure
.variants()
.iter()
.flat_map(|v| v.ast().fields.iter().filter_map(|f| f.ident.as_ref().map(|i| i.to_string())))
.collect();
// tidy errors on `#[test]` outside of test files, so we use `#[test ]` to work around this
quote! {
#[cfg(test)]
#[test ]
fn #ident() {
let variables = [#(#variables),*];
for vref in crate::fluent_generated::#ref_slug {
assert!(variables.contains(vref), "{}: variable `{vref}` not found ({})", stringify!(#struct_name), stringify!(#slug));
}
}
}
}
Loading
Loading