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(
325 FieldInfo { binding: binding_info, ty: inner_ty, span: &field.span() }
,
328 .unwrap_or_else(|v
| v
.to_compile_error());
330 if needs_destructure
{
331 inner_ty
.with(field_binding
, generated_code
)
339 fn generate_inner_field_code(
343 binding
: TokenStream
,
344 ) -> Result
<TokenStream
, DiagnosticDeriveError
> {
345 let diag
= &self.parent
.diag
;
346 let meta
= attr
.parse_meta()?
;
348 let ident
= &attr
.path
.segments
.last().unwrap().ident
;
349 let name
= ident
.to_string();
350 match (&meta
, name
.as_str()) {
351 // Don't need to do anything - by virtue of the attribute existing, the
352 // `set_arg` call will not be generated.
353 (Meta
::Path(_
), "skip_arg") => return Ok(quote
! {}
),
354 (Meta
::Path(_
), "primary_span") => {
355 match self.parent
.kind
{
356 DiagnosticDeriveKind
::Diagnostic { .. }
=> {
357 report_error_if_not_applied_to_span(attr
, &info
)?
;
360 #diag.set_span(#binding);
363 DiagnosticDeriveKind
::LintDiagnostic
=> {
364 throw_invalid_attr
!(attr
, &meta
, |diag
| {
365 diag
.help("the `primary_span` field attribute is not valid for lint diagnostics")
370 (Meta
::Path(_
), "subdiagnostic") => {
371 if FieldInnerTy
::from_type(&info
.binding
.ast().ty
).will_iterate() {
372 let DiagnosticDeriveKind
::Diagnostic { handler }
= &self.parent
.kind
else {
373 // No eager translation for lints.
374 return Ok(quote
! { #diag.subdiagnostic(#binding); }
);
376 return Ok(quote
! { #diag.eager_subdiagnostic(#handler, #binding); }
);
378 return Ok(quote
! { #diag.subdiagnostic(#binding); }
);
381 (Meta
::List(MetaList { ref nested, .. }
), "subdiagnostic") => {
383 && let Some(NestedMeta
::Meta(Meta
::Path(path
))) = nested
.first()
384 && path
.is_ident("eager") {
385 let handler
= match &self.parent
.kind
{
386 DiagnosticDeriveKind
::Diagnostic { handler }
=> handler
,
387 DiagnosticDeriveKind
::LintDiagnostic
=> {
388 throw_invalid_attr
!(attr
, &meta
, |diag
| {
389 diag
.help("eager subdiagnostics are not supported on lints")
393 return Ok(quote
! { #diag.eager_subdiagnostic(#handler, #binding); }
);
395 throw_invalid_attr
!(attr
, &meta
, |diag
| {
397 "`eager` is the only supported nested attribute for `subdiagnostic`",
405 let Some((subdiag
, slug
)) = self.parse_subdiag_attribute(attr
)?
else {
406 // Some attributes aren't errors - like documentation comments - but also aren't
408 return Ok(quote
! {}
);
410 let fn_ident
= format_ident
!("{}", subdiag
);
412 SubdiagnosticKind
::Label
=> {
413 report_error_if_not_applied_to_span(attr
, &info
)?
;
414 Ok(self.add_spanned_subdiagnostic(binding
, &fn_ident
, slug
))
416 SubdiagnosticKind
::Note
| SubdiagnosticKind
::Help
| SubdiagnosticKind
::Warn
=> {
417 if type_matches_path(info
.ty
.inner_type(), &["rustc_span", "Span"]) {
418 Ok(self.add_spanned_subdiagnostic(binding
, &fn_ident
, slug
))
419 } else if type_is_unit(info
.ty
.inner_type()) {
420 Ok(self.add_subdiagnostic(&fn_ident
, slug
))
422 report_type_error(attr
, "`Span` or `()`")?
425 SubdiagnosticKind
::Suggestion
{
427 applicability
: static_applicability
,
431 if let FieldInnerTy
::Vec(_
) = info
.ty
{
432 throw_invalid_attr
!(attr
, &meta
, |diag
| {
434 .note("`#[suggestion(...)]` applied to `Vec` field is ambiguous")
435 .help("to show a suggestion consisting of multiple parts, use a `Subdiagnostic` annotated with `#[multipart_suggestion(...)]`")
436 .help("to show a variable set of suggestions, use a `Vec` of `Subdiagnostic`s annotated with `#[suggestion(...)]`")
440 let (span_field
, mut applicability
) = self.span_and_applicability_of_ty(info
)?
;
442 if let Some((static_applicability
, span
)) = static_applicability
{
443 applicability
.set_once(quote
! { #static_applicability }
, span
);
446 let applicability
= applicability
448 .unwrap_or_else(|| quote
! { rustc_errors::Applicability::Unspecified }
);
449 let style
= suggestion_kind
.to_suggestion_style();
451 self.formatting_init
.extend(code_init
);
453 #diag.span_suggestions_with_style(
455 crate::fluent_generated
::#slug,
462 SubdiagnosticKind
::MultipartSuggestion { .. }
=> unreachable
!(),
466 /// Adds a spanned subdiagnostic by generating a `diag.span_$kind` call with the current slug
467 /// and `fluent_attr_identifier`.
468 fn add_spanned_subdiagnostic(
470 field_binding
: TokenStream
,
472 fluent_attr_identifier
: Path
,
474 let diag
= &self.parent
.diag
;
475 let fn_name
= format_ident
!("span_{}", kind
);
479 crate::fluent_generated
::#fluent_attr_identifier
484 /// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current slug
485 /// and `fluent_attr_identifier`.
486 fn add_subdiagnostic(&self, kind
: &Ident
, fluent_attr_identifier
: Path
) -> TokenStream
{
487 let diag
= &self.parent
.diag
;
489 #diag.#kind(crate::fluent_generated::#fluent_attr_identifier);
493 fn span_and_applicability_of_ty(
496 ) -> Result
<(TokenStream
, SpannedOption
<TokenStream
>), DiagnosticDeriveError
> {
497 match &info
.ty
.inner_type() {
498 // If `ty` is `Span` w/out applicability, then use `Applicability::Unspecified`.
499 ty @ Type
::Path(..) if type_matches_path(ty
, &["rustc_span", "Span"]) => {
500 let binding
= &info
.binding
.binding
;
501 Ok((quote
!(#binding), None))
503 // If `ty` is `(Span, Applicability)` then return tokens accessing those.
504 Type
::Tuple(tup
) => {
505 let mut span_idx
= None
;
506 let mut applicability_idx
= None
;
508 fn type_err(span
: &Span
) -> Result
<!, DiagnosticDeriveError
> {
509 span_err(span
.unwrap(), "wrong types for suggestion")
511 "`#[suggestion(...)]` on a tuple field must be applied to fields \
512 of type `(Span, Applicability)`",
515 Err(DiagnosticDeriveError
::ErrorHandled
)
518 for (idx
, elem
) in tup
.elems
.iter().enumerate() {
519 if type_matches_path(elem
, &["rustc_span", "Span"]) {
520 span_idx
.set_once(syn
::Index
::from(idx
), elem
.span().unwrap());
521 } else if type_matches_path(elem
, &["rustc_errors", "Applicability"]) {
522 applicability_idx
.set_once(syn
::Index
::from(idx
), elem
.span().unwrap());
524 type_err(&elem
.span())?
;
528 let Some((span_idx
, _
)) = span_idx
else {
529 type_err(&tup
.span())?
;
531 let Some((applicability_idx
, applicability_span
)) = applicability_idx
else {
532 type_err(&tup
.span())?
;
534 let binding
= &info
.binding
.binding
;
535 let span
= quote
!(#binding.#span_idx);
536 let applicability
= quote
!(#binding.#applicability_idx);
538 Ok((span
, Some((applicability
, applicability_span
))))
540 // If `ty` isn't a `Span` or `(Span, Applicability)` then emit an error.
541 _
=> throw_span_err
!(info
.span
.unwrap(), "wrong field type for suggestion", |diag
| {
543 "`#[suggestion(...)]` should be applied to fields of type `Span` or \
544 `(Span, Applicability)`",