Skip to content

Commit 5e1db44

Browse files
Rollup merge of #151872 - JonathanBrouwer:diag3, r=Kivooeo
Add inline syntax for diagnostic messages This PR adds the new inline diagnostics syntax needed for #151366. The syntax itself is briefly described in the MCP: rust-lang/compiler-team#959 To test these changes: * I added quite a few uitests * I converted the first crate `rustc_attr_parsing` to the new syntax in a separate PR: #151944 r? @Kivooeo
2 parents 894d05d + ca2be71 commit 5e1db44

16 files changed

Lines changed: 3139 additions & 188 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4229,6 +4229,8 @@ dependencies = [
42294229
name = "rustc_macros"
42304230
version = "0.0.0"
42314231
dependencies = [
4232+
"fluent-bundle",
4233+
"fluent-syntax",
42324234
"proc-macro2",
42334235
"quote",
42344236
"syn 2.0.110",

compiler/rustc_error_messages/src/lib.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,9 @@ pub enum SubdiagMessage {
247247
/// Identifier of a Fluent message. Instances of this variant are generated by the
248248
/// `Subdiagnostic` derive.
249249
FluentIdentifier(FluentId),
250+
/// An inline Fluent message. Instances of this variant are generated by the
251+
/// `Subdiagnostic` derive.
252+
Inline(Cow<'static, str>),
250253
/// Attribute of a Fluent message. Needs to be combined with a Fluent identifier to produce an
251254
/// actual translated message. Instances of this variant are generated by the `fluent_messages`
252255
/// macro.
@@ -291,6 +294,8 @@ pub enum DiagMessage {
291294
/// <https://projectfluent.org/fluent/guide/hello.html>
292295
/// <https://projectfluent.org/fluent/guide/attributes.html>
293296
FluentIdentifier(FluentId, Option<FluentId>),
297+
/// An inline Fluent message, containing the to be translated diagnostic message.
298+
Inline(Cow<'static, str>),
294299
}
295300

296301
impl DiagMessage {
@@ -305,21 +310,22 @@ impl DiagMessage {
305310
SubdiagMessage::FluentIdentifier(id) => {
306311
return DiagMessage::FluentIdentifier(id, None);
307312
}
313+
SubdiagMessage::Inline(s) => return DiagMessage::Inline(s),
308314
SubdiagMessage::FluentAttr(attr) => attr,
309315
};
310316

311317
match self {
312-
DiagMessage::Str(s) => DiagMessage::Str(s.clone()),
313318
DiagMessage::FluentIdentifier(id, _) => {
314319
DiagMessage::FluentIdentifier(id.clone(), Some(attr))
315320
}
321+
_ => panic!("Tried to add a subdiagnostic to a message without a fluent identifier"),
316322
}
317323
}
318324

319325
pub fn as_str(&self) -> Option<&str> {
320326
match self {
321327
DiagMessage::Str(s) => Some(s),
322-
DiagMessage::FluentIdentifier(_, _) => None,
328+
DiagMessage::FluentIdentifier(_, _) | DiagMessage::Inline(_) => None,
323329
}
324330
}
325331
}
@@ -353,6 +359,7 @@ impl From<DiagMessage> for SubdiagMessage {
353359
// There isn't really a sensible behaviour for this because it loses information but
354360
// this is the most sensible of the behaviours.
355361
DiagMessage::FluentIdentifier(_, Some(attr)) => SubdiagMessage::FluentAttr(attr),
362+
DiagMessage::Inline(s) => SubdiagMessage::Inline(s),
356363
}
357364
}
358365
}

compiler/rustc_errors/src/translation.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ use std::env;
33
use std::error::Report;
44
use std::sync::Arc;
55

6+
use rustc_error_messages::langid;
67
pub use rustc_error_messages::{FluentArgs, LazyFallbackBundle};
78
use tracing::{debug, trace};
89

910
use crate::error::{TranslateError, TranslateErrorKind};
10-
use crate::{DiagArg, DiagMessage, FluentBundle, Style};
11+
use crate::fluent_bundle::FluentResource;
12+
use crate::{DiagArg, DiagMessage, FluentBundle, Style, fluent_bundle};
1113

1214
/// Convert diagnostic arguments (a rustc internal type that exists to implement
1315
/// `Encodable`/`Decodable`) into `FluentArgs` which is necessary to perform translation.
@@ -79,6 +81,28 @@ impl Translator {
7981
return Ok(Cow::Borrowed(msg));
8082
}
8183
DiagMessage::FluentIdentifier(identifier, attr) => (identifier, attr),
84+
// This translates an inline fluent diagnostic message
85+
// It does this by creating a new `FluentBundle` with only one message,
86+
// and then translating using this bundle.
87+
DiagMessage::Inline(msg) => {
88+
const GENERATED_MSG_ID: &str = "generated_msg";
89+
let resource =
90+
FluentResource::try_new(format!("{GENERATED_MSG_ID} = {msg}\n")).unwrap();
91+
let mut bundle = fluent_bundle::FluentBundle::new(vec![langid!("en-US")]);
92+
bundle.set_use_isolating(false);
93+
bundle.add_resource(resource).unwrap();
94+
let message = bundle.get_message(GENERATED_MSG_ID).unwrap();
95+
let value = message.value().unwrap();
96+
97+
let mut errs = vec![];
98+
let translated = bundle.format_pattern(value, Some(args), &mut errs).to_string();
99+
debug!(?translated, ?errs);
100+
return if errs.is_empty() {
101+
Ok(Cow::Owned(translated))
102+
} else {
103+
Err(TranslateError::fluent(&Cow::Borrowed(GENERATED_MSG_ID), args, errs))
104+
};
105+
}
82106
};
83107
let translate_with_bundle =
84108
|bundle: &'a FluentBundle| -> Result<Cow<'_, str>, TranslateError<'_>> {

compiler/rustc_macros/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ proc-macro = true
88

99
[dependencies]
1010
# tidy-alphabetical-start
11+
fluent-bundle = "0.16"
12+
fluent-syntax = "0.12"
1113
proc-macro2 = "1"
1214
quote = "1"
1315
syn = { version = "2.0.9", features = ["full"] }

compiler/rustc_macros/src/diagnostics/diagnostic.rs

Lines changed: 13 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,22 @@ impl<'a> DiagnosticDerive<'a> {
2222
pub(crate) fn into_tokens(self) -> TokenStream {
2323
let DiagnosticDerive { mut structure } = self;
2424
let kind = DiagnosticDeriveKind::Diagnostic;
25-
let slugs = RefCell::new(Vec::new());
25+
let messages = RefCell::new(Vec::new());
2626
let implementation = kind.each_variant(&mut structure, |mut builder, variant| {
2727
let preamble = builder.preamble(variant);
2828
let body = builder.body(variant);
2929

30-
let Some(slug) = builder.primary_message() else {
30+
let Some(message) = builder.primary_message() else {
3131
return DiagnosticDeriveError::ErrorHandled.to_compile_error();
3232
};
33-
slugs.borrow_mut().push(slug.clone());
33+
messages.borrow_mut().push(message.clone());
34+
let message = message.diag_message(variant);
35+
3436
let init = quote! {
3537
let mut diag = rustc_errors::Diag::new(
3638
dcx,
3739
level,
38-
crate::fluent_generated::#slug
40+
#message
3941
);
4042
};
4143

@@ -66,7 +68,7 @@ impl<'a> DiagnosticDerive<'a> {
6668
}
6769
}
6870
});
69-
for test in slugs.borrow().iter().map(|s| generate_test(s, &structure)) {
71+
for test in messages.borrow().iter().map(|s| s.generate_test(&structure)) {
7072
imp.extend(test);
7173
}
7274
imp
@@ -86,17 +88,18 @@ impl<'a> LintDiagnosticDerive<'a> {
8688
pub(crate) fn into_tokens(self) -> TokenStream {
8789
let LintDiagnosticDerive { mut structure } = self;
8890
let kind = DiagnosticDeriveKind::LintDiagnostic;
89-
let slugs = RefCell::new(Vec::new());
91+
let messages = RefCell::new(Vec::new());
9092
let implementation = kind.each_variant(&mut structure, |mut builder, variant| {
9193
let preamble = builder.preamble(variant);
9294
let body = builder.body(variant);
9395

94-
let Some(slug) = builder.primary_message() else {
96+
let Some(message) = builder.primary_message() else {
9597
return DiagnosticDeriveError::ErrorHandled.to_compile_error();
9698
};
97-
slugs.borrow_mut().push(slug.clone());
99+
messages.borrow_mut().push(message.clone());
100+
let message = message.diag_message(variant);
98101
let primary_message = quote! {
99-
diag.primary_message(crate::fluent_generated::#slug);
102+
diag.primary_message(#message);
100103
};
101104

102105
let formatting_init = &builder.formatting_init;
@@ -122,47 +125,10 @@ impl<'a> LintDiagnosticDerive<'a> {
122125
}
123126
}
124127
});
125-
for test in slugs.borrow().iter().map(|s| generate_test(s, &structure)) {
128+
for test in messages.borrow().iter().map(|s| s.generate_test(&structure)) {
126129
imp.extend(test);
127130
}
128131

129132
imp
130133
}
131134
}
132-
133-
/// Generates a `#[test]` that verifies that all referenced variables
134-
/// exist on this structure.
135-
fn generate_test(slug: &syn::Path, structure: &Structure<'_>) -> TokenStream {
136-
// FIXME: We can't identify variables in a subdiagnostic
137-
for field in structure.variants().iter().flat_map(|v| v.ast().fields.iter()) {
138-
for attr_name in field.attrs.iter().filter_map(|at| at.path().get_ident()) {
139-
if attr_name == "subdiagnostic" {
140-
return quote!();
141-
}
142-
}
143-
}
144-
use std::sync::atomic::{AtomicUsize, Ordering};
145-
// We need to make sure that the same diagnostic slug can be used multiple times without
146-
// causing an error, so just have a global counter here.
147-
static COUNTER: AtomicUsize = AtomicUsize::new(0);
148-
let slug = slug.get_ident().unwrap();
149-
let ident = quote::format_ident!("verify_{slug}_{}", COUNTER.fetch_add(1, Ordering::Relaxed));
150-
let ref_slug = quote::format_ident!("{slug}_refs");
151-
let struct_name = &structure.ast().ident;
152-
let variables: Vec<_> = structure
153-
.variants()
154-
.iter()
155-
.flat_map(|v| v.ast().fields.iter().filter_map(|f| f.ident.as_ref().map(|i| i.to_string())))
156-
.collect();
157-
// tidy errors on `#[test]` outside of test files, so we use `#[test ]` to work around this
158-
quote! {
159-
#[cfg(test)]
160-
#[test ]
161-
fn #ident() {
162-
let variables = [#(#variables),*];
163-
for vref in crate::fluent_generated::#ref_slug {
164-
assert!(variables.contains(vref), "{}: variable `{vref}` not found ({})", stringify!(#struct_name), stringify!(#slug));
165-
}
166-
}
167-
}
168-
}

0 commit comments

Comments
 (0)