Skip to content

Commit d9b874c

Browse files
committed
Allow deriving multiple subdiagnostics using one SessionSubdiagnostic
This reimplements ac638c1, which had to be reverted in the previous commit because it contains a rebase accident that itself reverted significant unrelated changes to SessionSubdiagnostic.
1 parent 9df75ee commit d9b874c

File tree

3 files changed

+154
-144
lines changed

3 files changed

+154
-144
lines changed

compiler/rustc_macros/src/diagnostics/subdiagnostic.rs

+120-96
Original file line numberDiff line numberDiff line change
@@ -211,11 +211,39 @@ impl<'a> HasFieldMap for SessionSubdiagnosticDeriveBuilder<'a> {
211211
}
212212
}
213213

214+
/// Provides frequently-needed information about the diagnostic kinds being derived for this type.
215+
#[derive(Clone, Copy, Debug)]
216+
struct KindsStatistics {
217+
has_multipart_suggestion: bool,
218+
all_multipart_suggestions: bool,
219+
has_normal_suggestion: bool,
220+
}
221+
222+
impl<'a> FromIterator<&'a SubdiagnosticKind> for KindsStatistics {
223+
fn from_iter<T: IntoIterator<Item = &'a SubdiagnosticKind>>(kinds: T) -> Self {
224+
let mut ret = Self {
225+
has_multipart_suggestion: false,
226+
all_multipart_suggestions: true,
227+
has_normal_suggestion: false,
228+
};
229+
for kind in kinds {
230+
if let SubdiagnosticKind::MultipartSuggestion { .. } = kind {
231+
ret.has_multipart_suggestion = true;
232+
} else {
233+
ret.all_multipart_suggestions = false;
234+
}
235+
236+
if let SubdiagnosticKind::Suggestion { .. } = kind {
237+
ret.has_normal_suggestion = true;
238+
}
239+
}
240+
ret
241+
}
242+
}
243+
214244
impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
215-
fn identify_kind(
216-
&mut self,
217-
) -> Result<Option<(SubdiagnosticKind, Path)>, DiagnosticDeriveError> {
218-
let mut kind_slug = None;
245+
fn identify_kind(&mut self) -> Result<Vec<(SubdiagnosticKind, Path)>, DiagnosticDeriveError> {
246+
let mut kind_slugs = vec![];
219247

220248
for attr in self.variant.ast().attrs {
221249
let span = attr.span().unwrap();
@@ -362,10 +390,10 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
362390
| SubdiagnosticKind::MultipartSuggestion { .. } => {}
363391
}
364392

365-
kind_slug.set_once(((kind, slug), span))
393+
kind_slugs.push((kind, slug))
366394
}
367395

368-
Ok(kind_slug.map(|(kind_slug, _)| kind_slug))
396+
Ok(kind_slugs)
369397
}
370398

371399
/// Generates the code for a field with no attributes.
@@ -387,7 +415,7 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
387415
fn generate_field_attr_code(
388416
&mut self,
389417
binding: &BindingInfo<'_>,
390-
kind: &SubdiagnosticKind,
418+
kind_stats: KindsStatistics,
391419
) -> TokenStream {
392420
let ast = binding.ast();
393421
assert!(ast.attrs.len() > 0, "field without attributes generating attr code");
@@ -405,7 +433,7 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
405433
};
406434

407435
let generated = self
408-
.generate_field_code_inner(kind, attr, info)
436+
.generate_field_code_inner(kind_stats, attr, info)
409437
.unwrap_or_else(|v| v.to_compile_error());
410438

411439
inner_ty.with(binding, generated)
@@ -415,15 +443,15 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
415443

416444
fn generate_field_code_inner(
417445
&mut self,
418-
kind: &SubdiagnosticKind,
446+
kind_stats: KindsStatistics,
419447
attr: &Attribute,
420448
info: FieldInfo<'_>,
421449
) -> Result<TokenStream, DiagnosticDeriveError> {
422450
let meta = attr.parse_meta()?;
423451
match meta {
424-
Meta::Path(path) => self.generate_field_code_inner_path(kind, attr, info, path),
452+
Meta::Path(path) => self.generate_field_code_inner_path(kind_stats, attr, info, path),
425453
Meta::List(list @ MetaList { .. }) => {
426-
self.generate_field_code_inner_list(kind, attr, info, list)
454+
self.generate_field_code_inner_list(kind_stats, attr, info, list)
427455
}
428456
_ => throw_invalid_attr!(attr, &meta),
429457
}
@@ -432,7 +460,7 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
432460
/// Generates the code for a `[Meta::Path]`-like attribute on a field (e.g. `#[primary_span]`).
433461
fn generate_field_code_inner_path(
434462
&mut self,
435-
kind: &SubdiagnosticKind,
463+
kind_stats: KindsStatistics,
436464
attr: &Attribute,
437465
info: FieldInfo<'_>,
438466
path: Path,
@@ -445,7 +473,7 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
445473
match name {
446474
"skip_arg" => Ok(quote! {}),
447475
"primary_span" => {
448-
if matches!(kind, SubdiagnosticKind::MultipartSuggestion { .. }) {
476+
if kind_stats.has_multipart_suggestion {
449477
throw_invalid_attr!(attr, &Meta::Path(path), |diag| {
450478
diag.help(
451479
"multipart suggestions use one or more `#[suggestion_part]`s rather \
@@ -464,32 +492,20 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
464492
"suggestion_part" => {
465493
self.has_suggestion_parts = true;
466494

467-
match kind {
468-
SubdiagnosticKind::MultipartSuggestion { .. } => {
469-
span_err(
470-
span,
471-
"`#[suggestion_part(...)]` attribute without `code = \"...\"`",
472-
)
495+
if kind_stats.has_multipart_suggestion {
496+
span_err(span, "`#[suggestion_part(...)]` attribute without `code = \"...\"`")
473497
.emit();
474-
Ok(quote! {})
475-
}
476-
SubdiagnosticKind::Label
477-
| SubdiagnosticKind::Note
478-
| SubdiagnosticKind::Help
479-
| SubdiagnosticKind::Warn
480-
| SubdiagnosticKind::Suggestion { .. } => {
481-
throw_invalid_attr!(attr, &Meta::Path(path), |diag| {
482-
diag.help(
498+
Ok(quote! {})
499+
} else {
500+
throw_invalid_attr!(attr, &Meta::Path(path), |diag| {
501+
diag.help(
483502
"`#[suggestion_part(...)]` is only valid in multipart suggestions, use `#[primary_span]` instead",
484503
)
485-
});
486-
}
504+
});
487505
}
488506
}
489507
"applicability" => {
490-
if let SubdiagnosticKind::Suggestion { .. }
491-
| SubdiagnosticKind::MultipartSuggestion { .. } = kind
492-
{
508+
if kind_stats.has_multipart_suggestion || kind_stats.has_normal_suggestion {
493509
report_error_if_not_applied_to_applicability(attr, &info)?;
494510

495511
let binding = info.binding.binding.clone();
@@ -501,13 +517,16 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
501517
Ok(quote! {})
502518
}
503519
_ => throw_invalid_attr!(attr, &Meta::Path(path), |diag| {
504-
let span_attr = if let SubdiagnosticKind::MultipartSuggestion { .. } = kind {
505-
"suggestion_part"
506-
} else {
507-
"primary_span"
508-
};
520+
let mut span_attrs = vec![];
521+
if kind_stats.has_multipart_suggestion {
522+
span_attrs.push("suggestion_part");
523+
}
524+
if !kind_stats.all_multipart_suggestions {
525+
span_attrs.push("primary_span")
526+
}
509527
diag.help(format!(
510-
"only `{span_attr}`, `applicability` and `skip_arg` are valid field attributes",
528+
"only `{}`, `applicability` and `skip_arg` are valid field attributes",
529+
span_attrs.join(", ")
511530
))
512531
}),
513532
}
@@ -517,7 +536,7 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
517536
/// `#[suggestion_part(code = "...")]`).
518537
fn generate_field_code_inner_list(
519538
&mut self,
520-
kind: &SubdiagnosticKind,
539+
kind_stats: KindsStatistics,
521540
attr: &Attribute,
522541
info: FieldInfo<'_>,
523542
list: MetaList,
@@ -529,7 +548,7 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
529548

530549
match name {
531550
"suggestion_part" => {
532-
if !matches!(kind, SubdiagnosticKind::MultipartSuggestion { .. }) {
551+
if !kind_stats.has_multipart_suggestion {
533552
throw_invalid_attr!(attr, &Meta::List(list), |diag| {
534553
diag.help(
535554
"`#[suggestion_part(...)]` is only valid in multipart suggestions",
@@ -576,43 +595,44 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
576595
Ok(quote! { suggestions.push((#binding, #code)); })
577596
}
578597
_ => throw_invalid_attr!(attr, &Meta::List(list), |diag| {
579-
let span_attr = if let SubdiagnosticKind::MultipartSuggestion { .. } = kind {
580-
"suggestion_part"
581-
} else {
582-
"primary_span"
583-
};
598+
let mut span_attrs = vec![];
599+
if kind_stats.has_multipart_suggestion {
600+
span_attrs.push("suggestion_part");
601+
}
602+
if !kind_stats.all_multipart_suggestions {
603+
span_attrs.push("primary_span")
604+
}
584605
diag.help(format!(
585-
"only `{span_attr}`, `applicability` and `skip_arg` are valid field attributes",
606+
"only `{}`, `applicability` and `skip_arg` are valid field attributes",
607+
span_attrs.join(", ")
586608
))
587609
}),
588610
}
589611
}
590612

591613
pub fn into_tokens(&mut self) -> Result<TokenStream, DiagnosticDeriveError> {
592-
let Some((kind, slug)) = self.identify_kind()? else {
614+
let kind_slugs = self.identify_kind()?;
615+
if kind_slugs.is_empty() {
593616
throw_span_err!(
594617
self.variant.ast().ident.span().unwrap(),
595618
"subdiagnostic kind not specified"
596619
);
597620
};
598621

599-
let init = match &kind {
600-
SubdiagnosticKind::Label
601-
| SubdiagnosticKind::Note
602-
| SubdiagnosticKind::Help
603-
| SubdiagnosticKind::Warn
604-
| SubdiagnosticKind::Suggestion { .. } => quote! {},
605-
SubdiagnosticKind::MultipartSuggestion { .. } => {
606-
quote! { let mut suggestions = Vec::new(); }
607-
}
622+
let kind_stats: KindsStatistics = kind_slugs.iter().map(|(kind, _slug)| kind).collect();
623+
624+
let init = if kind_stats.has_multipart_suggestion {
625+
quote! { let mut suggestions = Vec::new(); }
626+
} else {
627+
quote! {}
608628
};
609629

610630
let attr_args: TokenStream = self
611631
.variant
612632
.bindings()
613633
.iter()
614634
.filter(|binding| !binding.ast().attrs.is_empty())
615-
.map(|binding| self.generate_field_attr_code(binding, &kind))
635+
.map(|binding| self.generate_field_attr_code(binding, kind_stats))
616636
.collect();
617637

618638
let span_field = self.span_field.as_ref().map(|(span, _)| span);
@@ -622,48 +642,52 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
622642
);
623643

624644
let diag = &self.diag;
625-
let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind);
626-
let message = quote! { rustc_errors::fluent::#slug };
627-
let call = match kind {
628-
SubdiagnosticKind::Suggestion { suggestion_kind, code } => {
629-
if let Some(span) = span_field {
630-
let style = suggestion_kind.to_suggestion_style();
631-
632-
quote! { #diag.#name(#span, #message, #code, #applicability, #style); }
633-
} else {
634-
span_err(self.span, "suggestion without `#[primary_span]` field").emit();
635-
quote! { unreachable!(); }
636-
}
637-
}
638-
SubdiagnosticKind::MultipartSuggestion { suggestion_kind } => {
639-
if !self.has_suggestion_parts {
640-
span_err(
641-
self.span,
642-
"multipart suggestion without any `#[suggestion_part(...)]` fields",
643-
)
644-
.emit();
645+
let mut calls = TokenStream::new();
646+
for (kind, slug) in kind_slugs {
647+
let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind);
648+
let message = quote! { rustc_errors::fluent::#slug };
649+
let call = match kind {
650+
SubdiagnosticKind::Suggestion { suggestion_kind, code } => {
651+
if let Some(span) = span_field {
652+
let style = suggestion_kind.to_suggestion_style();
653+
654+
quote! { #diag.#name(#span, #message, #code, #applicability, #style); }
655+
} else {
656+
span_err(self.span, "suggestion without `#[primary_span]` field").emit();
657+
quote! { unreachable!(); }
658+
}
645659
}
660+
SubdiagnosticKind::MultipartSuggestion { suggestion_kind } => {
661+
if !self.has_suggestion_parts {
662+
span_err(
663+
self.span,
664+
"multipart suggestion without any `#[suggestion_part(...)]` fields",
665+
)
666+
.emit();
667+
}
646668

647-
let style = suggestion_kind.to_suggestion_style();
669+
let style = suggestion_kind.to_suggestion_style();
648670

649-
quote! { #diag.#name(#message, suggestions, #applicability, #style); }
650-
}
651-
SubdiagnosticKind::Label => {
652-
if let Some(span) = span_field {
653-
quote! { #diag.#name(#span, #message); }
654-
} else {
655-
span_err(self.span, "label without `#[primary_span]` field").emit();
656-
quote! { unreachable!(); }
671+
quote! { #diag.#name(#message, suggestions, #applicability, #style); }
657672
}
658-
}
659-
_ => {
660-
if let Some(span) = span_field {
661-
quote! { #diag.#name(#span, #message); }
662-
} else {
663-
quote! { #diag.#name(#message); }
673+
SubdiagnosticKind::Label => {
674+
if let Some(span) = span_field {
675+
quote! { #diag.#name(#span, #message); }
676+
} else {
677+
span_err(self.span, "label without `#[primary_span]` field").emit();
678+
quote! { unreachable!(); }
679+
}
664680
}
665-
}
666-
};
681+
_ => {
682+
if let Some(span) = span_field {
683+
quote! { #diag.#name(#span, #message); }
684+
} else {
685+
quote! { #diag.#name(#message); }
686+
}
687+
}
688+
};
689+
calls.extend(call);
690+
}
667691

668692
let plain_args: TokenStream = self
669693
.variant
@@ -676,7 +700,7 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
676700
Ok(quote! {
677701
#init
678702
#attr_args
679-
#call
703+
#calls
680704
#plain_args
681705
})
682706
}

src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs

-2
Original file line numberDiff line numberDiff line change
@@ -309,9 +309,7 @@ union AC {
309309

310310
#[derive(SessionSubdiagnostic)]
311311
#[label(parser::add_paren)]
312-
//~^ NOTE previously specified here
313312
#[label(parser::add_paren)]
314-
//~^ ERROR specified multiple times
315313
struct AD {
316314
#[primary_span]
317315
span: Span,

0 commit comments

Comments
 (0)