]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_macros/src/diagnostics/diagnostic.rs
f49166433faad545b45e4b7cdeb3f7850a371ed4
[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     option_inner_ty, report_error_if_not_applied_to_span, type_matches_path, Applicability,
9     FieldInfo, HasFieldMap, SetOnce,
10 };
11 use proc_macro2::TokenStream;
12 use quote::{format_ident, quote};
13 use std::collections::HashMap;
14 use std::str::FromStr;
15 use syn::{spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, Type};
16 use synstructure::Structure;
17
18 /// The central struct for constructing the `into_diagnostic` method from an annotated struct.
19 pub(crate) struct SessionDiagnosticDerive<'a> {
20     structure: Structure<'a>,
21     builder: SessionDiagnosticDeriveBuilder,
22 }
23
24 impl<'a> SessionDiagnosticDerive<'a> {
25     pub(crate) fn new(diag: syn::Ident, sess: syn::Ident, structure: Structure<'a>) -> Self {
26         // Build the mapping of field names to fields. This allows attributes to peek values from
27         // other fields.
28         let mut fields_map = HashMap::new();
29
30         // Convenience bindings.
31         let ast = structure.ast();
32
33         if let syn::Data::Struct(syn::DataStruct { fields, .. }) = &ast.data {
34             for field in fields.iter() {
35                 if let Some(ident) = &field.ident {
36                     fields_map.insert(ident.to_string(), quote! { &self.#ident });
37                 }
38             }
39         }
40
41         Self {
42             builder: SessionDiagnosticDeriveBuilder {
43                 diag,
44                 sess,
45                 fields: fields_map,
46                 kind: None,
47                 code: None,
48                 slug: None,
49             },
50             structure,
51         }
52     }
53
54     pub(crate) fn into_tokens(self) -> TokenStream {
55         let SessionDiagnosticDerive { mut structure, mut builder } = self;
56
57         let ast = structure.ast();
58         let attrs = &ast.attrs;
59
60         let (implementation, param_ty) = {
61             if let syn::Data::Struct(..) = ast.data {
62                 let preamble = {
63                     let preamble = attrs.iter().map(|attr| {
64                         builder
65                             .generate_structure_code(attr)
66                             .unwrap_or_else(|v| v.to_compile_error())
67                     });
68
69                     quote! {
70                         #(#preamble)*;
71                     }
72                 };
73
74                 // Generates calls to `span_label` and similar functions based on the attributes
75                 // on fields. Code for suggestions uses formatting machinery and the value of
76                 // other fields - because any given field can be referenced multiple times, it
77                 // should be accessed through a borrow. When passing fields to `set_arg` (which
78                 // happens below) for Fluent, we want to move the data, so that has to happen
79                 // in a separate pass over the fields.
80                 let attrs = structure.each(|field_binding| {
81                     let field = field_binding.ast();
82                     let result = field.attrs.iter().map(|attr| {
83                         builder
84                             .generate_field_attr_code(
85                                 attr,
86                                 FieldInfo {
87                                     vis: &field.vis,
88                                     binding: field_binding,
89                                     ty: &field.ty,
90                                     span: &field.span(),
91                                 },
92                             )
93                             .unwrap_or_else(|v| v.to_compile_error())
94                     });
95
96                     quote! { #(#result);* }
97                 });
98
99                 // When generating `set_arg` calls, move data rather than borrow it to avoid
100                 // requiring clones - this must therefore be the last use of each field (for
101                 // example, any formatting machinery that might refer to a field should be
102                 // generated already).
103                 structure.bind_with(|_| synstructure::BindStyle::Move);
104                 let args = structure.each(|field_binding| {
105                     let field = field_binding.ast();
106                     // When a field has attributes like `#[label]` or `#[note]` then it doesn't
107                     // need to be passed as an argument to the diagnostic. But when a field has no
108                     // attributes then it must be passed as an argument to the diagnostic so that
109                     // it can be referred to by Fluent messages.
110                     if field.attrs.is_empty() {
111                         let diag = &builder.diag;
112                         let ident = field_binding.ast().ident.as_ref().unwrap();
113                         quote! {
114                             #diag.set_arg(
115                                 stringify!(#ident),
116                                 #field_binding.into_diagnostic_arg()
117                             );
118                         }
119                     } else {
120                         quote! {}
121                     }
122                 });
123
124                 let span = ast.span().unwrap();
125                 let (diag, sess) = (&builder.diag, &builder.sess);
126                 let init = match (builder.kind, builder.slug) {
127                     (None, _) => {
128                         span_err(span, "diagnostic kind not specified")
129                             .help("use the `#[error(...)]` attribute to create an error")
130                             .emit();
131                         return SessionDiagnosticDeriveError::ErrorHandled.to_compile_error();
132                     }
133                     (Some((kind, _)), None) => {
134                         span_err(span, "`slug` not specified")
135                             .help(&format!("use the `#[{}(slug = \"...\")]` attribute to set this diagnostic's slug", kind.descr()))
136                             .emit();
137                         return SessionDiagnosticDeriveError::ErrorHandled.to_compile_error();
138                     }
139                     (Some((SessionDiagnosticKind::Error, _)), Some((slug, _))) => {
140                         quote! {
141                             let mut #diag = #sess.struct_err(
142                                 rustc_errors::DiagnosticMessage::fluent(#slug),
143                             );
144                         }
145                     }
146                     (Some((SessionDiagnosticKind::Warn, _)), Some((slug, _))) => {
147                         quote! {
148                             let mut #diag = #sess.struct_warn(
149                                 rustc_errors::DiagnosticMessage::fluent(#slug),
150                             );
151                         }
152                     }
153                 };
154
155                 let implementation = quote! {
156                     #init
157                     #preamble
158                     match self {
159                         #attrs
160                     }
161                     match self {
162                         #args
163                     }
164                     #diag
165                 };
166                 let param_ty = match builder.kind {
167                     Some((SessionDiagnosticKind::Error, _)) => {
168                         quote! { rustc_errors::ErrorGuaranteed }
169                     }
170                     Some((SessionDiagnosticKind::Warn, _)) => quote! { () },
171                     _ => unreachable!(),
172                 };
173
174                 (implementation, param_ty)
175             } else {
176                 span_err(
177                     ast.span().unwrap(),
178                     "`#[derive(SessionDiagnostic)]` can only be used on structs",
179                 )
180                 .emit();
181
182                 let implementation = SessionDiagnosticDeriveError::ErrorHandled.to_compile_error();
183                 let param_ty = quote! { rustc_errors::ErrorGuaranteed };
184                 (implementation, param_ty)
185             }
186         };
187
188         let sess = &builder.sess;
189         structure.gen_impl(quote! {
190             gen impl<'__session_diagnostic_sess> rustc_session::SessionDiagnostic<'__session_diagnostic_sess, #param_ty>
191                     for @Self
192             {
193                 fn into_diagnostic(
194                     self,
195                     #sess: &'__session_diagnostic_sess rustc_session::parse::ParseSess
196                 ) -> rustc_errors::DiagnosticBuilder<'__session_diagnostic_sess, #param_ty> {
197                     use rustc_errors::IntoDiagnosticArg;
198                     #implementation
199                 }
200             }
201         })
202     }
203 }
204
205 /// What kind of session diagnostic is being derived - an error or a warning?
206 #[derive(Copy, Clone)]
207 enum SessionDiagnosticKind {
208     /// `#[error(..)]`
209     Error,
210     /// `#[warn(..)]`
211     Warn,
212 }
213
214 impl SessionDiagnosticKind {
215     /// Returns human-readable string corresponding to the kind.
216     fn descr(&self) -> &'static str {
217         match self {
218             SessionDiagnosticKind::Error => "error",
219             SessionDiagnosticKind::Warn => "warning",
220         }
221     }
222 }
223
224 /// Tracks persistent information required for building up the individual calls to diagnostic
225 /// methods for the final generated method. This is a separate struct to `SessionDiagnosticDerive`
226 /// only to be able to destructure and split `self.builder` and the `self.structure` up to avoid a
227 /// double mut borrow later on.
228 struct SessionDiagnosticDeriveBuilder {
229     /// Name of the session parameter that's passed in to the `as_error` method.
230     sess: syn::Ident,
231     /// The identifier to use for the generated `DiagnosticBuilder` instance.
232     diag: syn::Ident,
233
234     /// Store a map of field name to its corresponding field. This is built on construction of the
235     /// derive builder.
236     fields: HashMap<String, TokenStream>,
237
238     /// Kind of diagnostic requested via the struct attribute.
239     kind: Option<(SessionDiagnosticKind, proc_macro::Span)>,
240     /// Slug is a mandatory part of the struct attribute as corresponds to the Fluent message that
241     /// has the actual diagnostic message.
242     slug: Option<(String, proc_macro::Span)>,
243     /// Error codes are a optional part of the struct attribute - this is only set to detect
244     /// multiple specifications.
245     code: Option<(String, proc_macro::Span)>,
246 }
247
248 impl HasFieldMap for SessionDiagnosticDeriveBuilder {
249     fn get_field_binding(&self, field: &String) -> Option<&TokenStream> {
250         self.fields.get(field)
251     }
252 }
253
254 impl SessionDiagnosticDeriveBuilder {
255     /// Establishes state in the `SessionDiagnosticDeriveBuilder` resulting from the struct
256     /// attributes like `#[error(..)#`, such as the diagnostic kind and slug. Generates
257     /// diagnostic builder calls for setting error code and creating note/help messages.
258     fn generate_structure_code(
259         &mut self,
260         attr: &Attribute,
261     ) -> Result<TokenStream, SessionDiagnosticDeriveError> {
262         let span = attr.span().unwrap();
263
264         let name = attr.path.segments.last().unwrap().ident.to_string();
265         let name = name.as_str();
266         let meta = attr.parse_meta()?;
267
268         if matches!(name, "help" | "note") && matches!(meta, Meta::Path(_) | Meta::NameValue(_)) {
269             let diag = &self.diag;
270             let slug = match &self.slug {
271                 Some((slug, _)) => slug.as_str(),
272                 None => throw_span_err!(
273                     span,
274                     &format!(
275                         "`#[{}{}]` must come after `#[error(..)]` or `#[warn(..)]`",
276                         name,
277                         match meta {
278                             Meta::Path(_) => "",
279                             Meta::NameValue(_) => " = ...",
280                             _ => unreachable!(),
281                         }
282                     )
283                 ),
284             };
285             let id = match meta {
286                 Meta::Path(..) => quote! { #name },
287                 Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
288                     quote! { #s }
289                 }
290                 _ => unreachable!(),
291             };
292             let fn_name = proc_macro2::Ident::new(name, attr.span());
293
294             return Ok(quote! {
295                 #diag.#fn_name(rustc_errors::DiagnosticMessage::fluent_attr(#slug, #id));
296             });
297         }
298
299         let nested = match meta {
300             Meta::List(MetaList { ref nested, .. }) => nested,
301             _ => throw_invalid_attr!(attr, &meta),
302         };
303
304         let kind = match name {
305             "error" => SessionDiagnosticKind::Error,
306             "warning" => SessionDiagnosticKind::Warn,
307             _ => throw_invalid_attr!(attr, &meta, |diag| {
308                 diag.help("only `error` and `warning` are valid attributes")
309             }),
310         };
311         self.kind.set_once((kind, span));
312
313         let mut tokens = Vec::new();
314         for nested_attr in nested {
315             let meta = match nested_attr {
316                 syn::NestedMeta::Meta(meta) => meta,
317                 _ => throw_invalid_nested_attr!(attr, &nested_attr),
318             };
319
320             let path = meta.path();
321             let nested_name = path.segments.last().unwrap().ident.to_string();
322             match &meta {
323                 // Struct attributes are only allowed to be applied once, and the diagnostic
324                 // changes will be set in the initialisation code.
325                 Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
326                     let span = s.span().unwrap();
327                     match nested_name.as_str() {
328                         "slug" => {
329                             self.slug.set_once((s.value(), span));
330                         }
331                         "code" => {
332                             self.code.set_once((s.value(), span));
333                             let (diag, code) = (&self.diag, &self.code.as_ref().map(|(v, _)| v));
334                             tokens.push(quote! {
335                                 #diag.code(rustc_errors::DiagnosticId::Error(#code.to_string()));
336                             });
337                         }
338                         _ => invalid_nested_attr(attr, &nested_attr)
339                             .help("only `slug` and `code` are valid nested attributes")
340                             .emit(),
341                     }
342                 }
343                 _ => invalid_nested_attr(attr, &nested_attr).emit(),
344             }
345         }
346
347         Ok(tokens.drain(..).collect())
348     }
349
350     fn generate_field_attr_code(
351         &mut self,
352         attr: &syn::Attribute,
353         info: FieldInfo<'_>,
354     ) -> Result<TokenStream, SessionDiagnosticDeriveError> {
355         let field_binding = &info.binding.binding;
356         let option_ty = option_inner_ty(&info.ty);
357         let generated_code = self.generate_non_option_field_code(
358             attr,
359             FieldInfo {
360                 vis: info.vis,
361                 binding: info.binding,
362                 ty: option_ty.unwrap_or(&info.ty),
363                 span: info.span,
364             },
365         )?;
366
367         if option_ty.is_none() {
368             Ok(quote! { #generated_code })
369         } else {
370             Ok(quote! {
371                 if let Some(#field_binding) = #field_binding {
372                     #generated_code
373                 }
374             })
375         }
376     }
377
378     fn generate_non_option_field_code(
379         &mut self,
380         attr: &Attribute,
381         info: FieldInfo<'_>,
382     ) -> Result<TokenStream, SessionDiagnosticDeriveError> {
383         let diag = &self.diag;
384         let field_binding = &info.binding.binding;
385
386         let name = attr.path.segments.last().unwrap().ident.to_string();
387         let name = name.as_str();
388
389         let meta = attr.parse_meta()?;
390         match meta {
391             Meta::Path(_) => match name {
392                 "skip_arg" => {
393                     // Don't need to do anything - by virtue of the attribute existing, the
394                     // `set_arg` call will not be generated.
395                     Ok(quote! {})
396                 }
397                 "primary_span" => {
398                     report_error_if_not_applied_to_span(attr, &info)?;
399                     Ok(quote! {
400                         #diag.set_span(*#field_binding);
401                     })
402                 }
403                 "label" | "note" | "help" => {
404                     report_error_if_not_applied_to_span(attr, &info)?;
405                     Ok(self.add_subdiagnostic(field_binding, name, name))
406                 }
407                 "subdiagnostic" => Ok(quote! { #diag.subdiagnostic(*#field_binding); }),
408                 _ => throw_invalid_attr!(attr, &meta, |diag| {
409                     diag
410                         .help("only `skip_arg`, `primary_span`, `label`, `note`, `help` and `subdiagnostic` are valid field attributes")
411                 }),
412             },
413             Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(ref s), .. }) => match name {
414                 "label" | "note" | "help" => {
415                     report_error_if_not_applied_to_span(attr, &info)?;
416                     Ok(self.add_subdiagnostic(field_binding, name, &s.value()))
417                 }
418                 _ => throw_invalid_attr!(attr, &meta, |diag| {
419                     diag.help("only `label`, `note` and `help` are valid field attributes")
420                 }),
421             },
422             Meta::List(MetaList { ref path, ref nested, .. }) => {
423                 let name = path.segments.last().unwrap().ident.to_string();
424                 let name = name.as_ref();
425
426                 match name {
427                     "suggestion" | "suggestion_short" | "suggestion_hidden"
428                     | "suggestion_verbose" => (),
429                     _ => throw_invalid_attr!(attr, &meta, |diag| {
430                         diag
431                             .help("only `suggestion{,_short,_hidden,_verbose}` are valid field attributes")
432                     }),
433                 };
434
435                 let (span_field, mut applicability) = self.span_and_applicability_of_ty(info)?;
436
437                 let mut msg = None;
438                 let mut code = None;
439
440                 for nested_attr in nested {
441                     let meta = match nested_attr {
442                         syn::NestedMeta::Meta(ref meta) => meta,
443                         syn::NestedMeta::Lit(_) => throw_invalid_nested_attr!(attr, &nested_attr),
444                     };
445
446                     let nested_name = meta.path().segments.last().unwrap().ident.to_string();
447                     let nested_name = nested_name.as_str();
448                     match meta {
449                         Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
450                             let span = meta.span().unwrap();
451                             match nested_name {
452                                 "message" => {
453                                     msg = Some(s.value());
454                                 }
455                                 "code" => {
456                                     let formatted_str = self.build_format(&s.value(), s.span());
457                                     code = Some(formatted_str);
458                                 }
459                                 "applicability" => {
460                                     applicability = match applicability {
461                                         Some(v) => {
462                                             span_err(
463                                                 span,
464                                                 "applicability cannot be set in both the field and attribute"
465                                             ).emit();
466                                             Some(v)
467                                         }
468                                         None => match Applicability::from_str(&s.value()) {
469                                             Ok(v) => Some(quote! { #v }),
470                                             Err(()) => {
471                                                 span_err(span, "invalid applicability").emit();
472                                                 None
473                                             }
474                                         },
475                                     }
476                                 }
477                                 _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
478                                     diag.help(
479                                         "only `message`, `code` and `applicability` are valid field attributes",
480                                     )
481                                 }),
482                             }
483                         }
484                         _ => throw_invalid_nested_attr!(attr, &nested_attr),
485                     }
486                 }
487
488                 let applicability = applicability
489                     .unwrap_or_else(|| quote!(rustc_errors::Applicability::Unspecified));
490
491                 let method = format_ident!("span_{}", name);
492
493                 let slug = self
494                     .slug
495                     .as_ref()
496                     .map(|(slug, _)| slug.as_str())
497                     .unwrap_or_else(|| "missing-slug");
498                 let msg = msg.as_deref().unwrap_or("suggestion");
499                 let msg = quote! { rustc_errors::DiagnosticMessage::fluent_attr(#slug, #msg) };
500                 let code = code.unwrap_or_else(|| quote! { String::new() });
501
502                 Ok(quote! { #diag.#method(#span_field, #msg, #code, #applicability); })
503             }
504             _ => throw_invalid_attr!(attr, &meta),
505         }
506     }
507
508     /// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current slug and
509     /// `fluent_attr_identifier`.
510     fn add_subdiagnostic(
511         &self,
512         field_binding: &proc_macro2::Ident,
513         kind: &str,
514         fluent_attr_identifier: &str,
515     ) -> TokenStream {
516         let diag = &self.diag;
517
518         let slug =
519             self.slug.as_ref().map(|(slug, _)| slug.as_str()).unwrap_or_else(|| "missing-slug");
520         let fn_name = format_ident!("span_{}", kind);
521         quote! {
522             #diag.#fn_name(
523                 *#field_binding,
524                 rustc_errors::DiagnosticMessage::fluent_attr(#slug, #fluent_attr_identifier)
525             );
526         }
527     }
528
529     fn span_and_applicability_of_ty(
530         &self,
531         info: FieldInfo<'_>,
532     ) -> Result<(TokenStream, Option<TokenStream>), SessionDiagnosticDeriveError> {
533         match &info.ty {
534             // If `ty` is `Span` w/out applicability, then use `Applicability::Unspecified`.
535             ty @ Type::Path(..) if type_matches_path(ty, &["rustc_span", "Span"]) => {
536                 let binding = &info.binding.binding;
537                 Ok((quote!(*#binding), None))
538             }
539             // If `ty` is `(Span, Applicability)` then return tokens accessing those.
540             Type::Tuple(tup) => {
541                 let mut span_idx = None;
542                 let mut applicability_idx = None;
543
544                 for (idx, elem) in tup.elems.iter().enumerate() {
545                     if type_matches_path(elem, &["rustc_span", "Span"]) {
546                         if span_idx.is_none() {
547                             span_idx = Some(syn::Index::from(idx));
548                         } else {
549                             throw_span_err!(
550                                 info.span.unwrap(),
551                                 "type of field annotated with `#[suggestion(...)]` contains more than one `Span`"
552                             );
553                         }
554                     } else if type_matches_path(elem, &["rustc_errors", "Applicability"]) {
555                         if applicability_idx.is_none() {
556                             applicability_idx = Some(syn::Index::from(idx));
557                         } else {
558                             throw_span_err!(
559                                 info.span.unwrap(),
560                                 "type of field annotated with `#[suggestion(...)]` contains more than one Applicability"
561                             );
562                         }
563                     }
564                 }
565
566                 if let Some(span_idx) = span_idx {
567                     let binding = &info.binding.binding;
568                     let span = quote!(#binding.#span_idx);
569                     let applicability = applicability_idx
570                         .map(|applicability_idx| quote!(#binding.#applicability_idx))
571                         .unwrap_or_else(|| quote!(rustc_errors::Applicability::Unspecified));
572
573                     return Ok((span, Some(applicability)));
574                 }
575
576                 throw_span_err!(info.span.unwrap(), "wrong types for suggestion", |diag| {
577                     diag.help("`#[suggestion(...)]` on a tuple field must be applied to fields of type `(Span, Applicability)`")
578                 });
579             }
580             // If `ty` isn't a `Span` or `(Span, Applicability)` then emit an error.
581             _ => throw_span_err!(info.span.unwrap(), "wrong field type for suggestion", |diag| {
582                 diag.help("`#[suggestion(...)]` should be applied to fields of type `Span` or `(Span, Applicability)`")
583             }),
584         }
585     }
586 }