Skip to content

Commit 2edb5e2

Browse files
committed
macros: compute slugs with raw fluent diags
Signed-off-by: David Wood <[email protected]>
1 parent f254287 commit 2edb5e2

File tree

6 files changed

+85
-24
lines changed

6 files changed

+85
-24
lines changed

compiler/rustc_macros/src/diagnostics/diagnostic.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ impl<'a> DiagnosticDerive<'a> {
2828
let preamble = builder.preamble(variant);
2929
let body = builder.body(variant);
3030

31-
let args = if matches!(builder.slug.value_ref(), Some(SlugOrRawFluent::RawFluent(_))) {
31+
let args = if matches!(builder.slug.value_ref(), Some(SlugOrRawFluent::RawFluent(_, _))) {
3232
let args = builder.args;
3333
quote! {
3434
use rustc_data_structures::fx::FxHashMap;
@@ -70,9 +70,9 @@ impl<'a> DiagnosticDerive<'a> {
7070
);
7171
}
7272
}
73-
Some(SlugOrRawFluent::RawFluent(raw)) => {
73+
Some(SlugOrRawFluent::RawFluent(slug, raw)) => {
7474
quote! {
75-
let raw = dcx.raw_translate("foo", #raw, args.iter());
75+
let raw = dcx.raw_translate(#slug, #raw, args.iter());
7676
let mut diag = rustc_errors::DiagnosticBuilder::new(
7777
dcx,
7878
level,
@@ -176,7 +176,7 @@ impl<'a> LintDiagnosticDerive<'a> {
176176
crate::fluent_generated::#slug.into()
177177
}
178178
}
179-
Some(SlugOrRawFluent::RawFluent(raw)) => {
179+
Some(SlugOrRawFluent::RawFluent(_, raw)) => {
180180
quote! {
181181
#raw
182182
}

compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use crate::diagnostics::error::{
55
};
66
use crate::diagnostics::utils::{
77
build_field_mapping, is_doc_comment, report_error_if_not_applied_to_span, report_type_error,
8-
should_generate_arg, type_is_bool, type_is_unit, type_matches_path, FieldInfo, FieldInnerTy,
9-
FieldMap, HasFieldMap, SetOnce, SpannedOption, SubdiagnosticKind,
8+
should_generate_arg, slugify, type_is_bool, type_is_unit, type_matches_path, FieldInfo,
9+
FieldInnerTy, FieldMap, HasFieldMap, SetOnce, SpannedOption, SubdiagnosticKind,
1010
};
1111
use proc_macro2::{Ident, Span, TokenStream};
1212
use quote::{format_ident, quote, quote_spanned};
@@ -29,10 +29,10 @@ pub(crate) enum SlugOrRawFluent {
2929
///
3030
/// e.g. `#[diag(foo_bar)]`
3131
Slug(Path),
32-
/// Literal string containing the Fluent message.
32+
/// Literal string containing the Fluent message and its computed slug.
3333
///
3434
/// e.g. `#[diag_raw(message = "raw fluent content")]`
35-
RawFluent(LitStr),
35+
RawFluent(String, LitStr),
3636
}
3737

3838
/// Tracks persistent information required for a specific variant when building up individual calls
@@ -60,6 +60,7 @@ pub(crate) struct DiagnosticDeriveVariantBuilder {
6060
/// multiple specifications.
6161
pub code: SpannedOption<()>,
6262

63+
pub generated_slug: String,
6364
pub args: Vec<TokenStream>,
6465
}
6566

@@ -96,8 +97,14 @@ impl DiagnosticDeriveKind {
9697
}
9798
}
9899

100+
let generated_slug = slugify(&structure.ast().ident.to_string());
99101
structure.bind_with(|_| synstructure::BindStyle::Move);
100102
let variants = structure.each_variant(|variant| {
103+
let mut generated_slug = generated_slug.clone();
104+
if matches!(ast.data, syn::Data::Enum(..)) {
105+
generated_slug.push_str("_");
106+
generated_slug.push_str(&slugify(&variant.ast().ident.to_string()));
107+
}
101108
let span = match structure.ast().data {
102109
syn::Data::Struct(..) => span,
103110
// There isn't a good way to get the span of the variant, so the variant's
@@ -111,6 +118,7 @@ impl DiagnosticDeriveKind {
111118
formatting_init: TokenStream::new(),
112119
slug: None,
113120
code: None,
121+
generated_slug,
114122
args: Vec::new(),
115123
};
116124
f(builder, variant)
@@ -157,9 +165,10 @@ impl DiagnosticDeriveVariantBuilder {
157165
/// Parse a `SubdiagnosticKind` from an `Attribute`.
158166
fn parse_subdiag_attribute(
159167
&self,
168+
generated_slug: &str,
160169
attr: &Attribute,
161170
) -> Result<Option<(SubdiagnosticKind, SlugOrRawFluent, bool)>, DiagnosticDeriveError> {
162-
let Some(subdiag) = SubdiagnosticVariant::from_attr(attr, self)? else {
171+
let Some(subdiag) = SubdiagnosticVariant::from_attr(generated_slug, attr, self)? else {
163172
// Some attributes aren't errors - like documentation comments - but also aren't
164173
// subdiagnostics.
165174
return Ok(None);
@@ -228,7 +237,10 @@ impl DiagnosticDeriveVariantBuilder {
228237

229238
if path.is_ident("message") {
230239
self.slug.set_once(
231-
SlugOrRawFluent::RawFluent(nested.parse::<LitStr>()?),
240+
SlugOrRawFluent::RawFluent(
241+
self.generated_slug.clone(),
242+
nested.parse::<LitStr>()?,
243+
),
232244
path.span().unwrap(),
233245
);
234246
return Ok(());
@@ -252,7 +264,9 @@ impl DiagnosticDeriveVariantBuilder {
252264
return Ok(tokens);
253265
}
254266

255-
let Some((subdiag, slug, _no_span)) = self.parse_subdiag_attribute(attr)? else {
267+
let Some((subdiag, slug, _no_span)) =
268+
self.parse_subdiag_attribute(&self.generated_slug, attr)?
269+
else {
256270
// Some attributes aren't errors - like documentation comments - but also aren't
257271
// subdiagnostics.
258272
return Ok(quote! {});
@@ -278,7 +292,7 @@ impl DiagnosticDeriveVariantBuilder {
278292
let ident = field.ident.as_ref().unwrap();
279293
let ident = format_ident!("{}", ident); // strip `r#` prefix, if present
280294

281-
if matches!(self.slug.value_ref(), Some(SlugOrRawFluent::RawFluent(_))) {
295+
if matches!(self.slug.value_ref(), Some(SlugOrRawFluent::RawFluent(_, _))) {
282296
// Cloning should not be necessary once there are no `diag.arg` calls.
283297
self.args.push(quote! {
284298
args.insert(stringify!(#ident).into(), #field_binding.clone().into_diagnostic_arg());
@@ -370,7 +384,14 @@ impl DiagnosticDeriveVariantBuilder {
370384
_ => (),
371385
}
372386

373-
let Some((subdiag, slug, _no_span)) = self.parse_subdiag_attribute(attr)? else {
387+
let mut generated_slug = self.generated_slug.clone();
388+
if let Some(field_name) = &info.binding.ast().ident {
389+
generated_slug.push_str("_");
390+
generated_slug.push_str(&slugify(&field_name.to_string()));
391+
}
392+
let Some((subdiag, slug, _no_span)) =
393+
self.parse_subdiag_attribute(&generated_slug, attr)?
394+
else {
374395
// Some attributes aren't errors - like documentation comments - but also aren't
375396
// subdiagnostics.
376397
return Ok(quote! {});
@@ -432,8 +453,8 @@ impl DiagnosticDeriveVariantBuilder {
432453
#style
433454
);
434455
}),
435-
SlugOrRawFluent::RawFluent(raw) => Ok(quote! {
436-
let raw = dcx.raw_translate("foo", #raw, args.iter());
456+
SlugOrRawFluent::RawFluent(slug, raw) => Ok(quote! {
457+
let raw = dcx.raw_translate(#slug, #raw, args.iter());
437458
diag.span_suggestions_with_style(
438459
#span_field,
439460
raw,
@@ -466,9 +487,9 @@ impl DiagnosticDeriveVariantBuilder {
466487
);
467488
}
468489
}
469-
SlugOrRawFluent::RawFluent(raw) => {
490+
SlugOrRawFluent::RawFluent(slug, raw) => {
470491
quote! {
471-
let raw = dcx.raw_translate("foo", #raw, args.iter());
492+
let raw = dcx.raw_translate(#slug, #raw, args.iter());
472493
diag.#fn_name(#field_binding, raw);
473494
}
474495
}
@@ -484,9 +505,9 @@ impl DiagnosticDeriveVariantBuilder {
484505
diag.#kind(crate::fluent_generated::#fluent_attr_identifier);
485506
}
486507
}
487-
SlugOrRawFluent::RawFluent(raw) => {
508+
SlugOrRawFluent::RawFluent(slug, raw) => {
488509
quote! {
489-
let raw = dcx.raw_translate("foo", #raw, args.iter());
510+
let raw = dcx.raw_translate(#slug, #raw, args.iter());
490511
diag.#kind(raw);
491512
}
492513
}

compiler/rustc_macros/src/diagnostics/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ mod diagnostic;
22
mod diagnostic_builder;
33
mod error;
44
mod subdiagnostic;
5+
#[cfg(test)]
6+
mod tests;
57
mod utils;
68

79
use diagnostic::{DiagnosticDerive, LintDiagnosticDerive};

compiler/rustc_macros/src/diagnostics/subdiagnostic.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use crate::diagnostics::error::{
66
use crate::diagnostics::utils::{
77
build_field_mapping, build_suggestion_code, is_doc_comment, new_code_ident,
88
report_error_if_not_applied_to_applicability, report_error_if_not_applied_to_span,
9-
should_generate_arg, AllowMultipleAlternatives, FieldInfo, FieldInnerTy, FieldMap, HasFieldMap,
10-
SetOnce, SpannedOption, SubdiagnosticKind,
9+
should_generate_arg, slugify, AllowMultipleAlternatives, FieldInfo, FieldInnerTy, FieldMap,
10+
HasFieldMap, SetOnce, SpannedOption, SubdiagnosticKind,
1111
};
1212
use proc_macro2::TokenStream;
1313
use quote::{format_ident, quote};
@@ -61,8 +61,14 @@ impl SubdiagnosticDeriveBuilder {
6161
}
6262
}
6363

64+
let generated_slug = slugify(&structure.ast().ident.to_string());
6465
structure.bind_with(|_| synstructure::BindStyle::Move);
6566
let variants_ = structure.each_variant(|variant| {
67+
let mut generated_slug = generated_slug.clone();
68+
if matches!(ast.data, syn::Data::Enum(..)) {
69+
generated_slug.push_str("_");
70+
generated_slug.push_str(&slugify(&variant.ast().ident.to_string()));
71+
}
6672
let mut builder = SubdiagnosticDeriveVariantBuilder {
6773
parent: &self,
6874
variant,
@@ -73,6 +79,7 @@ impl SubdiagnosticDeriveBuilder {
7379
applicability: None,
7480
has_suggestion_parts: false,
7581
is_enum,
82+
generated_slug,
7683
};
7784
builder.into_tokens().unwrap_or_else(|v| v.to_compile_error())
7885
});
@@ -132,6 +139,8 @@ struct SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
132139

133140
/// Set to true when this variant is an enum variant rather than just the body of a struct.
134141
is_enum: bool,
142+
143+
generated_slug: String,
135144
}
136145

137146
impl<'parent, 'a> HasFieldMap for SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
@@ -186,7 +195,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
186195

187196
for attr in self.variant.ast().attrs {
188197
let Some(SubdiagnosticVariant { kind, slug, no_span }) =
189-
SubdiagnosticVariant::from_attr(attr, self)?
198+
SubdiagnosticVariant::from_attr(&self.generated_slug, attr, self)?
190199
else {
191200
// Some attributes aren't errors - like documentation comments - but also aren't
192201
// subdiagnostics.
@@ -520,7 +529,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
520529
quote! { let #message = #f(#diag, crate::fluent_generated::#slug.into()); },
521530
);
522531
}
523-
SlugOrRawFluent::RawFluent(raw) => {
532+
SlugOrRawFluent::RawFluent(_, raw) => {
524533
calls.extend(quote! { let #message = #raw; });
525534
}
526535
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
use crate::diagnostics::utils::slugify;
2+
3+
#[test]
4+
fn slugs() {
5+
assert_eq!(slugify("ExampleStructName"), "example_struct_name");
6+
assert_eq!(slugify("foo-bar"), "foo_bar");
7+
}

compiler/rustc_macros/src/diagnostics/utils.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,7 @@ impl SubdiagnosticVariant {
609609
/// `#[error(parser::add_paren, no_span)]` or `#[suggestion(code = "...")]`. Returns the
610610
/// `SubdiagnosticKind` and the diagnostic slug, if specified.
611611
pub(super) fn from_attr(
612+
generated_slug: &str,
612613
attr: &Attribute,
613614
fields: &impl HasFieldMap,
614615
) -> Result<Option<SubdiagnosticVariant>, DiagnosticDeriveError> {
@@ -750,7 +751,7 @@ impl SubdiagnosticVariant {
750751
.emit();
751752
return Ok(());
752753
};
753-
slug = Some(SlugOrRawFluent::RawFluent(value.parse::<LitStr>()?));
754+
slug = Some(SlugOrRawFluent::RawFluent(generated_slug.to_string(), value.parse::<LitStr>()?));
754755
},
755756
("code", SubdiagnosticKind::Suggestion { code_field, .. }) => {
756757
let code_init = build_suggestion_code(
@@ -885,3 +886,24 @@ pub(super) fn should_generate_arg(field: &Field) -> bool {
885886
pub(super) fn is_doc_comment(attr: &Attribute) -> bool {
886887
attr.path().segments.last().unwrap().ident == "doc"
887888
}
889+
890+
pub(super) fn slugify(inpt: &str) -> String {
891+
let mut slug = Vec::with_capacity(inpt.len());
892+
for c in inpt.chars() {
893+
match c {
894+
'A'..='Z' => {
895+
if !slug.is_empty() {
896+
slug.push('_');
897+
}
898+
slug.extend(char::to_lowercase(c));
899+
}
900+
'-' => {
901+
slug.push('_');
902+
}
903+
_ => {
904+
slug.push(c);
905+
}
906+
}
907+
}
908+
slug.into_iter().collect()
909+
}

0 commit comments

Comments
 (0)