1 #![deny(unused_must_use)]
3 use crate::diagnostics
::error
::{
4 invalid_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
, new_code_ident
,
9 report_error_if_not_applied_to_applicability
, report_error_if_not_applied_to_span
, FieldInfo
,
10 FieldInnerTy
, FieldMap
, HasFieldMap
, SetOnce
, SpannedOption
, SubdiagnosticKind
,
12 use proc_macro2
::TokenStream
;
13 use quote
::{format_ident, quote}
;
14 use syn
::{spanned::Spanned, Attribute, Meta, MetaList, NestedMeta, Path}
;
15 use synstructure
::{BindingInfo, Structure, VariantInfo}
;
17 use super::utils
::{build_suggestion_code, AllowMultipleAlternatives}
;
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
<'a
>(self, mut structure
: Structure
<'a
>) -> 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",
46 let is_enum
= matches
!(ast
.data
, syn
::Data
::Enum(..));
48 for attr
in &ast
.attrs
{
49 // Always allow documentation comments.
50 if is_doc_comment(attr
) {
56 "unsupported type attribute for subdiagnostic enum",
62 structure
.bind_with(|_
| synstructure
::BindStyle
::Move
);
63 let variants_
= structure
.each_variant(|variant
| {
64 let mut builder
= SubdiagnosticDeriveVariantBuilder
{
68 formatting_init
: TokenStream
::new(),
69 fields
: build_field_mapping(variant
),
72 has_suggestion_parts
: false,
75 builder
.into_tokens().unwrap_or_else(|v
| v
.to_compile_error())
85 let diag
= &self.diag
;
87 let ret
= structure
.gen_impl(quote
! {
88 gen
impl rustc_errors
::AddToDiagnostic
for @
Self {
89 fn add_to_diagnostic_with
<__F
>(self, #diag: &mut rustc_errors::Diagnostic, #f: __F)
92 &mut rustc_errors
::Diagnostic
,
93 rustc_errors
::SubdiagnosticMessage
94 ) -> rustc_errors
::SubdiagnosticMessage
,
96 use rustc_errors
::{Applicability, IntoDiagnosticArg}
;
105 /// Tracks persistent information required for building up the call to add to the diagnostic
106 /// for the final generated method. This is a separate struct to `SubdiagnosticDerive`
107 /// only to be able to destructure and split `self.builder` and the `self.structure` up to avoid a
108 /// double mut borrow later on.
109 struct SubdiagnosticDeriveVariantBuilder
<'parent
, 'a
> {
110 /// The identifier to use for the generated `DiagnosticBuilder` instance.
111 parent
: &'parent SubdiagnosticDeriveBuilder
,
113 /// Info for the current variant (or the type if not an enum).
114 variant
: &'a VariantInfo
<'a
>,
115 /// Span for the entire type.
116 span
: proc_macro
::Span
,
118 /// Initialization of format strings for code suggestions.
119 formatting_init
: TokenStream
,
121 /// Store a map of field name to its corresponding field. This is built on construction of the
125 /// Identifier for the binding to the `#[primary_span]` field.
126 span_field
: SpannedOption
<proc_macro2
::Ident
>,
128 /// The binding to the `#[applicability]` field, if present.
129 applicability
: SpannedOption
<TokenStream
>,
131 /// Set to true when a `#[suggestion_part]` field is encountered, used to generate an error
132 /// during finalization if still `false`.
133 has_suggestion_parts
: bool
,
135 /// Set to true when this variant is an enum variant rather than just the body of a struct.
139 impl<'parent
, 'a
> HasFieldMap
for SubdiagnosticDeriveVariantBuilder
<'parent
, 'a
> {
140 fn get_field_binding(&self, field
: &String
) -> Option
<&TokenStream
> {
141 self.fields
.get(field
)
145 /// Provides frequently-needed information about the diagnostic kinds being derived for this type.
146 #[derive(Clone, Copy, Debug)]
147 struct KindsStatistics
{
148 has_multipart_suggestion
: bool
,
149 all_multipart_suggestions
: bool
,
150 has_normal_suggestion
: bool
,
151 all_applicabilities_static
: bool
,
154 impl<'a
> FromIterator
<&'a SubdiagnosticKind
> for KindsStatistics
{
155 fn from_iter
<T
: IntoIterator
<Item
= &'a SubdiagnosticKind
>>(kinds
: T
) -> Self {
157 has_multipart_suggestion
: false,
158 all_multipart_suggestions
: true,
159 has_normal_suggestion
: false,
160 all_applicabilities_static
: true,
164 if let SubdiagnosticKind
::MultipartSuggestion { applicability: None, .. }
165 | SubdiagnosticKind
::Suggestion { applicability: None, .. }
= kind
167 ret
.all_applicabilities_static
= false;
169 if let SubdiagnosticKind
::MultipartSuggestion { .. }
= kind
{
170 ret
.has_multipart_suggestion
= true;
172 ret
.all_multipart_suggestions
= false;
175 if let SubdiagnosticKind
::Suggestion { .. }
= kind
{
176 ret
.has_normal_suggestion
= true;
183 impl<'parent
, 'a
> SubdiagnosticDeriveVariantBuilder
<'parent
, 'a
> {
184 fn identify_kind(&mut self) -> Result
<Vec
<(SubdiagnosticKind
, Path
)>, DiagnosticDeriveError
> {
185 let mut kind_slugs
= vec
![];
187 for attr
in self.variant
.ast().attrs
{
188 let Some((kind
, slug
)) = SubdiagnosticKind
::from_attr(attr
, self)?
else {
189 // Some attributes aren't errors - like documentation comments - but also aren't
194 let Some(slug
) = slug
else {
195 let name
= attr
.path
.segments
.last().unwrap().ident
.to_string();
196 let name
= name
.as_str();
199 attr
.span().unwrap(),
201 "diagnostic slug must be first argument of a `#[{}(...)]` attribute",
207 kind_slugs
.push((kind
, slug
));
213 /// Generates the code for a field with no attributes.
214 fn generate_field_set_arg(&mut self, binding
: &BindingInfo
<'_
>) -> TokenStream
{
215 let ast
= binding
.ast();
216 assert_eq
!(ast
.attrs
.len(), 0, "field with attribute used as diagnostic arg");
218 let diag
= &self.parent
.diag
;
219 let ident
= ast
.ident
.as_ref().unwrap();
220 // strip `r#` prefix, if present
221 let ident
= format_ident
!("{}", ident
);
231 /// Generates the necessary code for all attributes on a field.
232 fn generate_field_attr_code(
234 binding
: &BindingInfo
<'_
>,
235 kind_stats
: KindsStatistics
,
237 let ast
= binding
.ast();
238 assert
!(ast
.attrs
.len() > 0, "field without attributes generating attr code");
240 // Abstract over `Vec<T>` and `Option<T>` fields using `FieldInnerTy`, which will
241 // apply the generated code on each element in the `Vec` or `Option`.
242 let inner_ty
= FieldInnerTy
::from_type(&ast
.ty
);
246 // Always allow documentation comments.
247 if is_doc_comment(attr
) {
251 let info
= FieldInfo
{
253 ty
: inner_ty
.inner_type().unwrap_or(&ast
.ty
),
258 .generate_field_code_inner(kind_stats
, attr
, info
, inner_ty
.will_iterate())
259 .unwrap_or_else(|v
| v
.to_compile_error());
261 inner_ty
.with(binding
, generated
)
266 fn generate_field_code_inner(
268 kind_stats
: KindsStatistics
,
271 clone_suggestion_code
: bool
,
272 ) -> Result
<TokenStream
, DiagnosticDeriveError
> {
273 let meta
= attr
.parse_meta()?
;
275 Meta
::Path(path
) => self.generate_field_code_inner_path(kind_stats
, attr
, info
, path
),
276 Meta
::List(list @ MetaList { .. }
) => self.generate_field_code_inner_list(
281 clone_suggestion_code
,
283 _
=> throw_invalid_attr
!(attr
, &meta
),
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
{
304 invalid_attr(attr
, &Meta
::Path(path
))
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 self.span_field
.set_once(binding
, span
);
321 "suggestion_part" => {
322 self.has_suggestion_parts
= true;
324 if kind_stats
.has_multipart_suggestion
{
325 span_err(span
, "`#[suggestion_part(...)]` attribute without `code = \"...\"`")
328 invalid_attr(attr
, &Meta
::Path(path
))
330 "`#[suggestion_part(...)]` is only valid in multipart suggestions, \
331 use `#[primary_span]` instead",
339 if kind_stats
.has_multipart_suggestion
|| kind_stats
.has_normal_suggestion
{
340 report_error_if_not_applied_to_applicability(attr
, &info
)?
;
342 if kind_stats
.all_applicabilities_static
{
345 "`#[applicability]` has no effect if all `#[suggestion]`/\
346 `#[multipart_suggestion]` attributes have a static \
347 `applicability = \"...\"`",
351 let binding
= info
.binding
.binding
.clone();
352 self.applicability
.set_once(quote
! { #binding }
, span
);
354 span_err(span
, "`#[applicability]` is only valid on suggestions").emit();
360 let mut span_attrs
= vec
![];
361 if kind_stats
.has_multipart_suggestion
{
362 span_attrs
.push("suggestion_part");
364 if !kind_stats
.all_multipart_suggestions
{
365 span_attrs
.push("primary_span")
368 invalid_attr(attr
, &Meta
::Path(path
))
370 "only `{}`, `applicability` and `skip_arg` are valid field attributes",
371 span_attrs
.join(", ")
380 /// Generates the code for a `[Meta::List]`-like attribute on a field (e.g.
381 /// `#[suggestion_part(code = "...")]`).
382 fn generate_field_code_inner_list(
384 kind_stats
: KindsStatistics
,
388 clone_suggestion_code
: bool
,
389 ) -> Result
<TokenStream
, DiagnosticDeriveError
> {
390 let span
= attr
.span().unwrap();
391 let ident
= &list
.path
.segments
.last().unwrap().ident
;
392 let name
= ident
.to_string();
393 let name
= name
.as_str();
396 "suggestion_part" => {
397 if !kind_stats
.has_multipart_suggestion
{
398 throw_invalid_attr
!(attr
, &Meta
::List(list
), |diag
| {
400 "`#[suggestion_part(...)]` is only valid in multipart suggestions",
405 self.has_suggestion_parts
= true;
407 report_error_if_not_applied_to_span(attr
, &info
)?
;
410 for nested_attr
in list
.nested
.iter() {
411 let NestedMeta
::Meta(ref meta
) = nested_attr
else {
412 throw_invalid_nested_attr
!(attr
, nested_attr
);
415 let span
= meta
.span().unwrap();
416 let nested_name
= meta
.path().segments
.last().unwrap().ident
.to_string();
417 let nested_name
= nested_name
.as_str();
421 let code_field
= new_code_ident();
422 let formatting_init
= build_suggestion_code(
426 AllowMultipleAlternatives
::No
,
428 code
.set_once((code_field
, formatting_init
), span
);
430 _
=> throw_invalid_nested_attr
!(attr
, nested_attr
, |diag
| {
431 diag
.help("`code` is the only valid nested attribute")
436 let Some((code_field
, formatting_init
)) = code
.value() else {
437 span_err(span
, "`#[suggestion_part(...)]` attribute without `code = \"...\"`")
439 return Ok(quote
! {}
);
441 let binding
= info
.binding
;
443 self.formatting_init
.extend(formatting_init
);
444 let code_field
= if clone_suggestion_code
{
445 quote
! { #code_field.clone() }
447 quote
! { #code_field }
449 Ok(quote
! { suggestions.push((#binding, #code_field)); }
)
451 _
=> throw_invalid_attr
!(attr
, &Meta
::List(list
), |diag
| {
452 let mut span_attrs
= vec
![];
453 if kind_stats
.has_multipart_suggestion
{
454 span_attrs
.push("suggestion_part");
456 if !kind_stats
.all_multipart_suggestions
{
457 span_attrs
.push("primary_span")
460 "only `{}`, `applicability` and `skip_arg` are valid field attributes",
461 span_attrs
.join(", ")
467 pub fn into_tokens(&mut self) -> Result
<TokenStream
, DiagnosticDeriveError
> {
468 let kind_slugs
= self.identify_kind()?
;
469 if kind_slugs
.is_empty() {
471 // It's okay for a variant to not be a subdiagnostic at all..
472 return Ok(quote
! {}
);
474 // ..but structs should always be _something_.
476 self.variant
.ast().ident
.span().unwrap(),
477 "subdiagnostic kind not specified"
482 let kind_stats
: KindsStatistics
= kind_slugs
.iter().map(|(kind
, _slug
)| kind
).collect();
484 let init
= if kind_stats
.has_multipart_suggestion
{
485 quote
! { let mut suggestions = Vec::new(); }
490 let attr_args
: TokenStream
= self
494 .filter(|binding
| !binding
.ast().attrs
.is_empty())
495 .map(|binding
| self.generate_field_attr_code(binding
, kind_stats
))
498 let span_field
= self.span_field
.value_ref();
500 let diag
= &self.parent
.diag
;
501 let f
= &self.parent
.f
;
502 let mut calls
= TokenStream
::new();
503 for (kind
, slug
) in kind_slugs
{
504 let message
= format_ident
!("__message");
505 calls
.extend(quote
! { let #message = #f(#diag, rustc_errors::fluent::#slug.into()); }
);
507 let name
= format_ident
!("{}{}", if span_field
.is_some() { "span_" }
else { "" }
, kind
);
508 let call
= match kind
{
509 SubdiagnosticKind
::Suggestion
{
515 self.formatting_init
.extend(code_init
);
517 let applicability
= applicability
519 .map(|a
| quote
! { #a }
)
520 .or_else(|| self.applicability
.take().value())
521 .unwrap_or_else(|| quote
! { rustc_errors::Applicability::Unspecified }
);
523 if let Some(span
) = span_field
{
524 let style
= suggestion_kind
.to_suggestion_style();
525 quote
! { #diag.#name(#span, #message, #code_field, #applicability, #style); }
527 span_err(self.span
, "suggestion without `#[primary_span]` field").emit();
528 quote
! { unreachable!(); }
531 SubdiagnosticKind
::MultipartSuggestion { suggestion_kind, applicability }
=> {
532 let applicability
= applicability
534 .map(|a
| quote
! { #a }
)
535 .or_else(|| self.applicability
.take().value())
536 .unwrap_or_else(|| quote
! { rustc_errors::Applicability::Unspecified }
);
538 if !self.has_suggestion_parts
{
541 "multipart suggestion without any `#[suggestion_part(...)]` fields",
546 let style
= suggestion_kind
.to_suggestion_style();
548 quote
! { #diag.#name(#message, suggestions, #applicability, #style); }
550 SubdiagnosticKind
::Label
=> {
551 if let Some(span
) = span_field
{
552 quote
! { #diag.#name(#span, #message); }
554 span_err(self.span
, "label without `#[primary_span]` field").emit();
555 quote
! { unreachable!(); }
559 if let Some(span
) = span_field
{
560 quote
! { #diag.#name(#span, #message); }
562 quote
! { #diag.#name(#message); }
570 let plain_args
: TokenStream
= self
574 .filter(|binding
| binding
.ast().attrs
.is_empty())
575 .map(|binding
| self.generate_field_set_arg(binding
))
578 let formatting_init
= &self.formatting_init
;