Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 7b7061d

Browse files
committed
macros: spanless subdiagnostics from () fields
Type attributes could previously be used to support spanless subdiagnostics but these couldn't easily be made optional in the same way that spanned subdiagnostics could by using a field attribute on a field with an `Option<Span>` type. Spanless subdiagnostics can now be specified on fields with `()` type or `Option<()>` type. Signed-off-by: David Wood <[email protected]>
1 parent 1d2ea98 commit 7b7061d

File tree

4 files changed

+97
-33
lines changed

4 files changed

+97
-33
lines changed

compiler/rustc_macros/src/diagnostics/diagnostic.rs

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ use crate::diagnostics::error::{
55
SessionDiagnosticDeriveError,
66
};
77
use crate::diagnostics::utils::{
8-
report_error_if_not_applied_to_span, type_matches_path, Applicability, FieldInfo, FieldInnerTy,
9-
HasFieldMap, SetOnce,
8+
report_error_if_not_applied_to_span, report_type_error, type_is_unit, type_matches_path,
9+
Applicability, FieldInfo, FieldInnerTy, HasFieldMap, SetOnce,
1010
};
11-
use proc_macro2::TokenStream;
11+
use proc_macro2::{Ident, TokenStream};
1212
use quote::{format_ident, quote};
1313
use std::collections::HashMap;
1414
use std::str::FromStr;
@@ -388,7 +388,8 @@ impl SessionDiagnosticDeriveBuilder {
388388
) -> Result<TokenStream, SessionDiagnosticDeriveError> {
389389
let diag = &self.diag;
390390

391-
let name = attr.path.segments.last().unwrap().ident.to_string();
391+
let ident = &attr.path.segments.last().unwrap().ident;
392+
let name = ident.to_string();
392393
let name = name.as_str();
393394

394395
let meta = attr.parse_meta()?;
@@ -405,9 +406,18 @@ impl SessionDiagnosticDeriveBuilder {
405406
#diag.set_span(#binding);
406407
})
407408
}
408-
"label" | "note" | "help" => {
409+
"label" => {
409410
report_error_if_not_applied_to_span(attr, &info)?;
410-
Ok(self.add_subdiagnostic(binding, name, name))
411+
Ok(self.add_spanned_subdiagnostic(binding, ident, name))
412+
}
413+
"note" | "help" => {
414+
if type_matches_path(&info.ty, &["rustc_span", "Span"]) {
415+
Ok(self.add_spanned_subdiagnostic(binding, ident, name))
416+
} else if type_is_unit(&info.ty) {
417+
Ok(self.add_subdiagnostic(ident, name))
418+
} else {
419+
report_type_error(attr, "`Span` or `()`")?;
420+
}
411421
}
412422
"subdiagnostic" => Ok(quote! { #diag.subdiagnostic(#binding); }),
413423
_ => throw_invalid_attr!(attr, &meta, |diag| {
@@ -416,9 +426,18 @@ impl SessionDiagnosticDeriveBuilder {
416426
}),
417427
},
418428
Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(ref s), .. }) => match name {
419-
"label" | "note" | "help" => {
429+
"label" => {
420430
report_error_if_not_applied_to_span(attr, &info)?;
421-
Ok(self.add_subdiagnostic(binding, name, &s.value()))
431+
Ok(self.add_spanned_subdiagnostic(binding, ident, &s.value()))
432+
}
433+
"note" | "help" => {
434+
if type_matches_path(&info.ty, &["rustc_span", "Span"]) {
435+
Ok(self.add_spanned_subdiagnostic(binding, ident, &s.value()))
436+
} else if type_is_unit(&info.ty) {
437+
Ok(self.add_subdiagnostic(ident, &s.value()))
438+
} else {
439+
report_type_error(attr, "`Span` or `()`")?;
440+
}
422441
}
423442
_ => throw_invalid_attr!(attr, &meta, |diag| {
424443
diag.help("only `label`, `note` and `help` are valid field attributes")
@@ -510,12 +529,12 @@ impl SessionDiagnosticDeriveBuilder {
510529
}
511530
}
512531

513-
/// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current slug and
514-
/// `fluent_attr_identifier`.
515-
fn add_subdiagnostic(
532+
/// Adds a spanned subdiagnostic by generating a `diag.span_$kind` call with the current slug
533+
/// and `fluent_attr_identifier`.
534+
fn add_spanned_subdiagnostic(
516535
&self,
517536
field_binding: TokenStream,
518-
kind: &str,
537+
kind: &Ident,
519538
fluent_attr_identifier: &str,
520539
) -> TokenStream {
521540
let diag = &self.diag;
@@ -531,6 +550,16 @@ impl SessionDiagnosticDeriveBuilder {
531550
}
532551
}
533552

553+
/// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current slug
554+
/// and `fluent_attr_identifier`.
555+
fn add_subdiagnostic(&self, kind: &Ident, fluent_attr_identifier: &str) -> TokenStream {
556+
let diag = &self.diag;
557+
let slug = self.slug.as_ref().map(|(slug, _)| slug.as_str()).unwrap_or("missing-slug");
558+
quote! {
559+
#diag.#kind(rustc_errors::DiagnosticMessage::fluent_attr(#slug, #fluent_attr_identifier));
560+
}
561+
}
562+
534563
fn span_and_applicability_of_ty(
535564
&self,
536565
info: FieldInfo<'_>,

compiler/rustc_macros/src/diagnostics/utils.rs

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use proc_macro2::TokenStream;
44
use quote::{format_ident, quote, ToTokens};
55
use std::collections::BTreeSet;
66
use std::str::FromStr;
7-
use syn::{spanned::Spanned, Attribute, Meta, Type, Visibility};
7+
use syn::{spanned::Spanned, Attribute, Meta, Type, TypeTuple, Visibility};
88
use synstructure::BindingInfo;
99

1010
/// Checks whether the type name of `ty` matches `name`.
@@ -25,31 +25,43 @@ pub(crate) fn type_matches_path(ty: &Type, name: &[&str]) -> bool {
2525
}
2626
}
2727

28-
/// Reports an error if the field's type is not `Applicability`.
28+
/// Checks whether the type `ty` is `()`.
29+
pub(crate) fn type_is_unit(ty: &Type) -> bool {
30+
if let Type::Tuple(TypeTuple { elems, .. }) = ty { elems.is_empty() } else { false }
31+
}
32+
33+
/// Reports a type error for field with `attr`.
34+
pub(crate) fn report_type_error(
35+
attr: &Attribute,
36+
ty_name: &str,
37+
) -> Result<!, SessionDiagnosticDeriveError> {
38+
let name = attr.path.segments.last().unwrap().ident.to_string();
39+
let meta = attr.parse_meta()?;
40+
41+
throw_span_err!(
42+
attr.span().unwrap(),
43+
&format!(
44+
"the `#[{}{}]` attribute can only be applied to fields of type {}",
45+
name,
46+
match meta {
47+
Meta::Path(_) => "",
48+
Meta::NameValue(_) => " = ...",
49+
Meta::List(_) => "(...)",
50+
},
51+
ty_name
52+
)
53+
);
54+
}
55+
56+
/// Reports an error if the field's type does not match `path`.
2957
fn report_error_if_not_applied_to_ty(
3058
attr: &Attribute,
3159
info: &FieldInfo<'_>,
3260
path: &[&str],
3361
ty_name: &str,
3462
) -> Result<(), SessionDiagnosticDeriveError> {
3563
if !type_matches_path(&info.ty, path) {
36-
let name = attr.path.segments.last().unwrap().ident.to_string();
37-
let name = name.as_str();
38-
let meta = attr.parse_meta()?;
39-
40-
throw_span_err!(
41-
attr.span().unwrap(),
42-
&format!(
43-
"the `#[{}{}]` attribute can only be applied to fields of type `{}`",
44-
name,
45-
match meta {
46-
Meta::Path(_) => "",
47-
Meta::NameValue(_) => " = ...",
48-
Meta::List(_) => "(...)",
49-
},
50-
ty_name
51-
)
52-
);
64+
report_type_error(attr, ty_name)?;
5365
}
5466

5567
Ok(())
@@ -64,7 +76,7 @@ pub(crate) fn report_error_if_not_applied_to_applicability(
6476
attr,
6577
info,
6678
&["rustc_errors", "Applicability"],
67-
"Applicability",
79+
"`Applicability`",
6880
)
6981
}
7082

@@ -73,7 +85,7 @@ pub(crate) fn report_error_if_not_applied_to_span(
7385
attr: &Attribute,
7486
info: &FieldInfo<'_>,
7587
) -> Result<(), SessionDiagnosticDeriveError> {
76-
report_error_if_not_applied_to_ty(attr, info, &["rustc_span", "Span"], "Span")
88+
report_error_if_not_applied_to_ty(attr, info, &["rustc_span", "Span"], "`Span`")
7789
}
7890

7991
/// Inner type of a field and type of wrapper.

compiler/rustc_macros/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#![feature(allow_internal_unstable)]
22
#![feature(let_else)]
3+
#![feature(never_type)]
34
#![feature(proc_macro_diagnostic)]
45
#![allow(rustc::default_hash_types)]
56
#![recursion_limit = "128"]

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,3 +482,25 @@ struct VecField {
482482
#[label]
483483
spans: Vec<Span>,
484484
}
485+
486+
#[derive(SessionDiagnostic)]
487+
#[error(code = "E0123", slug = "foo")]
488+
struct UnitField {
489+
#[primary_span]
490+
spans: Span,
491+
#[help]
492+
foo: (),
493+
#[help = "a"]
494+
bar: (),
495+
}
496+
497+
#[derive(SessionDiagnostic)]
498+
#[error(code = "E0123", slug = "foo")]
499+
struct OptUnitField {
500+
#[primary_span]
501+
spans: Span,
502+
#[help]
503+
foo: Option<()>,
504+
#[help = "a"]
505+
bar: Option<()>,
506+
}

0 commit comments

Comments
 (0)