1 #![deny(unused_must_use)]
3 use crate::diagnostics
::error
::{
4 invalid_nested_attr
, span_err
, throw_invalid_attr
, throw_invalid_nested_attr
, throw_span_err
,
7 use crate::diagnostics
::utils
::{
8 build_field_mapping
, is_doc_comment
, report_error_if_not_applied_to_span
, report_type_error
,
9 should_generate_set_arg
, type_is_unit
, type_matches_path
, FieldInfo
, FieldInnerTy
, FieldMap
,
10 HasFieldMap
, SetOnce
, SpannedOption
, SubdiagnosticKind
,
12 use proc_macro2
::{Ident, Span, TokenStream}
;
13 use quote
::{format_ident, quote}
;
15 parse_quote
, spanned
::Spanned
, Attribute
, Meta
, MetaList
, MetaNameValue
, NestedMeta
, Path
, Type
,
17 use synstructure
::{BindingInfo, Structure, VariantInfo}
;
19 /// What kind of diagnostic is being derived - a fatal/error/warning or a lint?
20 #[derive(Clone, PartialEq, Eq)]
21 pub(crate) enum DiagnosticDeriveKind
{
22 Diagnostic { handler: syn::Ident }
,
26 /// Tracks persistent information required for the entire type when building up individual calls to
27 /// diagnostic methods for generated diagnostic derives - both `Diagnostic` for
28 /// fatal/errors/warnings and `LintDiagnostic` for lints.
29 pub(crate) struct DiagnosticDeriveBuilder
{
30 /// The identifier to use for the generated `DiagnosticBuilder` instance.
32 /// Kind of diagnostic that should be derived.
33 pub kind
: DiagnosticDeriveKind
,
36 /// Tracks persistent information required for a specific variant when building up individual calls
37 /// to diagnostic methods for generated diagnostic derives - both `Diagnostic` for
38 /// fatal/errors/warnings and `LintDiagnostic` for lints.
39 pub(crate) struct DiagnosticDeriveVariantBuilder
<'parent
> {
40 /// The parent builder for the entire type.
41 pub parent
: &'parent DiagnosticDeriveBuilder
,
43 /// Initialization of format strings for code suggestions.
44 pub formatting_init
: TokenStream
,
46 /// Span of the struct or the enum variant.
47 pub span
: proc_macro
::Span
,
49 /// Store a map of field name to its corresponding field. This is built on construction of the
51 pub field_map
: FieldMap
,
53 /// Slug is a mandatory part of the struct attribute as corresponds to the Fluent message that
54 /// has the actual diagnostic message.
55 pub slug
: SpannedOption
<Path
>,
56 /// Error codes are a optional part of the struct attribute - this is only set to detect
57 /// multiple specifications.
58 pub code
: SpannedOption
<()>,
61 impl<'a
> HasFieldMap
for DiagnosticDeriveVariantBuilder
<'a
> {
62 fn get_field_binding(&self, field
: &String
) -> Option
<&TokenStream
> {
63 self.field_map
.get(field
)
67 impl DiagnosticDeriveBuilder
{
68 /// Call `f` for the struct or for each variant of the enum, returning a `TokenStream` with the
69 /// tokens from `f` wrapped in an `match` expression. Emits errors for use of derive on unions
70 /// or attributes on the type itself when input is an enum.
71 pub fn each_variant
<'s
, F
>(&mut self, structure
: &mut Structure
<'s
>, f
: F
) -> TokenStream
73 F
: for<'a
, 'v
> Fn(DiagnosticDeriveVariantBuilder
<'a
>, &VariantInfo
<'v
>) -> TokenStream
,
75 let ast
= structure
.ast();
76 let span
= ast
.span().unwrap();
78 syn
::Data
::Struct(..) | syn
::Data
::Enum(..) => (),
79 syn
::Data
::Union(..) => {
80 span_err(span
, "diagnostic derives can only be used on structs and enums");
84 if matches
!(ast
.data
, syn
::Data
::Enum(..)) {
85 for attr
in &ast
.attrs
{
88 "unsupported type attribute for diagnostic derive enum",
94 structure
.bind_with(|_
| synstructure
::BindStyle
::Move
);
95 let variants
= structure
.each_variant(|variant
| {
96 let span
= match structure
.ast().data
{
97 syn
::Data
::Struct(..) => span
,
98 // There isn't a good way to get the span of the variant, so the variant's
99 // name will need to do.
100 _
=> variant
.ast().ident
.span().unwrap(),
102 let builder
= DiagnosticDeriveVariantBuilder
{
105 field_map
: build_field_mapping(variant
),
106 formatting_init
: TokenStream
::new(),
121 impl<'a
> DiagnosticDeriveVariantBuilder
<'a
> {
122 /// Generates calls to `code` and similar functions based on the attributes on the type or
124 pub fn preamble
<'s
>(&mut self, variant
: &VariantInfo
<'s
>) -> TokenStream
{
125 let ast
= variant
.ast();
126 let attrs
= &ast
.attrs
;
127 let preamble
= attrs
.iter().map(|attr
| {
128 self.generate_structure_code_for_attr(attr
).unwrap_or_else(|v
| v
.to_compile_error())
136 /// Generates calls to `span_label` and similar functions based on the attributes on fields or
137 /// calls to `set_arg` when no attributes are present.
138 pub fn body
<'s
>(&mut self, variant
: &VariantInfo
<'s
>) -> TokenStream
{
139 let mut body
= quote
! {}
;
140 // Generate `set_arg` calls first..
141 for binding
in variant
.bindings().iter().filter(|bi
| should_generate_set_arg(bi
.ast())) {
142 body
.extend(self.generate_field_code(binding
));
144 // ..and then subdiagnostic additions.
145 for binding
in variant
.bindings().iter().filter(|bi
| !should_generate_set_arg(bi
.ast())) {
146 body
.extend(self.generate_field_attrs_code(binding
));
151 /// Parse a `SubdiagnosticKind` from an `Attribute`.
152 fn parse_subdiag_attribute(
155 ) -> Result
<Option
<(SubdiagnosticKind
, Path
)>, DiagnosticDeriveError
> {
156 let Some((subdiag
, slug
)) = SubdiagnosticKind
::from_attr(attr
, self)?
else {
157 // Some attributes aren't errors - like documentation comments - but also aren't
162 if let SubdiagnosticKind
::MultipartSuggestion { .. }
= subdiag
{
163 let meta
= attr
.parse_meta()?
;
164 throw_invalid_attr
!(attr
, &meta
, |diag
| diag
165 .help("consider creating a `Subdiagnostic` instead"));
168 let slug
= slug
.unwrap_or_else(|| match subdiag
{
169 SubdiagnosticKind
::Label
=> parse_quote
! { _subdiag::label }
,
170 SubdiagnosticKind
::Note
=> parse_quote
! { _subdiag::note }
,
171 SubdiagnosticKind
::Help
=> parse_quote
! { _subdiag::help }
,
172 SubdiagnosticKind
::Warn
=> parse_quote
! { _subdiag::warn }
,
173 SubdiagnosticKind
::Suggestion { .. }
=> parse_quote
! { _subdiag::suggestion }
,
174 SubdiagnosticKind
::MultipartSuggestion { .. }
=> unreachable
!(),
177 Ok(Some((subdiag
, slug
)))
180 /// Establishes state in the `DiagnosticDeriveBuilder` resulting from the struct
181 /// attributes like `#[diag(..)]`, such as the slug and error code. Generates
182 /// diagnostic builder calls for setting error code and creating note/help messages.
183 fn generate_structure_code_for_attr(
186 ) -> Result
<TokenStream
, DiagnosticDeriveError
> {
187 let diag
= &self.parent
.diag
;
189 // Always allow documentation comments.
190 if is_doc_comment(attr
) {
191 return Ok(quote
! {}
);
194 let name
= attr
.path
.segments
.last().unwrap().ident
.to_string();
195 let name
= name
.as_str();
196 let meta
= attr
.parse_meta()?
;
199 let Meta
::List(MetaList { ref nested, .. }
) = meta
else {
206 let mut nested_iter
= nested
.into_iter().peekable();
208 match nested_iter
.peek() {
209 Some(NestedMeta
::Meta(Meta
::Path(slug
))) => {
210 self.slug
.set_once(slug
.clone(), slug
.span().unwrap());
213 Some(NestedMeta
::Meta(Meta
::NameValue { .. }
)) => {}
214 Some(nested_attr
) => throw_invalid_nested_attr
!(attr
, &nested_attr
, |diag
| diag
215 .help("a diagnostic slug is required as the first argument")),
216 None
=> throw_invalid_attr
!(attr
, &meta
, |diag
| diag
217 .help("a diagnostic slug is required as the first argument")),
220 // Remaining attributes are optional, only `code = ".."` at the moment.
221 let mut tokens
= TokenStream
::new();
222 for nested_attr
in nested_iter
{
223 let (value
, path
) = match nested_attr
{
224 NestedMeta
::Meta(Meta
::NameValue(MetaNameValue
{
225 lit
: syn
::Lit
::Str(value
),
228 })) => (value
, path
),
229 NestedMeta
::Meta(Meta
::Path(_
)) => {
230 invalid_nested_attr(attr
, &nested_attr
)
231 .help("diagnostic slug must be the first argument")
236 invalid_nested_attr(attr
, &nested_attr
).emit();
241 let nested_name
= path
.segments
.last().unwrap().ident
.to_string();
242 // Struct attributes are only allowed to be applied once, and the diagnostic
243 // changes will be set in the initialisation code.
244 let span
= value
.span().unwrap();
245 match nested_name
.as_str() {
247 self.code
.set_once((), span
);
249 let code
= value
.value();
250 tokens
.extend(quote
! {
251 #diag.code(rustc_errors::DiagnosticId::Error(#code.to_string()));
254 _
=> invalid_nested_attr(attr
, &nested_attr
)
255 .help("only `code` is a valid nested attributes following the slug")
262 let Some((subdiag
, slug
)) = self.parse_subdiag_attribute(attr
)?
else {
263 // Some attributes aren't errors - like documentation comments - but also aren't
265 return Ok(quote
! {}
);
267 let fn_ident
= format_ident
!("{}", subdiag
);
269 SubdiagnosticKind
::Note
| SubdiagnosticKind
::Help
| SubdiagnosticKind
::Warn
=> {
270 Ok(self.add_subdiagnostic(&fn_ident
, slug
))
272 SubdiagnosticKind
::Label
| SubdiagnosticKind
::Suggestion { .. }
=> {
273 throw_invalid_attr
!(attr
, &meta
, |diag
| diag
274 .help("`#[label]` and `#[suggestion]` can only be applied to fields"));
276 SubdiagnosticKind
::MultipartSuggestion { .. }
=> unreachable
!(),
280 fn generate_field_code(&mut self, binding_info
: &BindingInfo
<'_
>) -> TokenStream
{
281 let diag
= &self.parent
.diag
;
283 let field
= binding_info
.ast();
284 let field_binding
= &binding_info
.binding
;
286 let ident
= field
.ident
.as_ref().unwrap();
287 let ident
= format_ident
!("{}", ident
); // strip `r#` prefix, if present
297 fn generate_field_attrs_code(&mut self, binding_info
: &BindingInfo
<'_
>) -> TokenStream
{
298 let field
= binding_info
.ast();
299 let field_binding
= &binding_info
.binding
;
301 let inner_ty
= FieldInnerTy
::from_type(&field
.ty
);
307 // Always allow documentation comments.
308 if is_doc_comment(attr
) {
312 let name
= attr
.path
.segments
.last().unwrap().ident
.to_string();
314 name
== "primary_span" && matches
!(inner_ty
, FieldInnerTy
::Vec(_
));
315 let (binding
, needs_destructure
) = if needs_clone
{
316 // `primary_span` can accept a `Vec<Span>` so don't destructure that.
317 (quote
! { #field_binding.clone() }
, false)
319 (quote
! { #field_binding }
, true)
322 let generated_code
= self
323 .generate_inner_field_code(
326 binding
: binding_info
,
327 ty
: inner_ty
.inner_type().unwrap_or(&field
.ty
),
332 .unwrap_or_else(|v
| v
.to_compile_error());
334 if needs_destructure
{
335 inner_ty
.with(field_binding
, generated_code
)
343 fn generate_inner_field_code(
347 binding
: TokenStream
,
348 ) -> Result
<TokenStream
, DiagnosticDeriveError
> {
349 let diag
= &self.parent
.diag
;
350 let meta
= attr
.parse_meta()?
;
352 let ident
= &attr
.path
.segments
.last().unwrap().ident
;
353 let name
= ident
.to_string();
354 match (&meta
, name
.as_str()) {
355 // Don't need to do anything - by virtue of the attribute existing, the
356 // `set_arg` call will not be generated.
357 (Meta
::Path(_
), "skip_arg") => return Ok(quote
! {}
),
358 (Meta
::Path(_
), "primary_span") => {
359 match self.parent
.kind
{
360 DiagnosticDeriveKind
::Diagnostic { .. }
=> {
361 report_error_if_not_applied_to_span(attr
, &info
)?
;
364 #diag.set_span(#binding);
367 DiagnosticDeriveKind
::LintDiagnostic
=> {
368 throw_invalid_attr
!(attr
, &meta
, |diag
| {
369 diag
.help("the `primary_span` field attribute is not valid for lint diagnostics")
374 (Meta
::Path(_
), "subdiagnostic") => {
375 return Ok(quote
! { #diag.subdiagnostic(#binding); }
);
377 (Meta
::NameValue(_
), "subdiagnostic") => {
378 throw_invalid_attr
!(attr
, &meta
, |diag
| {
379 diag
.help("`eager` is the only supported nested attribute for `subdiagnostic`")
382 (Meta
::List(MetaList { ref nested, .. }
), "subdiagnostic") => {
383 if nested
.len() != 1 {
384 throw_invalid_attr
!(attr
, &meta
, |diag
| {
386 "`eager` is the only supported nested attribute for `subdiagnostic`",
391 let handler
= match &self.parent
.kind
{
392 DiagnosticDeriveKind
::Diagnostic { handler }
=> handler
,
393 DiagnosticDeriveKind
::LintDiagnostic
=> {
394 throw_invalid_attr
!(attr
, &meta
, |diag
| {
395 diag
.help("eager subdiagnostics are not supported on lints")
400 let nested_attr
= nested
.first().expect("pop failed for single element list");
402 NestedMeta
::Meta(meta @ Meta
::Path(_
))
403 if meta
.path().segments
.last().unwrap().ident
.to_string().as_str()
406 return Ok(quote
! { #diag.eager_subdiagnostic(#handler, #binding); }
);
409 throw_invalid_nested_attr
!(attr
, nested_attr
, |diag
| {
410 diag
.help("`eager` is the only supported nested attribute for `subdiagnostic`")
418 let Some((subdiag
, slug
)) = self.parse_subdiag_attribute(attr
)?
else {
419 // Some attributes aren't errors - like documentation comments - but also aren't
421 return Ok(quote
! {}
);
423 let fn_ident
= format_ident
!("{}", subdiag
);
425 SubdiagnosticKind
::Label
=> {
426 report_error_if_not_applied_to_span(attr
, &info
)?
;
427 Ok(self.add_spanned_subdiagnostic(binding
, &fn_ident
, slug
))
429 SubdiagnosticKind
::Note
| SubdiagnosticKind
::Help
| SubdiagnosticKind
::Warn
=> {
430 if type_matches_path(&info
.ty
, &["rustc_span", "Span"]) {
431 Ok(self.add_spanned_subdiagnostic(binding
, &fn_ident
, slug
))
432 } else if type_is_unit(&info
.ty
) {
433 Ok(self.add_subdiagnostic(&fn_ident
, slug
))
435 report_type_error(attr
, "`Span` or `()`")?
438 SubdiagnosticKind
::Suggestion
{
440 applicability
: static_applicability
,
444 let (span_field
, mut applicability
) = self.span_and_applicability_of_ty(info
)?
;
446 if let Some((static_applicability
, span
)) = static_applicability
{
447 applicability
.set_once(quote
! { #static_applicability }
, span
);
450 let applicability
= applicability
452 .unwrap_or_else(|| quote
! { rustc_errors::Applicability::Unspecified }
);
453 let style
= suggestion_kind
.to_suggestion_style();
455 self.formatting_init
.extend(code_init
);
457 #diag.span_suggestions_with_style(
459 rustc_errors
::fluent
::#slug,
466 SubdiagnosticKind
::MultipartSuggestion { .. }
=> unreachable
!(),
470 /// Adds a spanned subdiagnostic by generating a `diag.span_$kind` call with the current slug
471 /// and `fluent_attr_identifier`.
472 fn add_spanned_subdiagnostic(
474 field_binding
: TokenStream
,
476 fluent_attr_identifier
: Path
,
478 let diag
= &self.parent
.diag
;
479 let fn_name
= format_ident
!("span_{}", kind
);
483 rustc_errors
::fluent
::#fluent_attr_identifier
488 /// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current slug
489 /// and `fluent_attr_identifier`.
490 fn add_subdiagnostic(&self, kind
: &Ident
, fluent_attr_identifier
: Path
) -> TokenStream
{
491 let diag
= &self.parent
.diag
;
493 #diag.#kind(rustc_errors::fluent::#fluent_attr_identifier);
497 fn span_and_applicability_of_ty(
500 ) -> Result
<(TokenStream
, SpannedOption
<TokenStream
>), DiagnosticDeriveError
> {
502 // If `ty` is `Span` w/out applicability, then use `Applicability::Unspecified`.
503 ty @ Type
::Path(..) if type_matches_path(ty
, &["rustc_span", "Span"]) => {
504 let binding
= &info
.binding
.binding
;
505 Ok((quote
!(#binding), None))
507 // If `ty` is `(Span, Applicability)` then return tokens accessing those.
508 Type
::Tuple(tup
) => {
509 let mut span_idx
= None
;
510 let mut applicability_idx
= None
;
512 fn type_err(span
: &Span
) -> Result
<!, DiagnosticDeriveError
> {
513 span_err(span
.unwrap(), "wrong types for suggestion")
515 "`#[suggestion(...)]` on a tuple field must be applied to fields \
516 of type `(Span, Applicability)`",
519 Err(DiagnosticDeriveError
::ErrorHandled
)
522 for (idx
, elem
) in tup
.elems
.iter().enumerate() {
523 if type_matches_path(elem
, &["rustc_span", "Span"]) {
524 span_idx
.set_once(syn
::Index
::from(idx
), elem
.span().unwrap());
525 } else if type_matches_path(elem
, &["rustc_errors", "Applicability"]) {
526 applicability_idx
.set_once(syn
::Index
::from(idx
), elem
.span().unwrap());
528 type_err(&elem
.span())?
;
532 let Some((span_idx
, _
)) = span_idx
else {
533 type_err(&tup
.span())?
;
535 let Some((applicability_idx
, applicability_span
)) = applicability_idx
else {
536 type_err(&tup
.span())?
;
538 let binding
= &info
.binding
.binding
;
539 let span
= quote
!(#binding.#span_idx);
540 let applicability
= quote
!(#binding.#applicability_idx);
542 Ok((span
, Some((applicability
, applicability_span
))))
544 // If `ty` isn't a `Span` or `(Span, Applicability)` then emit an error.
545 _
=> throw_span_err
!(info
.span
.unwrap(), "wrong field type for suggestion", |diag
| {
547 "`#[suggestion(...)]` should be applied to fields of type `Span` or \
548 `(Span, Applicability)`",