]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_macros/src/session_diagnostic.rs
Rollup merge of #96029 - IsakNyberg:error-messages-fix, r=Dylan-DPC
[rust.git] / compiler / rustc_macros / src / session_diagnostic.rs
1 #![deny(unused_must_use)]
2 use proc_macro::Diagnostic;
3 use quote::{format_ident, quote};
4 use syn::spanned::Spanned;
5
6 use std::collections::{BTreeSet, HashMap};
7
8 /// Implements `#[derive(SessionDiagnostic)]`, which allows for errors to be specified as a struct,
9 /// independent from the actual diagnostics emitting code.
10 ///
11 /// ```ignore (pseudo-rust)
12 /// # extern crate rustc_errors;
13 /// # use rustc_errors::Applicability;
14 /// # extern crate rustc_span;
15 /// # use rustc_span::{symbol::Ident, Span};
16 /// # extern crate rust_middle;
17 /// # use rustc_middle::ty::Ty;
18 /// #[derive(SessionDiagnostic)]
19 /// #[error(code = "E0505", slug = "move-out-of-borrow-error")]
20 /// pub struct MoveOutOfBorrowError<'tcx> {
21 ///     pub name: Ident,
22 ///     pub ty: Ty<'tcx>,
23 ///     #[primary_span]
24 ///     #[label = "cannot move out of borrow"]
25 ///     pub span: Span,
26 ///     #[label = "`{ty}` first borrowed here"]
27 ///     pub other_span: Span,
28 ///     #[suggestion(message = "consider cloning here", code = "{name}.clone()")]
29 ///     pub opt_sugg: Option<(Span, Applicability)>
30 /// }
31 /// ```
32 ///
33 /// Then, later, to emit the error:
34 ///
35 /// ```ignore (pseudo-rust)
36 /// sess.emit_err(MoveOutOfBorrowError {
37 ///     expected,
38 ///     actual,
39 ///     span,
40 ///     other_span,
41 ///     opt_sugg: Some(suggestion, Applicability::MachineApplicable),
42 /// });
43 /// ```
44 pub fn session_diagnostic_derive(s: synstructure::Structure<'_>) -> proc_macro2::TokenStream {
45     // Names for the diagnostic we build and the session we build it from.
46     let diag = format_ident!("diag");
47     let sess = format_ident!("sess");
48
49     SessionDiagnosticDerive::new(diag, sess, s).into_tokens()
50 }
51
52 /// Checks whether the type name of `ty` matches `name`.
53 ///
54 /// Given some struct at `a::b::c::Foo`, this will return true for `c::Foo`, `b::c::Foo`, or
55 /// `a::b::c::Foo`. This reasonably allows qualified names to be used in the macro.
56 fn type_matches_path(ty: &syn::Type, name: &[&str]) -> bool {
57     if let syn::Type::Path(ty) = ty {
58         ty.path
59             .segments
60             .iter()
61             .map(|s| s.ident.to_string())
62             .rev()
63             .zip(name.iter().rev())
64             .all(|(x, y)| &x.as_str() == y)
65     } else {
66         false
67     }
68 }
69
70 /// The central struct for constructing the `as_error` method from an annotated struct.
71 struct SessionDiagnosticDerive<'a> {
72     structure: synstructure::Structure<'a>,
73     builder: SessionDiagnosticDeriveBuilder<'a>,
74 }
75
76 impl std::convert::From<syn::Error> for SessionDiagnosticDeriveError {
77     fn from(e: syn::Error) -> Self {
78         SessionDiagnosticDeriveError::SynError(e)
79     }
80 }
81
82 #[derive(Debug)]
83 enum SessionDiagnosticDeriveError {
84     SynError(syn::Error),
85     ErrorHandled,
86 }
87
88 impl SessionDiagnosticDeriveError {
89     fn to_compile_error(self) -> proc_macro2::TokenStream {
90         match self {
91             SessionDiagnosticDeriveError::SynError(e) => e.to_compile_error(),
92             SessionDiagnosticDeriveError::ErrorHandled => {
93                 // Return ! to avoid having to create a blank DiagnosticBuilder to return when an
94                 // error has already been emitted to the compiler.
95                 quote! {
96                     { unreachable!(); }
97                 }
98             }
99         }
100     }
101 }
102
103 fn span_err(span: impl proc_macro::MultiSpan, msg: &str) -> proc_macro::Diagnostic {
104     Diagnostic::spanned(span, proc_macro::Level::Error, msg)
105 }
106
107 /// For methods that return a `Result<_, SessionDiagnosticDeriveError>`:
108 ///
109 /// Emit a diagnostic on span `$span` with msg `$msg` (optionally performing additional decoration
110 /// using the `FnOnce` passed in `diag`) and return `Err(ErrorHandled)`.
111 macro_rules! throw_span_err {
112     ($span:expr, $msg:expr) => {{ throw_span_err!($span, $msg, |diag| diag) }};
113     ($span:expr, $msg:expr, $f:expr) => {{
114         return Err(_throw_span_err($span, $msg, $f));
115     }};
116 }
117
118 /// When possible, prefer using `throw_span_err!` over using this function directly. This only
119 /// exists as a function to constrain `f` to an `impl FnOnce`.
120 fn _throw_span_err(
121     span: impl proc_macro::MultiSpan,
122     msg: &str,
123     f: impl FnOnce(proc_macro::Diagnostic) -> proc_macro::Diagnostic,
124 ) -> SessionDiagnosticDeriveError {
125     let diag = span_err(span, msg);
126     f(diag).emit();
127     SessionDiagnosticDeriveError::ErrorHandled
128 }
129
130 impl<'a> SessionDiagnosticDerive<'a> {
131     fn new(diag: syn::Ident, sess: syn::Ident, structure: synstructure::Structure<'a>) -> Self {
132         // Build the mapping of field names to fields. This allows attributes to peek values from
133         // other fields.
134         let mut fields_map = HashMap::new();
135
136         // Convenience bindings.
137         let ast = structure.ast();
138
139         if let syn::Data::Struct(syn::DataStruct { fields, .. }) = &ast.data {
140             for field in fields.iter() {
141                 if let Some(ident) = &field.ident {
142                     fields_map.insert(ident.to_string(), field);
143                 }
144             }
145         }
146
147         Self {
148             builder: SessionDiagnosticDeriveBuilder {
149                 diag,
150                 sess,
151                 fields: fields_map,
152                 kind: None,
153                 code: None,
154                 slug: None,
155             },
156             structure,
157         }
158     }
159
160     fn into_tokens(self) -> proc_macro2::TokenStream {
161         let SessionDiagnosticDerive { mut structure, mut builder } = self;
162
163         let ast = structure.ast();
164         let attrs = &ast.attrs;
165
166         let (implementation, param_ty) = {
167             if let syn::Data::Struct(..) = ast.data {
168                 let preamble = {
169                     let preamble = attrs.iter().map(|attr| {
170                         builder
171                             .generate_structure_code(attr)
172                             .unwrap_or_else(|v| v.to_compile_error())
173                     });
174
175                     quote! {
176                         #(#preamble)*;
177                     }
178                 };
179
180                 // Generates calls to `span_label` and similar functions based on the attributes
181                 // on fields. Code for suggestions uses formatting machinery and the value of
182                 // other fields - because any given field can be referenced multiple times, it
183                 // should be accessed through a borrow. When passing fields to `set_arg` (which
184                 // happens below) for Fluent, we want to move the data, so that has to happen
185                 // in a separate pass over the fields.
186                 let attrs = structure.each(|field_binding| {
187                     let field = field_binding.ast();
188                     let result = field.attrs.iter().map(|attr| {
189                         builder
190                             .generate_field_attr_code(
191                                 attr,
192                                 FieldInfo {
193                                     vis: &field.vis,
194                                     binding: field_binding,
195                                     ty: &field.ty,
196                                     span: &field.span(),
197                                 },
198                             )
199                             .unwrap_or_else(|v| v.to_compile_error())
200                     });
201
202                     quote! { #(#result);* }
203                 });
204
205                 // When generating `set_arg` calls, move data rather than borrow it to avoid
206                 // requiring clones - this must therefore be the last use of each field (for
207                 // example, any formatting machinery that might refer to a field should be
208                 // generated already).
209                 structure.bind_with(|_| synstructure::BindStyle::Move);
210                 let args = structure.each(|field_binding| {
211                     let field = field_binding.ast();
212                     // When a field has attributes like `#[label]` or `#[note]` then it doesn't
213                     // need to be passed as an argument to the diagnostic. But when a field has no
214                     // attributes then it must be passed as an argument to the diagnostic so that
215                     // it can be referred to by Fluent messages.
216                     if field.attrs.is_empty() {
217                         let diag = &builder.diag;
218                         let ident = field_binding.ast().ident.as_ref().unwrap();
219                         quote! {
220                             #diag.set_arg(
221                                 stringify!(#ident),
222                                 #field_binding.into_diagnostic_arg()
223                             );
224                         }
225                     } else {
226                         quote! {}
227                     }
228                 });
229
230                 let span = ast.span().unwrap();
231                 let (diag, sess) = (&builder.diag, &builder.sess);
232                 let init = match (builder.kind, builder.slug) {
233                     (None, _) => {
234                         span_err(span, "diagnostic kind not specified")
235                             .help("use the `#[error(...)]` attribute to create an error")
236                             .emit();
237                         return SessionDiagnosticDeriveError::ErrorHandled.to_compile_error();
238                     }
239                     (Some((kind, _)), None) => {
240                         span_err(span, "`slug` not specified")
241                             .help(&format!("use the `#[{}(slug = \"...\")]` attribute to set this diagnostic's slug", kind.descr()))
242                             .emit();
243                         return SessionDiagnosticDeriveError::ErrorHandled.to_compile_error();
244                     }
245                     (Some((SessionDiagnosticKind::Error, _)), Some((slug, _))) => {
246                         quote! {
247                             let mut #diag = #sess.struct_err(
248                                 rustc_errors::DiagnosticMessage::fluent(#slug),
249                             );
250                         }
251                     }
252                     (Some((SessionDiagnosticKind::Warn, _)), Some((slug, _))) => {
253                         quote! {
254                             let mut #diag = #sess.struct_warn(
255                                 rustc_errors::DiagnosticMessage::fluent(#slug),
256                             );
257                         }
258                     }
259                 };
260
261                 let implementation = quote! {
262                     #init
263                     #preamble
264                     match self {
265                         #attrs
266                     }
267                     match self {
268                         #args
269                     }
270                     #diag
271                 };
272                 let param_ty = match builder.kind {
273                     Some((SessionDiagnosticKind::Error, _)) => {
274                         quote! { rustc_errors::ErrorGuaranteed }
275                     }
276                     Some((SessionDiagnosticKind::Warn, _)) => quote! { () },
277                     _ => unreachable!(),
278                 };
279
280                 (implementation, param_ty)
281             } else {
282                 span_err(
283                     ast.span().unwrap(),
284                     "`#[derive(SessionDiagnostic)]` can only be used on structs",
285                 )
286                 .emit();
287
288                 let implementation = SessionDiagnosticDeriveError::ErrorHandled.to_compile_error();
289                 let param_ty = quote! { rustc_errors::ErrorGuaranteed };
290                 (implementation, param_ty)
291             }
292         };
293
294         let sess = &builder.sess;
295         structure.gen_impl(quote! {
296             gen impl<'__session_diagnostic_sess> rustc_session::SessionDiagnostic<'__session_diagnostic_sess, #param_ty>
297                     for @Self
298             {
299                 fn into_diagnostic(
300                     self,
301                     #sess: &'__session_diagnostic_sess rustc_session::Session
302                 ) -> rustc_errors::DiagnosticBuilder<'__session_diagnostic_sess, #param_ty> {
303                     use rustc_errors::IntoDiagnosticArg;
304                     #implementation
305                 }
306             }
307         })
308     }
309 }
310
311 /// Field information passed to the builder. Deliberately omits attrs to discourage the
312 /// `generate_*` methods from walking the attributes themselves.
313 struct FieldInfo<'a> {
314     vis: &'a syn::Visibility,
315     binding: &'a synstructure::BindingInfo<'a>,
316     ty: &'a syn::Type,
317     span: &'a proc_macro2::Span,
318 }
319
320 /// What kind of session diagnostic is being derived - an error or a warning?
321 #[derive(Copy, Clone)]
322 enum SessionDiagnosticKind {
323     /// `#[error(..)]`
324     Error,
325     /// `#[warn(..)]`
326     Warn,
327 }
328
329 impl SessionDiagnosticKind {
330     /// Returns human-readable string corresponding to the kind.
331     fn descr(&self) -> &'static str {
332         match self {
333             SessionDiagnosticKind::Error => "error",
334             SessionDiagnosticKind::Warn => "warning",
335         }
336     }
337 }
338
339 /// Tracks persistent information required for building up the individual calls to diagnostic
340 /// methods for the final generated method. This is a separate struct to `SessionDiagnosticDerive`
341 /// only to be able to destructure and split `self.builder` and the `self.structure` up to avoid a
342 /// double mut borrow later on.
343 struct SessionDiagnosticDeriveBuilder<'a> {
344     /// Name of the session parameter that's passed in to the `as_error` method.
345     sess: syn::Ident,
346     /// The identifier to use for the generated `DiagnosticBuilder` instance.
347     diag: syn::Ident,
348
349     /// Store a map of field name to its corresponding field. This is built on construction of the
350     /// derive builder.
351     fields: HashMap<String, &'a syn::Field>,
352
353     /// Kind of diagnostic requested via the struct attribute.
354     kind: Option<(SessionDiagnosticKind, proc_macro::Span)>,
355     /// Slug is a mandatory part of the struct attribute as corresponds to the Fluent message that
356     /// has the actual diagnostic message.
357     slug: Option<(String, proc_macro::Span)>,
358     /// Error codes are a optional part of the struct attribute - this is only set to detect
359     /// multiple specifications.
360     code: Option<proc_macro::Span>,
361 }
362
363 impl<'a> SessionDiagnosticDeriveBuilder<'a> {
364     /// Establishes state in the `SessionDiagnosticDeriveBuilder` resulting from the struct
365     /// attributes like `#[error(..)#`, such as the diagnostic kind and slug. Generates
366     /// diagnostic builder calls for setting error code and creating note/help messages.
367     fn generate_structure_code(
368         &mut self,
369         attr: &syn::Attribute,
370     ) -> Result<proc_macro2::TokenStream, SessionDiagnosticDeriveError> {
371         let span = attr.span().unwrap();
372
373         let name = attr.path.segments.last().unwrap().ident.to_string();
374         let name = name.as_str();
375         let meta = attr.parse_meta()?;
376
377         if matches!(name, "help" | "note")
378             && matches!(meta, syn::Meta::Path(_) | syn::Meta::NameValue(_))
379         {
380             let diag = &self.diag;
381             let slug = match &self.slug {
382                 Some((slug, _)) => slug.as_str(),
383                 None => throw_span_err!(
384                     span,
385                     &format!(
386                         "`#[{}{}]` must come after `#[error(..)]` or `#[warn(..)]`",
387                         name,
388                         match meta {
389                             syn::Meta::Path(_) => "",
390                             syn::Meta::NameValue(_) => " = ...",
391                             _ => unreachable!(),
392                         }
393                     )
394                 ),
395             };
396             let id = match meta {
397                 syn::Meta::Path(..) => quote! { #name },
398                 syn::Meta::NameValue(syn::MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
399                     quote! { #s }
400                 }
401                 _ => unreachable!(),
402             };
403             let fn_name = proc_macro2::Ident::new(name, attr.span());
404
405             return Ok(quote! {
406                 #diag.#fn_name(rustc_errors::DiagnosticMessage::fluent_attr(#slug, #id));
407             });
408         }
409
410         let nested = match meta {
411             syn::Meta::List(syn::MetaList { nested, .. }) => nested,
412             syn::Meta::Path(..) => throw_span_err!(
413                 span,
414                 &format!("`#[{}]` is not a valid `SessionDiagnostic` struct attribute", name)
415             ),
416             syn::Meta::NameValue(..) => throw_span_err!(
417                 span,
418                 &format!("`#[{} = ...]` is not a valid `SessionDiagnostic` struct attribute", name)
419             ),
420         };
421
422         let kind = match name {
423             "error" => SessionDiagnosticKind::Error,
424             "warning" => SessionDiagnosticKind::Warn,
425             other => throw_span_err!(
426                 span,
427                 &format!("`#[{}(...)]` is not a valid `SessionDiagnostic` struct attribute", other)
428             ),
429         };
430         self.set_kind_once(kind, span)?;
431
432         let mut tokens = Vec::new();
433         for attr in nested {
434             let span = attr.span().unwrap();
435             let meta = match attr {
436                 syn::NestedMeta::Meta(meta) => meta,
437                 syn::NestedMeta::Lit(_) => throw_span_err!(
438                     span,
439                     &format!(
440                         "`#[{}(\"...\")]` is not a valid `SessionDiagnostic` struct attribute",
441                         name
442                     )
443                 ),
444             };
445
446             let path = meta.path();
447             let nested_name = path.segments.last().unwrap().ident.to_string();
448             match &meta {
449                 // Struct attributes are only allowed to be applied once, and the diagnostic
450                 // changes will be set in the initialisation code.
451                 syn::Meta::NameValue(syn::MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
452                     match nested_name.as_str() {
453                         "slug" => {
454                             self.set_slug_once(s.value(), s.span().unwrap());
455                         }
456                         "code" => {
457                             tokens.push(self.set_code_once(s.value(), s.span().unwrap()));
458                         }
459                         other => {
460                             let diag = span_err(
461                                 span,
462                                 &format!(
463                                     "`#[{}({} = ...)]` is not a valid `SessionDiagnostic` struct attribute",
464                                     name, other
465                                 ),
466                             );
467                             diag.emit();
468                         }
469                     }
470                 }
471                 syn::Meta::NameValue(..) => {
472                     span_err(
473                         span,
474                         &format!(
475                             "`#[{}({} = ...)]` is not a valid `SessionDiagnostic` struct attribute",
476                             name, nested_name
477                         ),
478                     )
479                     .help("value must be a string")
480                     .emit();
481                 }
482                 syn::Meta::Path(..) => {
483                     span_err(
484                         span,
485                         &format!(
486                             "`#[{}({})]` is not a valid `SessionDiagnostic` struct attribute",
487                             name, nested_name
488                         ),
489                     )
490                     .emit();
491                 }
492                 syn::Meta::List(..) => {
493                     span_err(
494                         span,
495                         &format!(
496                             "`#[{}({}(...))]` is not a valid `SessionDiagnostic` struct attribute",
497                             name, nested_name
498                         ),
499                     )
500                     .emit();
501                 }
502             }
503         }
504
505         Ok(tokens.drain(..).collect())
506     }
507
508     #[must_use]
509     fn set_kind_once(
510         &mut self,
511         kind: SessionDiagnosticKind,
512         span: proc_macro::Span,
513     ) -> Result<(), SessionDiagnosticDeriveError> {
514         match self.kind {
515             None => {
516                 self.kind = Some((kind, span));
517                 Ok(())
518             }
519             Some((prev_kind, prev_span)) => {
520                 let existing = prev_kind.descr();
521                 let current = kind.descr();
522
523                 let msg = if current == existing {
524                     format!("`{}` specified multiple times", existing)
525                 } else {
526                     format!("`{}` specified when `{}` was already specified", current, existing)
527                 };
528                 throw_span_err!(span, &msg, |diag| diag
529                     .span_note(prev_span, "previously specified here"));
530             }
531         }
532     }
533
534     fn set_code_once(&mut self, code: String, span: proc_macro::Span) -> proc_macro2::TokenStream {
535         match self.code {
536             None => {
537                 self.code = Some(span);
538             }
539             Some(prev_span) => {
540                 span_err(span, "`code` specified multiple times")
541                     .span_note(prev_span, "previously specified here")
542                     .emit();
543             }
544         }
545
546         let diag = &self.diag;
547         quote! { #diag.code(rustc_errors::DiagnosticId::Error(#code.to_string())); }
548     }
549
550     fn set_slug_once(&mut self, slug: String, span: proc_macro::Span) {
551         match self.slug {
552             None => {
553                 self.slug = Some((slug, span));
554             }
555             Some((_, prev_span)) => {
556                 span_err(span, "`slug` specified multiple times")
557                     .span_note(prev_span, "previously specified here")
558                     .emit();
559             }
560         }
561     }
562
563     fn generate_field_attr_code(
564         &mut self,
565         attr: &syn::Attribute,
566         info: FieldInfo<'_>,
567     ) -> Result<proc_macro2::TokenStream, SessionDiagnosticDeriveError> {
568         let field_binding = &info.binding.binding;
569         let option_ty = option_inner_ty(&info.ty);
570         let generated_code = self.generate_non_option_field_code(
571             attr,
572             FieldInfo {
573                 vis: info.vis,
574                 binding: info.binding,
575                 ty: option_ty.unwrap_or(&info.ty),
576                 span: info.span,
577             },
578         )?;
579
580         if option_ty.is_none() {
581             Ok(quote! { #generated_code })
582         } else {
583             Ok(quote! {
584                 if let Some(#field_binding) = #field_binding {
585                     #generated_code
586                 }
587             })
588         }
589     }
590
591     fn generate_non_option_field_code(
592         &mut self,
593         attr: &syn::Attribute,
594         info: FieldInfo<'_>,
595     ) -> Result<proc_macro2::TokenStream, SessionDiagnosticDeriveError> {
596         let diag = &self.diag;
597         let span = attr.span().unwrap();
598         let field_binding = &info.binding.binding;
599
600         let name = attr.path.segments.last().unwrap().ident.to_string();
601         let name = name.as_str();
602
603         let meta = attr.parse_meta()?;
604         match meta {
605             syn::Meta::Path(_) => match name {
606                 "skip_arg" => {
607                     // Don't need to do anything - by virtue of the attribute existing, the
608                     // `set_arg` call will not be generated.
609                     Ok(quote! {})
610                 }
611                 "primary_span" => {
612                     self.report_error_if_not_applied_to_span(attr, info)?;
613                     Ok(quote! {
614                         #diag.set_span(*#field_binding);
615                     })
616                 }
617                 "label" | "note" | "help" => {
618                     self.report_error_if_not_applied_to_span(attr, info)?;
619                     Ok(self.add_subdiagnostic(field_binding, name, name))
620                 }
621                 other => throw_span_err!(
622                     span,
623                     &format!("`#[{}]` is not a valid `SessionDiagnostic` field attribute", other)
624                 ),
625             },
626             syn::Meta::NameValue(syn::MetaNameValue { lit: syn::Lit::Str(s), .. }) => match name {
627                 "label" | "note" | "help" => {
628                     self.report_error_if_not_applied_to_span(attr, info)?;
629                     Ok(self.add_subdiagnostic(field_binding, name, &s.value()))
630                 }
631                 other => throw_span_err!(
632                     span,
633                     &format!(
634                         "`#[{} = ...]` is not a valid `SessionDiagnostic` field attribute",
635                         other
636                     )
637                 ),
638             },
639             syn::Meta::NameValue(_) => throw_span_err!(
640                 span,
641                 &format!("`#[{} = ...]` is not a valid `SessionDiagnostic` field attribute", name),
642                 |diag| diag.help("value must be a string")
643             ),
644             syn::Meta::List(syn::MetaList { path, nested, .. }) => {
645                 let name = path.segments.last().unwrap().ident.to_string();
646                 let name = name.as_ref();
647
648                 match name {
649                     "suggestion" | "suggestion_short" | "suggestion_hidden"
650                     | "suggestion_verbose" => (),
651                     other => throw_span_err!(
652                         span,
653                         &format!(
654                             "`#[{}(...)]` is not a valid `SessionDiagnostic` field attribute",
655                             other
656                         )
657                     ),
658                 };
659
660                 let (span_, applicability) = self.span_and_applicability_of_ty(info)?;
661
662                 let mut msg = None;
663                 let mut code = None;
664
665                 for attr in nested {
666                     let meta = match attr {
667                         syn::NestedMeta::Meta(meta) => meta,
668                         syn::NestedMeta::Lit(_) => throw_span_err!(
669                             span,
670                             &format!(
671                                 "`#[{}(\"...\")]` is not a valid `SessionDiagnostic` field attribute",
672                                 name
673                             )
674                         ),
675                     };
676
677                     let span = meta.span().unwrap();
678                     let nested_name = meta.path().segments.last().unwrap().ident.to_string();
679                     let nested_name = nested_name.as_str();
680
681                     match meta {
682                         syn::Meta::NameValue(syn::MetaNameValue {
683                             lit: syn::Lit::Str(s), ..
684                         }) => match nested_name {
685                             "message" => {
686                                 msg = Some(s.value());
687                             }
688                             "code" => {
689                                 let formatted_str = self.build_format(&s.value(), s.span());
690                                 code = Some(formatted_str);
691                             }
692                             other => throw_span_err!(
693                                 span,
694                                 &format!(
695                                     "`#[{}({} = ...)]` is not a valid `SessionDiagnostic` field attribute",
696                                     name, other
697                                 )
698                             ),
699                         },
700                         syn::Meta::NameValue(..) => throw_span_err!(
701                             span,
702                             &format!(
703                                 "`#[{}({} = ...)]` is not a valid `SessionDiagnostic` struct attribute",
704                                 name, nested_name
705                             ),
706                             |diag| diag.help("value must be a string")
707                         ),
708                         syn::Meta::Path(..) => throw_span_err!(
709                             span,
710                             &format!(
711                                 "`#[{}({})]` is not a valid `SessionDiagnostic` struct attribute",
712                                 name, nested_name
713                             )
714                         ),
715                         syn::Meta::List(..) => throw_span_err!(
716                             span,
717                             &format!(
718                                 "`#[{}({}(...))]` is not a valid `SessionDiagnostic` struct attribute",
719                                 name, nested_name
720                             )
721                         ),
722                     }
723                 }
724
725                 let method = format_ident!("span_{}", name);
726
727                 let slug = self
728                     .slug
729                     .as_ref()
730                     .map(|(slug, _)| slug.as_str())
731                     .unwrap_or_else(|| "missing-slug");
732                 let msg = msg.as_deref().unwrap_or("suggestion");
733                 let msg = quote! { rustc_errors::DiagnosticMessage::fluent_attr(#slug, #msg) };
734                 let code = code.unwrap_or_else(|| quote! { String::new() });
735
736                 Ok(quote! { #diag.#method(#span_, #msg, #code, #applicability); })
737             }
738         }
739     }
740
741     /// Reports an error if the field's type is not `Span`.
742     fn report_error_if_not_applied_to_span(
743         &self,
744         attr: &syn::Attribute,
745         info: FieldInfo<'_>,
746     ) -> Result<(), SessionDiagnosticDeriveError> {
747         if !type_matches_path(&info.ty, &["rustc_span", "Span"]) {
748             let name = attr.path.segments.last().unwrap().ident.to_string();
749             let name = name.as_str();
750             let meta = attr.parse_meta()?;
751
752             throw_span_err!(
753                 attr.span().unwrap(),
754                 &format!(
755                     "the `#[{}{}]` attribute can only be applied to fields of type `Span`",
756                     name,
757                     match meta {
758                         syn::Meta::Path(_) => "",
759                         syn::Meta::NameValue(_) => " = ...",
760                         syn::Meta::List(_) => "(...)",
761                     }
762                 )
763             );
764         }
765
766         Ok(())
767     }
768
769     /// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current slug and
770     /// `fluent_attr_identifier`.
771     fn add_subdiagnostic(
772         &self,
773         field_binding: &proc_macro2::Ident,
774         kind: &str,
775         fluent_attr_identifier: &str,
776     ) -> proc_macro2::TokenStream {
777         let diag = &self.diag;
778
779         let slug =
780             self.slug.as_ref().map(|(slug, _)| slug.as_str()).unwrap_or_else(|| "missing-slug");
781         let fn_name = format_ident!("span_{}", kind);
782         quote! {
783             #diag.#fn_name(
784                 *#field_binding,
785                 rustc_errors::DiagnosticMessage::fluent_attr(#slug, #fluent_attr_identifier)
786             );
787         }
788     }
789
790     fn span_and_applicability_of_ty(
791         &self,
792         info: FieldInfo<'_>,
793     ) -> Result<(proc_macro2::TokenStream, proc_macro2::TokenStream), SessionDiagnosticDeriveError>
794     {
795         match &info.ty {
796             // If `ty` is `Span` w/out applicability, then use `Applicability::Unspecified`.
797             ty @ syn::Type::Path(..) if type_matches_path(ty, &["rustc_span", "Span"]) => {
798                 let binding = &info.binding.binding;
799                 Ok((quote!(*#binding), quote!(rustc_errors::Applicability::Unspecified)))
800             }
801             // If `ty` is `(Span, Applicability)` then return tokens accessing those.
802             syn::Type::Tuple(tup) => {
803                 let mut span_idx = None;
804                 let mut applicability_idx = None;
805
806                 for (idx, elem) in tup.elems.iter().enumerate() {
807                     if type_matches_path(elem, &["rustc_span", "Span"]) {
808                         if span_idx.is_none() {
809                             span_idx = Some(syn::Index::from(idx));
810                         } else {
811                             throw_span_err!(
812                                 info.span.unwrap(),
813                                 "type of field annotated with `#[suggestion(...)]` contains more than one `Span`"
814                             );
815                         }
816                     } else if type_matches_path(elem, &["rustc_errors", "Applicability"]) {
817                         if applicability_idx.is_none() {
818                             applicability_idx = Some(syn::Index::from(idx));
819                         } else {
820                             throw_span_err!(
821                                 info.span.unwrap(),
822                                 "type of field annotated with `#[suggestion(...)]` contains more than one Applicability"
823                             );
824                         }
825                     }
826                 }
827
828                 if let Some(span_idx) = span_idx {
829                     let binding = &info.binding.binding;
830                     let span = quote!(#binding.#span_idx);
831                     let applicability = applicability_idx
832                         .map(|applicability_idx| quote!(#binding.#applicability_idx))
833                         .unwrap_or_else(|| quote!(rustc_errors::Applicability::Unspecified));
834
835                     return Ok((span, applicability));
836                 }
837
838                 throw_span_err!(info.span.unwrap(), "wrong types for suggestion", |diag| {
839                     diag.help("`#[suggestion(...)]` on a tuple field must be applied to fields of type `(Span, Applicability)`")
840                 });
841             }
842             // If `ty` isn't a `Span` or `(Span, Applicability)` then emit an error.
843             _ => throw_span_err!(info.span.unwrap(), "wrong field type for suggestion", |diag| {
844                 diag.help("`#[suggestion(...)]` should be applied to fields of type `Span` or `(Span, Applicability)`")
845             }),
846         }
847     }
848
849     /// In the strings in the attributes supplied to this macro, we want callers to be able to
850     /// reference fields in the format string. For example:
851     ///
852     /// ```ignore (not-usage-example)
853     /// struct Point {
854     ///     #[error = "Expected a point greater than ({x}, {y})"]
855     ///     x: i32,
856     ///     y: i32,
857     /// }
858     /// ```
859     ///
860     /// We want to automatically pick up that `{x}` refers `self.x` and `{y}` refers to `self.y`,
861     /// then generate this call to `format!`:
862     ///
863     /// ```ignore (not-usage-example)
864     /// format!("Expected a point greater than ({x}, {y})", x = self.x, y = self.y)
865     /// ```
866     ///
867     /// This function builds the entire call to `format!`.
868     fn build_format(&self, input: &str, span: proc_macro2::Span) -> proc_macro2::TokenStream {
869         // This set is used later to generate the final format string. To keep builds reproducible,
870         // the iteration order needs to be deterministic, hence why we use a BTreeSet here instead
871         // of a HashSet.
872         let mut referenced_fields: BTreeSet<String> = BTreeSet::new();
873
874         // At this point, we can start parsing the format string.
875         let mut it = input.chars().peekable();
876         // Once the start of a format string has been found, process the format string and spit out
877         // the referenced fields. Leaves `it` sitting on the closing brace of the format string, so the
878         // next call to `it.next()` retrieves the next character.
879         while let Some(c) = it.next() {
880             if c == '{' && *it.peek().unwrap_or(&'\0') != '{' {
881                 let mut eat_argument = || -> Option<String> {
882                     let mut result = String::new();
883                     // Format specifiers look like
884                     // format   := '{' [ argument ] [ ':' format_spec ] '}' .
885                     // Therefore, we only need to eat until ':' or '}' to find the argument.
886                     while let Some(c) = it.next() {
887                         result.push(c);
888                         let next = *it.peek().unwrap_or(&'\0');
889                         if next == '}' {
890                             break;
891                         } else if next == ':' {
892                             // Eat the ':' character.
893                             assert_eq!(it.next().unwrap(), ':');
894                             break;
895                         }
896                     }
897                     // Eat until (and including) the matching '}'
898                     while it.next()? != '}' {
899                         continue;
900                     }
901                     Some(result)
902                 };
903
904                 if let Some(referenced_field) = eat_argument() {
905                     referenced_fields.insert(referenced_field);
906                 }
907             }
908         }
909         // At this point, `referenced_fields` contains a set of the unique fields that were
910         // referenced in the format string. Generate the corresponding "x = self.x" format
911         // string parameters:
912         let args = referenced_fields.into_iter().map(|field: String| {
913             let field_ident = format_ident!("{}", field);
914             let value = if self.fields.contains_key(&field) {
915                 quote! {
916                     &self.#field_ident
917                 }
918             } else {
919                 // This field doesn't exist. Emit a diagnostic.
920                 Diagnostic::spanned(
921                     span.unwrap(),
922                     proc_macro::Level::Error,
923                     format!("`{}` doesn't refer to a field on this type", field),
924                 )
925                 .emit();
926                 quote! {
927                     "{#field}"
928                 }
929             };
930             quote! {
931                 #field_ident = #value
932             }
933         });
934         quote! {
935             format!(#input #(,#args)*)
936         }
937     }
938 }
939
940 /// If `ty` is an Option, returns `Some(inner type)`, otherwise returns `None`.
941 fn option_inner_ty(ty: &syn::Type) -> Option<&syn::Type> {
942     if type_matches_path(ty, &["std", "option", "Option"]) {
943         if let syn::Type::Path(ty_path) = ty {
944             let path = &ty_path.path;
945             let ty = path.segments.iter().last().unwrap();
946             if let syn::PathArguments::AngleBracketed(bracketed) = &ty.arguments {
947                 if bracketed.args.len() == 1 {
948                     if let syn::GenericArgument::Type(ty) = &bracketed.args[0] {
949                         return Some(ty);
950                     }
951                 }
952             }
953         }
954     }
955     None
956 }