]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_macros/src/diagnostics/diagnostic.rs
Rollup merge of #98428 - davidtwco:translation-derive-typed-identifiers, r=oli-obk
[rust.git] / compiler / rustc_macros / src / diagnostics / diagnostic.rs
1 #![deny(unused_must_use)]
2
3 use crate::diagnostics::error::{
4     invalid_nested_attr, span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err,
5     SessionDiagnosticDeriveError,
6 };
7 use crate::diagnostics::utils::{
8     report_error_if_not_applied_to_span, report_type_error, type_is_unit, type_matches_path,
9     Applicability, FieldInfo, FieldInnerTy, HasFieldMap, SetOnce,
10 };
11 use proc_macro2::{Ident, TokenStream};
12 use quote::{format_ident, quote};
13 use std::collections::HashMap;
14 use std::str::FromStr;
15 use syn::{
16     parse_quote, spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, NestedMeta, Path, Type,
17 };
18 use synstructure::{BindingInfo, Structure};
19
20 /// The central struct for constructing the `into_diagnostic` method from an annotated struct.
21 pub(crate) struct SessionDiagnosticDerive<'a> {
22     structure: Structure<'a>,
23     builder: SessionDiagnosticDeriveBuilder,
24 }
25
26 impl<'a> SessionDiagnosticDerive<'a> {
27     pub(crate) fn new(diag: syn::Ident, sess: syn::Ident, structure: Structure<'a>) -> Self {
28         // Build the mapping of field names to fields. This allows attributes to peek values from
29         // other fields.
30         let mut fields_map = HashMap::new();
31
32         // Convenience bindings.
33         let ast = structure.ast();
34
35         if let syn::Data::Struct(syn::DataStruct { fields, .. }) = &ast.data {
36             for field in fields.iter() {
37                 if let Some(ident) = &field.ident {
38                     fields_map.insert(ident.to_string(), quote! { &self.#ident });
39                 }
40             }
41         }
42
43         Self {
44             builder: SessionDiagnosticDeriveBuilder {
45                 diag,
46                 sess,
47                 fields: fields_map,
48                 kind: None,
49                 code: None,
50                 slug: None,
51             },
52             structure,
53         }
54     }
55
56     pub(crate) fn into_tokens(self) -> TokenStream {
57         let SessionDiagnosticDerive { mut structure, mut builder } = self;
58
59         let ast = structure.ast();
60         let attrs = &ast.attrs;
61
62         let (implementation, param_ty) = {
63             if let syn::Data::Struct(..) = ast.data {
64                 let preamble = {
65                     let preamble = attrs.iter().map(|attr| {
66                         builder
67                             .generate_structure_code(attr)
68                             .unwrap_or_else(|v| v.to_compile_error())
69                     });
70
71                     quote! {
72                         #(#preamble)*;
73                     }
74                 };
75
76                 // Keep track of which fields are subdiagnostics or have no attributes.
77                 let mut subdiagnostics_or_empty = std::collections::HashSet::new();
78
79                 // Generates calls to `span_label` and similar functions based on the attributes
80                 // on fields. Code for suggestions uses formatting machinery and the value of
81                 // other fields - because any given field can be referenced multiple times, it
82                 // should be accessed through a borrow. When passing fields to `add_subdiagnostic`
83                 // or `set_arg` (which happens below) for Fluent, we want to move the data, so that
84                 // has to happen in a separate pass over the fields.
85                 let attrs = structure
86                     .clone()
87                     .filter(|field_binding| {
88                         let attrs = &field_binding.ast().attrs;
89
90                         (!attrs.is_empty()
91                             && attrs.iter().all(|attr| {
92                                 "subdiagnostic"
93                                     != attr.path.segments.last().unwrap().ident.to_string()
94                             }))
95                             || {
96                                 subdiagnostics_or_empty.insert(field_binding.binding.clone());
97                                 false
98                             }
99                     })
100                     .each(|field_binding| builder.generate_field_attrs_code(field_binding));
101
102                 structure.bind_with(|_| synstructure::BindStyle::Move);
103                 // When a field has attributes like `#[label]` or `#[note]` then it doesn't
104                 // need to be passed as an argument to the diagnostic. But when a field has no
105                 // attributes or a `#[subdiagnostic]` attribute then it must be passed as an
106                 // argument to the diagnostic so that it can be referred to by Fluent messages.
107                 let args = structure
108                     .filter(|field_binding| {
109                         subdiagnostics_or_empty.contains(&field_binding.binding)
110                     })
111                     .each(|field_binding| builder.generate_field_attrs_code(field_binding));
112
113                 let span = ast.span().unwrap();
114                 let (diag, sess) = (&builder.diag, &builder.sess);
115                 let init = match (builder.kind, builder.slug) {
116                     (None, _) => {
117                         span_err(span, "diagnostic kind not specified")
118                             .help("use the `#[error(...)]` attribute to create an error")
119                             .emit();
120                         return SessionDiagnosticDeriveError::ErrorHandled.to_compile_error();
121                     }
122                     (Some((kind, _)), None) => {
123                         span_err(span, "diagnostic slug not specified")
124                             .help(&format!(
125                                 "specify the slug as the first argument to the attribute, such as \
126                                  `#[{}(typeck::example_error)]`",
127                                 kind.descr()
128                             ))
129                             .emit();
130                         return SessionDiagnosticDeriveError::ErrorHandled.to_compile_error();
131                     }
132                     (Some((SessionDiagnosticKind::Error, _)), Some((slug, _))) => {
133                         quote! {
134                             let mut #diag = #sess.struct_err(rustc_errors::fluent::#slug);
135                         }
136                     }
137                     (Some((SessionDiagnosticKind::Warn, _)), Some((slug, _))) => {
138                         quote! {
139                             let mut #diag = #sess.struct_warn(rustc_errors::fluent::#slug);
140                         }
141                     }
142                 };
143
144                 let implementation = quote! {
145                     #init
146                     #preamble
147                     match self {
148                         #attrs
149                     }
150                     match self {
151                         #args
152                     }
153                     #diag
154                 };
155                 let param_ty = match builder.kind {
156                     Some((SessionDiagnosticKind::Error, _)) => {
157                         quote! { rustc_errors::ErrorGuaranteed }
158                     }
159                     Some((SessionDiagnosticKind::Warn, _)) => quote! { () },
160                     _ => unreachable!(),
161                 };
162
163                 (implementation, param_ty)
164             } else {
165                 span_err(
166                     ast.span().unwrap(),
167                     "`#[derive(SessionDiagnostic)]` can only be used on structs",
168                 )
169                 .emit();
170
171                 let implementation = SessionDiagnosticDeriveError::ErrorHandled.to_compile_error();
172                 let param_ty = quote! { rustc_errors::ErrorGuaranteed };
173                 (implementation, param_ty)
174             }
175         };
176
177         let sess = &builder.sess;
178         structure.gen_impl(quote! {
179             gen impl<'__session_diagnostic_sess> rustc_session::SessionDiagnostic<'__session_diagnostic_sess, #param_ty>
180                     for @Self
181             {
182                 fn into_diagnostic(
183                     self,
184                     #sess: &'__session_diagnostic_sess rustc_session::parse::ParseSess
185                 ) -> rustc_errors::DiagnosticBuilder<'__session_diagnostic_sess, #param_ty> {
186                     use rustc_errors::IntoDiagnosticArg;
187                     #implementation
188                 }
189             }
190         })
191     }
192 }
193
194 /// What kind of session diagnostic is being derived - an error or a warning?
195 #[derive(Copy, Clone)]
196 enum SessionDiagnosticKind {
197     /// `#[error(..)]`
198     Error,
199     /// `#[warn(..)]`
200     Warn,
201 }
202
203 impl SessionDiagnosticKind {
204     /// Returns human-readable string corresponding to the kind.
205     fn descr(&self) -> &'static str {
206         match self {
207             SessionDiagnosticKind::Error => "error",
208             SessionDiagnosticKind::Warn => "warning",
209         }
210     }
211 }
212
213 /// Tracks persistent information required for building up the individual calls to diagnostic
214 /// methods for the final generated method. This is a separate struct to `SessionDiagnosticDerive`
215 /// only to be able to destructure and split `self.builder` and the `self.structure` up to avoid a
216 /// double mut borrow later on.
217 struct SessionDiagnosticDeriveBuilder {
218     /// Name of the session parameter that's passed in to the `as_error` method.
219     sess: syn::Ident,
220     /// The identifier to use for the generated `DiagnosticBuilder` instance.
221     diag: syn::Ident,
222
223     /// Store a map of field name to its corresponding field. This is built on construction of the
224     /// derive builder.
225     fields: HashMap<String, TokenStream>,
226
227     /// Kind of diagnostic requested via the struct attribute.
228     kind: Option<(SessionDiagnosticKind, proc_macro::Span)>,
229     /// Slug is a mandatory part of the struct attribute as corresponds to the Fluent message that
230     /// has the actual diagnostic message.
231     slug: Option<(Path, proc_macro::Span)>,
232     /// Error codes are a optional part of the struct attribute - this is only set to detect
233     /// multiple specifications.
234     code: Option<(String, proc_macro::Span)>,
235 }
236
237 impl HasFieldMap for SessionDiagnosticDeriveBuilder {
238     fn get_field_binding(&self, field: &String) -> Option<&TokenStream> {
239         self.fields.get(field)
240     }
241 }
242
243 impl SessionDiagnosticDeriveBuilder {
244     /// Establishes state in the `SessionDiagnosticDeriveBuilder` resulting from the struct
245     /// attributes like `#[error(..)`, such as the diagnostic kind and slug. Generates
246     /// diagnostic builder calls for setting error code and creating note/help messages.
247     fn generate_structure_code(
248         &mut self,
249         attr: &Attribute,
250     ) -> Result<TokenStream, SessionDiagnosticDeriveError> {
251         let diag = &self.diag;
252         let span = attr.span().unwrap();
253
254         let name = attr.path.segments.last().unwrap().ident.to_string();
255         let name = name.as_str();
256         let meta = attr.parse_meta()?;
257
258         let is_help_or_note = matches!(name, "help" | "note");
259
260         let nested = match meta {
261             // Most attributes are lists, like `#[error(..)]`/`#[warning(..)]` for most cases or
262             // `#[help(..)]`/`#[note(..)]` when the user is specifying a alternative slug.
263             Meta::List(MetaList { ref nested, .. }) => nested,
264             // Subdiagnostics without spans can be applied to the type too, and these are just
265             // paths: `#[help]` and `#[note]`
266             Meta::Path(_) if is_help_or_note => {
267                 let fn_name = proc_macro2::Ident::new(name, attr.span());
268                 return Ok(quote! { #diag.#fn_name(rustc_errors::fluent::_subdiag::#fn_name); });
269             }
270             _ => throw_invalid_attr!(attr, &meta),
271         };
272
273         // Check the kind before doing any further processing so that there aren't misleading
274         // "no kind specified" errors if there are failures later.
275         match name {
276             "error" => self.kind.set_once((SessionDiagnosticKind::Error, span)),
277             "warning" => self.kind.set_once((SessionDiagnosticKind::Warn, span)),
278             "help" | "note" => (),
279             _ => throw_invalid_attr!(attr, &meta, |diag| {
280                 diag.help("only `error`, `warning`, `help` and `note` are valid attributes")
281             }),
282         }
283
284         // First nested element should always be the path, e.g. `#[error(typeck::invalid)]` or
285         // `#[help(typeck::another_help)]`.
286         let mut nested_iter = nested.into_iter();
287         if let Some(nested_attr) = nested_iter.next() {
288             // Report an error if there are any other list items after the path.
289             if is_help_or_note && nested_iter.next().is_some() {
290                 throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
291                     diag.help("`help` and `note` struct attributes can only have one argument")
292                 });
293             }
294
295             match nested_attr {
296                 NestedMeta::Meta(Meta::Path(path)) if is_help_or_note => {
297                     let fn_name = proc_macro2::Ident::new(name, attr.span());
298                     return Ok(quote! { #diag.#fn_name(rustc_errors::fluent::#path); });
299                 }
300                 NestedMeta::Meta(Meta::Path(path)) => {
301                     self.slug.set_once((path.clone(), span));
302                 }
303                 NestedMeta::Meta(meta @ Meta::NameValue(_))
304                     if !is_help_or_note
305                         && meta.path().segments.last().unwrap().ident.to_string() == "code" =>
306                 {
307                     // don't error for valid follow-up attributes
308                 }
309                 nested_attr => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
310                     diag.help("first argument of the attribute should be the diagnostic slug")
311                 }),
312             };
313         }
314
315         // Remaining attributes are optional, only `code = ".."` at the moment.
316         let mut tokens = Vec::new();
317         for nested_attr in nested_iter {
318             let meta = match nested_attr {
319                 syn::NestedMeta::Meta(meta) => meta,
320                 _ => throw_invalid_nested_attr!(attr, &nested_attr),
321             };
322
323             let path = meta.path();
324             let nested_name = path.segments.last().unwrap().ident.to_string();
325             // Struct attributes are only allowed to be applied once, and the diagnostic
326             // changes will be set in the initialisation code.
327             if let Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) = &meta {
328                 let span = s.span().unwrap();
329                 match nested_name.as_str() {
330                     "code" => {
331                         self.code.set_once((s.value(), span));
332                         let code = &self.code.as_ref().map(|(v, _)| v);
333                         tokens.push(quote! {
334                             #diag.code(rustc_errors::DiagnosticId::Error(#code.to_string()));
335                         });
336                     }
337                     _ => invalid_nested_attr(attr, &nested_attr)
338                         .help("only `code` is a valid nested attributes following the slug")
339                         .emit(),
340                 }
341             } else {
342                 invalid_nested_attr(attr, &nested_attr).emit()
343             }
344         }
345
346         Ok(tokens.drain(..).collect())
347     }
348
349     fn generate_field_attrs_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream {
350         let field = binding_info.ast();
351         let field_binding = &binding_info.binding;
352
353         let inner_ty = FieldInnerTy::from_type(&field.ty);
354
355         // When generating `set_arg` or `add_subdiagnostic` calls, move data rather than
356         // borrow it to avoid requiring clones - this must therefore be the last use of
357         // each field (for example, any formatting machinery that might refer to a field
358         // should be generated already).
359         if field.attrs.is_empty() {
360             let diag = &self.diag;
361             let ident = field.ident.as_ref().unwrap();
362             quote! {
363                 #diag.set_arg(
364                     stringify!(#ident),
365                     #field_binding
366                 );
367             }
368         } else {
369             field
370                 .attrs
371                 .iter()
372                 .map(move |attr| {
373                     let name = attr.path.segments.last().unwrap().ident.to_string();
374                     let (binding, needs_destructure) = match (name.as_str(), &inner_ty) {
375                         // `primary_span` can accept a `Vec<Span>` so don't destructure that.
376                         ("primary_span", FieldInnerTy::Vec(_)) => {
377                             (quote! { #field_binding.clone() }, false)
378                         }
379                         // `subdiagnostics` are not derefed because they are bound by value.
380                         ("subdiagnostic", _) => (quote! { #field_binding }, true),
381                         _ => (quote! { *#field_binding }, true),
382                     };
383
384                     let generated_code = self
385                         .generate_inner_field_code(
386                             attr,
387                             FieldInfo {
388                                 binding: binding_info,
389                                 ty: inner_ty.inner_type().unwrap_or(&field.ty),
390                                 span: &field.span(),
391                             },
392                             binding,
393                         )
394                         .unwrap_or_else(|v| v.to_compile_error());
395
396                     if needs_destructure {
397                         inner_ty.with(field_binding, generated_code)
398                     } else {
399                         generated_code
400                     }
401                 })
402                 .collect()
403         }
404     }
405
406     fn generate_inner_field_code(
407         &mut self,
408         attr: &Attribute,
409         info: FieldInfo<'_>,
410         binding: TokenStream,
411     ) -> Result<TokenStream, SessionDiagnosticDeriveError> {
412         let meta = attr.parse_meta()?;
413         match meta {
414             Meta::Path(_) => self.generate_inner_field_code_path(attr, info, binding),
415             Meta::List(MetaList { .. }) => self.generate_inner_field_code_list(attr, info, binding),
416             _ => throw_invalid_attr!(attr, &meta),
417         }
418     }
419
420     fn generate_inner_field_code_path(
421         &mut self,
422         attr: &Attribute,
423         info: FieldInfo<'_>,
424         binding: TokenStream,
425     ) -> Result<TokenStream, SessionDiagnosticDeriveError> {
426         assert!(matches!(attr.parse_meta()?, Meta::Path(_)));
427         let diag = &self.diag;
428
429         let meta = attr.parse_meta()?;
430
431         let ident = &attr.path.segments.last().unwrap().ident;
432         let name = ident.to_string();
433         let name = name.as_str();
434         match name {
435             "skip_arg" => {
436                 // Don't need to do anything - by virtue of the attribute existing, the
437                 // `set_arg` call will not be generated.
438                 Ok(quote! {})
439             }
440             "primary_span" => {
441                 report_error_if_not_applied_to_span(attr, &info)?;
442                 Ok(quote! {
443                     #diag.set_span(#binding);
444                 })
445             }
446             "label" => {
447                 report_error_if_not_applied_to_span(attr, &info)?;
448                 Ok(self.add_spanned_subdiagnostic(binding, ident, parse_quote! { _subdiag::label }))
449             }
450             "note" | "help" => {
451                 let path = match name {
452                     "note" => parse_quote! { _subdiag::note },
453                     "help" => parse_quote! { _subdiag::help },
454                     _ => unreachable!(),
455                 };
456                 if type_matches_path(&info.ty, &["rustc_span", "Span"]) {
457                     Ok(self.add_spanned_subdiagnostic(binding, ident, path))
458                 } else if type_is_unit(&info.ty) {
459                     Ok(self.add_subdiagnostic(ident, path))
460                 } else {
461                     report_type_error(attr, "`Span` or `()`")?;
462                 }
463             }
464             "subdiagnostic" => Ok(quote! { #diag.subdiagnostic(#binding); }),
465             _ => throw_invalid_attr!(attr, &meta, |diag| {
466                 diag.help(
467                     "only `skip_arg`, `primary_span`, `label`, `note`, `help` and `subdiagnostic` \
468                      are valid field attributes",
469                 )
470             }),
471         }
472     }
473
474     fn generate_inner_field_code_list(
475         &mut self,
476         attr: &Attribute,
477         info: FieldInfo<'_>,
478         binding: TokenStream,
479     ) -> Result<TokenStream, SessionDiagnosticDeriveError> {
480         let meta = attr.parse_meta()?;
481         let Meta::List(MetaList { ref path, ref nested, .. }) = meta  else { unreachable!() };
482
483         let ident = &attr.path.segments.last().unwrap().ident;
484         let name = path.segments.last().unwrap().ident.to_string();
485         let name = name.as_ref();
486         match name {
487             "suggestion" | "suggestion_short" | "suggestion_hidden" | "suggestion_verbose" => {
488                 return self.generate_inner_field_code_suggestion(attr, info);
489             }
490             "label" | "help" | "note" => (),
491             _ => throw_invalid_attr!(attr, &meta, |diag| {
492                 diag.help(
493                     "only `label`, `note`, `help` or `suggestion{,_short,_hidden,_verbose}` are \
494                      valid field attributes",
495                 )
496             }),
497         }
498
499         // For `#[label(..)]`, `#[note(..)]` and `#[help(..)]`, the first nested element must be a
500         // path, e.g. `#[label(typeck::label)]`.
501         let mut nested_iter = nested.into_iter();
502         let msg = match nested_iter.next() {
503             Some(NestedMeta::Meta(Meta::Path(path))) => path.clone(),
504             Some(nested_attr) => throw_invalid_nested_attr!(attr, &nested_attr),
505             None => throw_invalid_attr!(attr, &meta),
506         };
507
508         // None of these attributes should have anything following the slug.
509         if nested_iter.next().is_some() {
510             throw_invalid_attr!(attr, &meta);
511         }
512
513         match name {
514             "label" => {
515                 report_error_if_not_applied_to_span(attr, &info)?;
516                 Ok(self.add_spanned_subdiagnostic(binding, ident, msg))
517             }
518             "note" | "help" if type_matches_path(&info.ty, &["rustc_span", "Span"]) => {
519                 Ok(self.add_spanned_subdiagnostic(binding, ident, msg))
520             }
521             "note" | "help" if type_is_unit(&info.ty) => Ok(self.add_subdiagnostic(ident, msg)),
522             "note" | "help" => {
523                 report_type_error(attr, "`Span` or `()`")?;
524             }
525             _ => unreachable!(),
526         }
527     }
528
529     fn generate_inner_field_code_suggestion(
530         &mut self,
531         attr: &Attribute,
532         info: FieldInfo<'_>,
533     ) -> Result<TokenStream, SessionDiagnosticDeriveError> {
534         let diag = &self.diag;
535
536         let mut meta = attr.parse_meta()?;
537         let Meta::List(MetaList { ref path, ref mut nested, .. }) = meta  else { unreachable!() };
538
539         let (span_field, mut applicability) = self.span_and_applicability_of_ty(info)?;
540
541         let mut msg = None;
542         let mut code = None;
543
544         let mut nested_iter = nested.into_iter().peekable();
545         if let Some(nested_attr) = nested_iter.peek() {
546             if let NestedMeta::Meta(Meta::Path(path)) = nested_attr {
547                 msg = Some(path.clone());
548             }
549         };
550         // Move the iterator forward if a path was found (don't otherwise so that
551         // code/applicability can be found or an error emitted).
552         if msg.is_some() {
553             let _ = nested_iter.next();
554         }
555
556         for nested_attr in nested_iter {
557             let meta = match nested_attr {
558                 syn::NestedMeta::Meta(ref meta) => meta,
559                 syn::NestedMeta::Lit(_) => throw_invalid_nested_attr!(attr, &nested_attr),
560             };
561
562             let nested_name = meta.path().segments.last().unwrap().ident.to_string();
563             let nested_name = nested_name.as_str();
564             match meta {
565                 Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
566                     let span = meta.span().unwrap();
567                     match nested_name {
568                         "code" => {
569                             let formatted_str = self.build_format(&s.value(), s.span());
570                             code = Some(formatted_str);
571                         }
572                         "applicability" => {
573                             applicability = match applicability {
574                                 Some(v) => {
575                                     span_err(
576                                         span,
577                                         "applicability cannot be set in both the field and \
578                                          attribute",
579                                     )
580                                     .emit();
581                                     Some(v)
582                                 }
583                                 None => match Applicability::from_str(&s.value()) {
584                                     Ok(v) => Some(quote! { #v }),
585                                     Err(()) => {
586                                         span_err(span, "invalid applicability").emit();
587                                         None
588                                     }
589                                 },
590                             }
591                         }
592                         _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
593                             diag.help(
594                                 "only `message`, `code` and `applicability` are valid field \
595                                  attributes",
596                             )
597                         }),
598                     }
599                 }
600                 _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
601                     if matches!(meta, Meta::Path(_)) {
602                         diag.help("a diagnostic slug must be the first argument to the attribute")
603                     } else {
604                         diag
605                     }
606                 }),
607             }
608         }
609
610         let applicability =
611             applicability.unwrap_or_else(|| quote!(rustc_errors::Applicability::Unspecified));
612
613         let name = path.segments.last().unwrap().ident.to_string();
614         let method = format_ident!("span_{}", name);
615
616         let msg = msg.unwrap_or_else(|| parse_quote! { _subdiag::suggestion });
617         let msg = quote! { rustc_errors::fluent::#msg };
618         let code = code.unwrap_or_else(|| quote! { String::new() });
619
620         Ok(quote! { #diag.#method(#span_field, #msg, #code, #applicability); })
621     }
622
623     /// Adds a spanned subdiagnostic by generating a `diag.span_$kind` call with the current slug
624     /// and `fluent_attr_identifier`.
625     fn add_spanned_subdiagnostic(
626         &self,
627         field_binding: TokenStream,
628         kind: &Ident,
629         fluent_attr_identifier: Path,
630     ) -> TokenStream {
631         let diag = &self.diag;
632         let fn_name = format_ident!("span_{}", kind);
633         quote! {
634             #diag.#fn_name(
635                 #field_binding,
636                 rustc_errors::fluent::#fluent_attr_identifier
637             );
638         }
639     }
640
641     /// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current slug
642     /// and `fluent_attr_identifier`.
643     fn add_subdiagnostic(&self, kind: &Ident, fluent_attr_identifier: Path) -> TokenStream {
644         let diag = &self.diag;
645         quote! {
646             #diag.#kind(rustc_errors::fluent::#fluent_attr_identifier);
647         }
648     }
649
650     fn span_and_applicability_of_ty(
651         &self,
652         info: FieldInfo<'_>,
653     ) -> Result<(TokenStream, Option<TokenStream>), SessionDiagnosticDeriveError> {
654         match &info.ty {
655             // If `ty` is `Span` w/out applicability, then use `Applicability::Unspecified`.
656             ty @ Type::Path(..) if type_matches_path(ty, &["rustc_span", "Span"]) => {
657                 let binding = &info.binding.binding;
658                 Ok((quote!(*#binding), None))
659             }
660             // If `ty` is `(Span, Applicability)` then return tokens accessing those.
661             Type::Tuple(tup) => {
662                 let mut span_idx = None;
663                 let mut applicability_idx = None;
664
665                 for (idx, elem) in tup.elems.iter().enumerate() {
666                     if type_matches_path(elem, &["rustc_span", "Span"]) {
667                         if span_idx.is_none() {
668                             span_idx = Some(syn::Index::from(idx));
669                         } else {
670                             throw_span_err!(
671                                 info.span.unwrap(),
672                                 "type of field annotated with `#[suggestion(...)]` contains more \
673                                  than one `Span`"
674                             );
675                         }
676                     } else if type_matches_path(elem, &["rustc_errors", "Applicability"]) {
677                         if applicability_idx.is_none() {
678                             applicability_idx = Some(syn::Index::from(idx));
679                         } else {
680                             throw_span_err!(
681                                 info.span.unwrap(),
682                                 "type of field annotated with `#[suggestion(...)]` contains more \
683                                  than one Applicability"
684                             );
685                         }
686                     }
687                 }
688
689                 if let Some(span_idx) = span_idx {
690                     let binding = &info.binding.binding;
691                     let span = quote!(#binding.#span_idx);
692                     let applicability = applicability_idx
693                         .map(|applicability_idx| quote!(#binding.#applicability_idx))
694                         .unwrap_or_else(|| quote!(rustc_errors::Applicability::Unspecified));
695
696                     return Ok((span, Some(applicability)));
697                 }
698
699                 throw_span_err!(info.span.unwrap(), "wrong types for suggestion", |diag| {
700                     diag.help(
701                         "`#[suggestion(...)]` on a tuple field must be applied to fields of type \
702                          `(Span, Applicability)`",
703                     )
704                 });
705             }
706             // If `ty` isn't a `Span` or `(Span, Applicability)` then emit an error.
707             _ => throw_span_err!(info.span.unwrap(), "wrong field type for suggestion", |diag| {
708                 diag.help(
709                     "`#[suggestion(...)]` should be applied to fields of type `Span` or \
710                      `(Span, Applicability)`",
711                 )
712             }),
713         }
714     }
715 }