1 #![deny(unused_must_use)]
3 use crate::diagnostics
::error
::{
4 invalid_attr
, span_err
, throw_invalid_attr
, throw_span_err
, DiagnosticDeriveError
,
6 use crate::diagnostics
::utils
::{
7 build_field_mapping
, build_suggestion_code
, is_doc_comment
, new_code_ident
,
8 report_error_if_not_applied_to_applicability
, report_error_if_not_applied_to_span
,
9 should_generate_set_arg
, AllowMultipleAlternatives
, FieldInfo
, FieldInnerTy
, FieldMap
,
10 HasFieldMap
, SetOnce
, SpannedOption
, SubdiagnosticKind
,
12 use proc_macro2
::TokenStream
;
13 use quote
::{format_ident, quote}
;
14 use syn
::{spanned::Spanned, Attribute, Meta, MetaList, Path}
;
15 use synstructure
::{BindingInfo, Structure, VariantInfo}
;
17 use super::utils
::SubdiagnosticVariant
;
19 /// The central struct for constructing the `add_to_diagnostic` method from an annotated struct.
20 pub(crate) struct SubdiagnosticDeriveBuilder
{
25 impl SubdiagnosticDeriveBuilder
{
26 pub(crate) fn new() -> Self {
27 let diag
= format_ident
!("diag");
28 let f
= format_ident
!("f");
32 pub(crate) fn into_tokens(self, mut structure
: Structure
<'_
>) -> TokenStream
{
33 let implementation
= {
34 let ast
= structure
.ast();
35 let span
= ast
.span().unwrap();
37 syn
::Data
::Struct(..) | syn
::Data
::Enum(..) => (),
38 syn
::Data
::Union(..) => {
41 "`#[derive(Subdiagnostic)]` can only be used on structs and enums",
47 let is_enum
= matches
!(ast
.data
, syn
::Data
::Enum(..));
49 for attr
in &ast
.attrs
{
50 // Always allow documentation comments.
51 if is_doc_comment(attr
) {
57 "unsupported type attribute for subdiagnostic enum",
63 structure
.bind_with(|_
| synstructure
::BindStyle
::Move
);
64 let variants_
= structure
.each_variant(|variant
| {
65 let mut builder
= SubdiagnosticDeriveVariantBuilder
{
69 formatting_init
: TokenStream
::new(),
70 fields
: build_field_mapping(variant
),
73 has_suggestion_parts
: false,
76 builder
.into_tokens().unwrap_or_else(|v
| v
.to_compile_error())
86 let diag
= &self.diag
;
88 let ret
= structure
.gen_impl(quote
! {
89 gen
impl rustc_errors
::AddToDiagnostic
for @
Self {
90 fn add_to_diagnostic_with
<__F
>(self, #diag: &mut rustc_errors::Diagnostic, #f: __F)
93 &mut rustc_errors
::Diagnostic
,
94 rustc_errors
::SubdiagnosticMessage
95 ) -> rustc_errors
::SubdiagnosticMessage
,
97 use rustc_errors
::{Applicability, IntoDiagnosticArg}
;
106 /// Tracks persistent information required for building up the call to add to the diagnostic
107 /// for the final generated method. This is a separate struct to `SubdiagnosticDerive`
108 /// only to be able to destructure and split `self.builder` and the `self.structure` up to avoid a
109 /// double mut borrow later on.
110 struct SubdiagnosticDeriveVariantBuilder
<'parent
, 'a
> {
111 /// The identifier to use for the generated `DiagnosticBuilder` instance.
112 parent
: &'parent SubdiagnosticDeriveBuilder
,
114 /// Info for the current variant (or the type if not an enum).
115 variant
: &'a VariantInfo
<'a
>,
116 /// Span for the entire type.
117 span
: proc_macro
::Span
,
119 /// Initialization of format strings for code suggestions.
120 formatting_init
: TokenStream
,
122 /// Store a map of field name to its corresponding field. This is built on construction of the
126 /// Identifier for the binding to the `#[primary_span]` field.
127 span_field
: SpannedOption
<proc_macro2
::Ident
>,
129 /// The binding to the `#[applicability]` field, if present.
130 applicability
: SpannedOption
<TokenStream
>,
132 /// Set to true when a `#[suggestion_part]` field is encountered, used to generate an error
133 /// during finalization if still `false`.
134 has_suggestion_parts
: bool
,
136 /// Set to true when this variant is an enum variant rather than just the body of a struct.
140 impl<'parent
, 'a
> HasFieldMap
for SubdiagnosticDeriveVariantBuilder
<'parent
, 'a
> {
141 fn get_field_binding(&self, field
: &String
) -> Option
<&TokenStream
> {
142 self.fields
.get(field
)
146 /// Provides frequently-needed information about the diagnostic kinds being derived for this type.
147 #[derive(Clone, Copy, Debug)]
148 struct KindsStatistics
{
149 has_multipart_suggestion
: bool
,
150 all_multipart_suggestions
: bool
,
151 has_normal_suggestion
: bool
,
152 all_applicabilities_static
: bool
,
155 impl<'a
> FromIterator
<&'a SubdiagnosticKind
> for KindsStatistics
{
156 fn from_iter
<T
: IntoIterator
<Item
= &'a SubdiagnosticKind
>>(kinds
: T
) -> Self {
158 has_multipart_suggestion
: false,
159 all_multipart_suggestions
: true,
160 has_normal_suggestion
: false,
161 all_applicabilities_static
: true,
165 if let SubdiagnosticKind
::MultipartSuggestion { applicability: None, .. }
166 | SubdiagnosticKind
::Suggestion { applicability: None, .. }
= kind
168 ret
.all_applicabilities_static
= false;
170 if let SubdiagnosticKind
::MultipartSuggestion { .. }
= kind
{
171 ret
.has_multipart_suggestion
= true;
173 ret
.all_multipart_suggestions
= false;
176 if let SubdiagnosticKind
::Suggestion { .. }
= kind
{
177 ret
.has_normal_suggestion
= true;
184 impl<'parent
, 'a
> SubdiagnosticDeriveVariantBuilder
<'parent
, 'a
> {
187 ) -> Result
<Vec
<(SubdiagnosticKind
, Path
, bool
)>, DiagnosticDeriveError
> {
188 let mut kind_slugs
= vec
![];
190 for attr
in self.variant
.ast().attrs
{
191 let Some(SubdiagnosticVariant { kind, slug, no_span }
) = SubdiagnosticVariant
::from_attr(attr
, self)?
else {
192 // Some attributes aren't errors - like documentation comments - but also aren't
197 let Some(slug
) = slug
else {
198 let name
= attr
.path().segments
.last().unwrap().ident
.to_string();
199 let name
= name
.as_str();
202 attr
.span().unwrap(),
204 "diagnostic slug must be first argument of a `#[{name}(...)]` attribute"
209 kind_slugs
.push((kind
, slug
, no_span
));
215 /// Generates the code for a field with no attributes.
216 fn generate_field_set_arg(&mut self, binding_info
: &BindingInfo
<'_
>) -> TokenStream
{
217 let diag
= &self.parent
.diag
;
219 let field
= binding_info
.ast();
220 let mut field_binding
= binding_info
.binding
.clone();
221 field_binding
.set_span(field
.ty
.span());
223 let ident
= field
.ident
.as_ref().unwrap();
224 let ident
= format_ident
!("{}", ident
); // strip `r#` prefix, if present
234 /// Generates the necessary code for all attributes on a field.
235 fn generate_field_attr_code(
237 binding
: &BindingInfo
<'_
>,
238 kind_stats
: KindsStatistics
,
240 let ast
= binding
.ast();
241 assert
!(ast
.attrs
.len() > 0, "field without attributes generating attr code");
243 // Abstract over `Vec<T>` and `Option<T>` fields using `FieldInnerTy`, which will
244 // apply the generated code on each element in the `Vec` or `Option`.
245 let inner_ty
= FieldInnerTy
::from_type(&ast
.ty
);
249 // Always allow documentation comments.
250 if is_doc_comment(attr
) {
254 let info
= FieldInfo { binding, ty: inner_ty, span: &ast.span() }
;
257 .generate_field_code_inner(kind_stats
, attr
, info
, inner_ty
.will_iterate())
258 .unwrap_or_else(|v
| v
.to_compile_error());
260 inner_ty
.with(binding
, generated
)
265 fn generate_field_code_inner(
267 kind_stats
: KindsStatistics
,
270 clone_suggestion_code
: bool
,
271 ) -> Result
<TokenStream
, DiagnosticDeriveError
> {
273 Meta
::Path(path
) => {
274 self.generate_field_code_inner_path(kind_stats
, attr
, info
, path
.clone())
276 Meta
::List(list
) => self.generate_field_code_inner_list(
281 clone_suggestion_code
,
283 _
=> throw_invalid_attr
!(attr
),
287 /// Generates the code for a `[Meta::Path]`-like attribute on a field (e.g. `#[primary_span]`).
288 fn generate_field_code_inner_path(
290 kind_stats
: KindsStatistics
,
294 ) -> Result
<TokenStream
, DiagnosticDeriveError
> {
295 let span
= attr
.span().unwrap();
296 let ident
= &path
.segments
.last().unwrap().ident
;
297 let name
= ident
.to_string();
298 let name
= name
.as_str();
301 "skip_arg" => Ok(quote
! {}
),
303 if kind_stats
.has_multipart_suggestion
{
306 "multipart suggestions use one or more `#[suggestion_part]`s rather \
307 than one `#[primary_span]`",
311 report_error_if_not_applied_to_span(attr
, &info
)?
;
313 let binding
= info
.binding
.binding
.clone();
314 // FIXME(#100717): support `Option<Span>` on `primary_span` like in the
316 if !matches
!(info
.ty
, FieldInnerTy
::Plain(_
)) {
317 throw_invalid_attr
!(attr
, |diag
| {
318 let diag
= diag
.note("there must be exactly one primary span");
320 if kind_stats
.has_normal_suggestion
{
322 "to create a suggestion with multiple spans, \
323 use `#[multipart_suggestion]` instead",
331 self.span_field
.set_once(binding
, span
);
336 "suggestion_part" => {
337 self.has_suggestion_parts
= true;
339 if kind_stats
.has_multipart_suggestion
{
340 span_err(span
, "`#[suggestion_part(...)]` attribute without `code = \"...\"`")
345 "`#[suggestion_part(...)]` is only valid in multipart suggestions, \
346 use `#[primary_span]` instead",
354 if kind_stats
.has_multipart_suggestion
|| kind_stats
.has_normal_suggestion
{
355 report_error_if_not_applied_to_applicability(attr
, &info
)?
;
357 if kind_stats
.all_applicabilities_static
{
360 "`#[applicability]` has no effect if all `#[suggestion]`/\
361 `#[multipart_suggestion]` attributes have a static \
362 `applicability = \"...\"`",
366 let binding
= info
.binding
.binding
.clone();
367 self.applicability
.set_once(quote
! { #binding }
, span
);
369 span_err(span
, "`#[applicability]` is only valid on suggestions").emit();
375 let mut span_attrs
= vec
![];
376 if kind_stats
.has_multipart_suggestion
{
377 span_attrs
.push("suggestion_part");
379 if !kind_stats
.all_multipart_suggestions
{
380 span_attrs
.push("primary_span")
385 "only `{}`, `applicability` and `skip_arg` are valid field attributes",
386 span_attrs
.join(", ")
395 /// Generates the code for a `[Meta::List]`-like attribute on a field (e.g.
396 /// `#[suggestion_part(code = "...")]`).
397 fn generate_field_code_inner_list(
399 kind_stats
: KindsStatistics
,
403 clone_suggestion_code
: bool
,
404 ) -> Result
<TokenStream
, DiagnosticDeriveError
> {
405 let span
= attr
.span().unwrap();
406 let mut ident
= list
.path
.segments
.last().unwrap().ident
.clone();
407 ident
.set_span(info
.ty
.span());
408 let name
= ident
.to_string();
409 let name
= name
.as_str();
412 "suggestion_part" => {
413 if !kind_stats
.has_multipart_suggestion
{
414 throw_invalid_attr
!(attr
, |diag
| {
416 "`#[suggestion_part(...)]` is only valid in multipart suggestions",
421 self.has_suggestion_parts
= true;
423 report_error_if_not_applied_to_span(attr
, &info
)?
;
427 list
.parse_nested_meta(|nested
| {
428 if nested
.path
.is_ident("code") {
429 let code_field
= new_code_ident();
430 let span
= nested
.path
.span().unwrap();
431 let formatting_init
= build_suggestion_code(
435 AllowMultipleAlternatives
::No
,
437 code
.set_once((code_field
, formatting_init
), span
);
440 nested
.path
.span().unwrap(),
441 "`code` is the only valid nested attribute",
448 let Some((code_field
, formatting_init
)) = code
.value() else {
449 span_err(span
, "`#[suggestion_part(...)]` attribute without `code = \"...\"`")
451 return Ok(quote
! {}
);
453 let binding
= info
.binding
;
455 self.formatting_init
.extend(formatting_init
);
456 let code_field
= if clone_suggestion_code
{
457 quote
! { #code_field.clone() }
459 quote
! { #code_field }
461 Ok(quote
! { suggestions.push((#binding, #code_field)); }
)
463 _
=> throw_invalid_attr
!(attr
, |diag
| {
464 let mut span_attrs
= vec
![];
465 if kind_stats
.has_multipart_suggestion
{
466 span_attrs
.push("suggestion_part");
468 if !kind_stats
.all_multipart_suggestions
{
469 span_attrs
.push("primary_span")
472 "only `{}`, `applicability` and `skip_arg` are valid field attributes",
473 span_attrs
.join(", ")
479 pub fn into_tokens(&mut self) -> Result
<TokenStream
, DiagnosticDeriveError
> {
480 let kind_slugs
= self.identify_kind()?
;
481 if kind_slugs
.is_empty() {
483 // It's okay for a variant to not be a subdiagnostic at all..
484 return Ok(quote
! {}
);
486 // ..but structs should always be _something_.
488 self.variant
.ast().ident
.span().unwrap(),
489 "subdiagnostic kind not specified"
494 let kind_stats
: KindsStatistics
=
495 kind_slugs
.iter().map(|(kind
, _slug
, _no_span
)| kind
).collect();
497 let init
= if kind_stats
.has_multipart_suggestion
{
498 quote
! { let mut suggestions = Vec::new(); }
503 let attr_args
: TokenStream
= self
507 .filter(|binding
| !should_generate_set_arg(binding
.ast()))
508 .map(|binding
| self.generate_field_attr_code(binding
, kind_stats
))
511 let span_field
= self.span_field
.value_ref();
513 let diag
= &self.parent
.diag
;
514 let f
= &self.parent
.f
;
515 let mut calls
= TokenStream
::new();
516 for (kind
, slug
, no_span
) in kind_slugs
{
517 let message
= format_ident
!("__message");
519 quote
! { let #message = #f(#diag, crate::fluent_generated::#slug.into()); }
,
522 let name
= format_ident
!(
524 if span_field
.is_some() && !no_span { "span_" }
else { "" }
,
527 let call
= match kind
{
528 SubdiagnosticKind
::Suggestion
{
534 self.formatting_init
.extend(code_init
);
536 let applicability
= applicability
538 .map(|a
| quote
! { #a }
)
539 .or_else(|| self.applicability
.take().value())
540 .unwrap_or_else(|| quote
! { rustc_errors::Applicability::Unspecified }
);
542 if let Some(span
) = span_field
{
543 let style
= suggestion_kind
.to_suggestion_style();
544 quote
! { #diag.#name(#span, #message, #code_field, #applicability, #style); }
546 span_err(self.span
, "suggestion without `#[primary_span]` field").emit();
547 quote
! { unreachable!(); }
550 SubdiagnosticKind
::MultipartSuggestion { suggestion_kind, applicability }
=> {
551 let applicability
= applicability
553 .map(|a
| quote
! { #a }
)
554 .or_else(|| self.applicability
.take().value())
555 .unwrap_or_else(|| quote
! { rustc_errors::Applicability::Unspecified }
);
557 if !self.has_suggestion_parts
{
560 "multipart suggestion without any `#[suggestion_part(...)]` fields",
565 let style
= suggestion_kind
.to_suggestion_style();
567 quote
! { #diag.#name(#message, suggestions, #applicability, #style); }
569 SubdiagnosticKind
::Label
=> {
570 if let Some(span
) = span_field
{
571 quote
! { #diag.#name(#span, #message); }
573 span_err(self.span
, "label without `#[primary_span]` field").emit();
574 quote
! { unreachable!(); }
578 if let Some(span
) = span_field
&& !no_span
{
579 quote
! { #diag.#name(#span, #message); }
581 quote
! { #diag.#name(#message); }
589 let plain_args
: TokenStream
= self
593 .filter(|binding
| should_generate_set_arg(binding
.ast()))
594 .map(|binding
| self.generate_field_set_arg(binding
))
597 let formatting_init
= &self.formatting_init
;