]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_macros/src/session_diagnostic.rs
Auto merge of #81635 - michaelwoerister:structured_def_path_hash, r=pnkfelix
[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, independent
9 /// from the actual diagnostics emitting code.
10 /// ```ignore (pseudo-rust)
11 /// # extern crate rustc_errors;
12 /// # use rustc_errors::Applicability;
13 /// # extern crate rustc_span;
14 /// # use rustc_span::{symbol::Ident, Span};
15 /// # extern crate rust_middle;
16 /// # use rustc_middle::ty::Ty;
17 /// #[derive(SessionDiagnostic)]
18 /// #[code = "E0505"]
19 /// #[error = "cannot move out of {name} because it is borrowed"]
20 /// pub struct MoveOutOfBorrowError<'tcx> {
21 ///     pub name: Ident,
22 ///     pub ty: Ty<'tcx>,
23 ///     #[label = "cannot move out of borrow"]
24 ///     pub span: Span,
25 ///     #[label = "`{ty}` first borrowed here"]
26 ///     pub other_span: Span,
27 ///     #[suggestion(message = "consider cloning here", code = "{name}.clone()")]
28 ///     pub opt_sugg: Option<(Span, Applicability)>
29 /// }
30 /// ```
31 /// Then, later, to emit the error:
32 ///
33 /// ```ignore (pseudo-rust)
34 /// sess.emit_err(MoveOutOfBorrowError {
35 ///     expected,
36 ///     actual,
37 ///     span,
38 ///     other_span,
39 ///     opt_sugg: Some(suggestion, Applicability::MachineApplicable),
40 /// });
41 /// ```
42 pub fn session_diagnostic_derive(s: synstructure::Structure<'_>) -> proc_macro2::TokenStream {
43     // Names for the diagnostic we build and the session we build it from.
44     let diag = format_ident!("diag");
45     let sess = format_ident!("sess");
46
47     SessionDiagnosticDerive::new(diag, sess, s).into_tokens()
48 }
49
50 // Checks whether the type name of `ty` matches `name`.
51 //
52 // Given some struct at a::b::c::Foo, this will return true for c::Foo, b::c::Foo, or
53 // a::b::c::Foo. This reasonably allows qualified names to be used in the macro.
54 fn type_matches_path(ty: &syn::Type, name: &[&str]) -> bool {
55     if let syn::Type::Path(ty) = ty {
56         ty.path
57             .segments
58             .iter()
59             .map(|s| s.ident.to_string())
60             .rev()
61             .zip(name.iter().rev())
62             .all(|(x, y)| &x.as_str() == y)
63     } else {
64         false
65     }
66 }
67
68 /// The central struct for constructing the as_error method from an annotated struct.
69 struct SessionDiagnosticDerive<'a> {
70     structure: synstructure::Structure<'a>,
71     builder: SessionDiagnosticDeriveBuilder<'a>,
72 }
73
74 impl std::convert::From<syn::Error> for SessionDiagnosticDeriveError {
75     fn from(e: syn::Error) -> Self {
76         SessionDiagnosticDeriveError::SynError(e)
77     }
78 }
79
80 /// Equivalent to rustc:errors::diagnostic::DiagnosticId, except stores the quoted expression to
81 /// initialise the code with.
82 enum DiagnosticId {
83     Error(proc_macro2::TokenStream),
84     Lint(proc_macro2::TokenStream),
85 }
86
87 #[derive(Debug)]
88 enum SessionDiagnosticDeriveError {
89     SynError(syn::Error),
90     ErrorHandled,
91 }
92
93 impl SessionDiagnosticDeriveError {
94     fn to_compile_error(self) -> proc_macro2::TokenStream {
95         match self {
96             SessionDiagnosticDeriveError::SynError(e) => e.to_compile_error(),
97             SessionDiagnosticDeriveError::ErrorHandled => {
98                 // Return ! to avoid having to create a blank DiagnosticBuilder to return when an
99                 // error has already been emitted to the compiler.
100                 quote! {
101                     unreachable!()
102                 }
103             }
104         }
105     }
106 }
107
108 fn span_err(span: impl proc_macro::MultiSpan, msg: &str) -> proc_macro::Diagnostic {
109     Diagnostic::spanned(span, proc_macro::Level::Error, msg)
110 }
111
112 /// For methods that return a Result<_, SessionDiagnosticDeriveError>: emit a diagnostic on
113 /// span $span with msg $msg (and, optionally, perform additional decoration using the FnOnce
114 /// passed in `diag`). Then, return Err(ErrorHandled).
115 macro_rules! throw_span_err {
116     ($span:expr, $msg:expr) => {{ throw_span_err!($span, $msg, |diag| diag) }};
117     ($span:expr, $msg:expr, $f:expr) => {{
118         return Err(_throw_span_err($span, $msg, $f));
119     }};
120 }
121
122 /// When possible, prefer using throw_span_err! over using this function directly. This only exists
123 /// as a function to constrain `f` to an impl FnOnce.
124 fn _throw_span_err(
125     span: impl proc_macro::MultiSpan,
126     msg: &str,
127     f: impl FnOnce(proc_macro::Diagnostic) -> proc_macro::Diagnostic,
128 ) -> SessionDiagnosticDeriveError {
129     let diag = span_err(span, msg);
130     f(diag).emit();
131     SessionDiagnosticDeriveError::ErrorHandled
132 }
133
134 impl<'a> SessionDiagnosticDerive<'a> {
135     fn new(diag: syn::Ident, sess: syn::Ident, structure: synstructure::Structure<'a>) -> Self {
136         // Build the mapping of field names to fields. This allows attributes to peek values from
137         // other fields.
138         let mut fields_map = HashMap::new();
139
140         // Convenience bindings.
141         let ast = structure.ast();
142
143         if let syn::Data::Struct(syn::DataStruct { fields, .. }) = &ast.data {
144             for field in fields.iter() {
145                 if let Some(ident) = &field.ident {
146                     fields_map.insert(ident.to_string(), field);
147                 }
148             }
149         }
150
151         Self {
152             builder: SessionDiagnosticDeriveBuilder { diag, sess, fields: fields_map, kind: None },
153             structure,
154         }
155     }
156     fn into_tokens(self) -> proc_macro2::TokenStream {
157         let SessionDiagnosticDerive { structure, mut builder } = self;
158
159         let ast = structure.ast();
160         let attrs = &ast.attrs;
161
162         let implementation = {
163             if let syn::Data::Struct(..) = ast.data {
164                 let preamble = {
165                     let preamble = attrs.iter().map(|attr| {
166                         builder
167                             .generate_structure_code(attr)
168                             .unwrap_or_else(|v| v.to_compile_error())
169                     });
170                     quote! {
171                         #(#preamble)*;
172                     }
173                 };
174
175                 let body = structure.each(|field_binding| {
176                     let field = field_binding.ast();
177                     let result = field.attrs.iter().map(|attr| {
178                         builder
179                             .generate_field_code(
180                                 attr,
181                                 FieldInfo {
182                                     vis: &field.vis,
183                                     binding: field_binding,
184                                     ty: &field.ty,
185                                     span: &field.span(),
186                                 },
187                             )
188                             .unwrap_or_else(|v| v.to_compile_error())
189                     });
190                     return quote! {
191                         #(#result);*
192                     };
193                 });
194                 // Finally, putting it altogether.
195                 match builder.kind {
196                     None => {
197                         span_err(ast.span().unwrap(), "`code` not specified")
198                         .help("use the [code = \"...\"] attribute to set this diagnostic's error code ")
199                         .emit();
200                         SessionDiagnosticDeriveError::ErrorHandled.to_compile_error()
201                     }
202                     Some((kind, _)) => match kind {
203                         DiagnosticId::Lint(_lint) => todo!(),
204                         DiagnosticId::Error(code) => {
205                             let (diag, sess) = (&builder.diag, &builder.sess);
206                             quote! {
207                                 let mut #diag = #sess.struct_err_with_code("", rustc_errors::DiagnosticId::Error(#code));
208                                 #preamble
209                                 match self {
210                                     #body
211                                 }
212                                 #diag
213                             }
214                         }
215                     },
216                 }
217             } else {
218                 span_err(
219                     ast.span().unwrap(),
220                     "`#[derive(SessionDiagnostic)]` can only be used on structs",
221                 )
222                 .emit();
223                 SessionDiagnosticDeriveError::ErrorHandled.to_compile_error()
224             }
225         };
226
227         let sess = &builder.sess;
228         structure.gen_impl(quote! {
229             gen impl<'__session_diagnostic_sess> rustc_session::SessionDiagnostic<'__session_diagnostic_sess>
230                     for @Self
231             {
232                 fn into_diagnostic(
233                     self,
234                     #sess: &'__session_diagnostic_sess rustc_session::Session
235                 ) -> rustc_errors::DiagnosticBuilder<'__session_diagnostic_sess> {
236                     #implementation
237                 }
238             }
239         })
240     }
241 }
242
243 /// Field information passed to the builder. Deliberately omits attrs to discourage the generate_*
244 /// methods from walking the attributes themselves.
245 struct FieldInfo<'a> {
246     vis: &'a syn::Visibility,
247     binding: &'a synstructure::BindingInfo<'a>,
248     ty: &'a syn::Type,
249     span: &'a proc_macro2::Span,
250 }
251
252 /// Tracks persistent information required for building up the individual calls to diagnostic
253 /// methods for the final generated method. This is a separate struct to SessionDerive only to be
254 /// able to destructure and split self.builder and the self.structure up to avoid a double mut
255 /// borrow later on.
256 struct SessionDiagnosticDeriveBuilder<'a> {
257     /// Name of the session parameter that's passed in to the as_error method.
258     sess: syn::Ident,
259
260     /// Store a map of field name to its corresponding field. This is built on construction of the
261     /// derive builder.
262     fields: HashMap<String, &'a syn::Field>,
263
264     /// The identifier to use for the generated DiagnosticBuilder instance.
265     diag: syn::Ident,
266
267     /// Whether this is a lint or an error. This dictates how the diag will be initialised. Span
268     /// stores at what Span the kind was first set at (for error reporting purposes, if the kind
269     /// was multiply specified).
270     kind: Option<(DiagnosticId, proc_macro2::Span)>,
271 }
272
273 impl<'a> SessionDiagnosticDeriveBuilder<'a> {
274     fn generate_structure_code(
275         &mut self,
276         attr: &syn::Attribute,
277     ) -> Result<proc_macro2::TokenStream, SessionDiagnosticDeriveError> {
278         Ok(match attr.parse_meta()? {
279             syn::Meta::NameValue(syn::MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
280                 let formatted_str = self.build_format(&s.value(), attr.span());
281                 let name = attr.path.segments.last().unwrap().ident.to_string();
282                 let name = name.as_str();
283                 match name {
284                     "message" => {
285                         let diag = &self.diag;
286                         quote! {
287                             #diag.set_primary_message(#formatted_str);
288                         }
289                     }
290                     attr @ "error" | attr @ "lint" => {
291                         self.set_kind_once(
292                             if attr == "error" {
293                                 DiagnosticId::Error(formatted_str)
294                             } else if attr == "lint" {
295                                 DiagnosticId::Lint(formatted_str)
296                             } else {
297                                 unreachable!()
298                             },
299                             s.span(),
300                         )?;
301                         // This attribute is only allowed to be applied once, and the attribute
302                         // will be set in the initialisation code.
303                         quote! {}
304                     }
305                     other => throw_span_err!(
306                         attr.span().unwrap(),
307                         &format!(
308                             "`#[{} = ...]` is not a valid SessionDiagnostic struct attribute",
309                             other
310                         )
311                     ),
312                 }
313             }
314             _ => todo!("unhandled meta kind"),
315         })
316     }
317
318     #[must_use]
319     fn set_kind_once(
320         &mut self,
321         kind: DiagnosticId,
322         span: proc_macro2::Span,
323     ) -> Result<(), SessionDiagnosticDeriveError> {
324         if self.kind.is_none() {
325             self.kind = Some((kind, span));
326             Ok(())
327         } else {
328             let kind_str = |kind: &DiagnosticId| match kind {
329                 DiagnosticId::Lint(..) => "lint",
330                 DiagnosticId::Error(..) => "error",
331             };
332
333             let existing_kind = kind_str(&self.kind.as_ref().unwrap().0);
334             let this_kind = kind_str(&kind);
335
336             let msg = if this_kind == existing_kind {
337                 format!("`{}` specified multiple times", existing_kind)
338             } else {
339                 format!("`{}` specified when `{}` was already specified", this_kind, existing_kind)
340             };
341             throw_span_err!(span.unwrap(), &msg);
342         }
343     }
344
345     fn generate_field_code(
346         &mut self,
347         attr: &syn::Attribute,
348         info: FieldInfo<'_>,
349     ) -> Result<proc_macro2::TokenStream, SessionDiagnosticDeriveError> {
350         let field_binding = &info.binding.binding;
351
352         let option_ty = option_inner_ty(&info.ty);
353
354         let generated_code = self.generate_non_option_field_code(
355             attr,
356             FieldInfo {
357                 vis: info.vis,
358                 binding: info.binding,
359                 ty: option_ty.unwrap_or(&info.ty),
360                 span: info.span,
361             },
362         )?;
363         Ok(if option_ty.is_none() {
364             quote! { #generated_code }
365         } else {
366             quote! {
367                 if let Some(#field_binding) = #field_binding {
368                     #generated_code
369                 }
370             }
371         })
372     }
373
374     fn generate_non_option_field_code(
375         &mut self,
376         attr: &syn::Attribute,
377         info: FieldInfo<'_>,
378     ) -> Result<proc_macro2::TokenStream, SessionDiagnosticDeriveError> {
379         let diag = &self.diag;
380         let field_binding = &info.binding.binding;
381         let name = attr.path.segments.last().unwrap().ident.to_string();
382         let name = name.as_str();
383         // At this point, we need to dispatch based on the attribute key + the
384         // type.
385         let meta = attr.parse_meta()?;
386         Ok(match meta {
387             syn::Meta::NameValue(syn::MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
388                 let formatted_str = self.build_format(&s.value(), attr.span());
389                 match name {
390                     "message" => {
391                         if type_matches_path(&info.ty, &["rustc_span", "Span"]) {
392                             quote! {
393                                 #diag.set_span(*#field_binding);
394                                 #diag.set_primary_message(#formatted_str);
395                             }
396                         } else {
397                             throw_span_err!(
398                                 attr.span().unwrap(),
399                                 "the `#[message = \"...\"]` attribute can only be applied to fields of type Span"
400                             );
401                         }
402                     }
403                     "label" => {
404                         if type_matches_path(&info.ty, &["rustc_span", "Span"]) {
405                             quote! {
406                                 #diag.span_label(*#field_binding, #formatted_str);
407                             }
408                         } else {
409                             throw_span_err!(
410                                 attr.span().unwrap(),
411                                 "The `#[label = ...]` attribute can only be applied to fields of type Span"
412                             );
413                         }
414                     }
415                     other => throw_span_err!(
416                         attr.span().unwrap(),
417                         &format!(
418                             "`#[{} = ...]` is not a valid SessionDiagnostic field attribute",
419                             other
420                         )
421                     ),
422                 }
423             }
424             syn::Meta::List(list) => {
425                 match list.path.segments.iter().last().unwrap().ident.to_string().as_str() {
426                     suggestion_kind @ "suggestion"
427                     | suggestion_kind @ "suggestion_short"
428                     | suggestion_kind @ "suggestion_hidden"
429                     | suggestion_kind @ "suggestion_verbose" => {
430                         // For suggest, we need to ensure we are running on a (Span,
431                         // Applicability) pair.
432                         let (span, applicability) = (|| match &info.ty {
433                             ty @ syn::Type::Path(..)
434                                 if type_matches_path(ty, &["rustc_span", "Span"]) =>
435                             {
436                                 let binding = &info.binding.binding;
437                                 Ok((
438                                     quote!(*#binding),
439                                     quote!(rustc_errors::Applicability::Unspecified),
440                                 ))
441                             }
442                             syn::Type::Tuple(tup) => {
443                                 let mut span_idx = None;
444                                 let mut applicability_idx = None;
445                                 for (idx, elem) in tup.elems.iter().enumerate() {
446                                     if type_matches_path(elem, &["rustc_span", "Span"]) {
447                                         if span_idx.is_none() {
448                                             span_idx = Some(syn::Index::from(idx));
449                                         } else {
450                                             throw_span_err!(
451                                                 info.span.clone().unwrap(),
452                                                 "type of field annotated with `#[suggestion(...)]` contains more than one Span"
453                                             );
454                                         }
455                                     } else if type_matches_path(
456                                         elem,
457                                         &["rustc_errors", "Applicability"],
458                                     ) {
459                                         if applicability_idx.is_none() {
460                                             applicability_idx = Some(syn::Index::from(idx));
461                                         } else {
462                                             throw_span_err!(
463                                                 info.span.clone().unwrap(),
464                                                 "type of field annotated with `#[suggestion(...)]` contains more than one Applicability"
465                                             );
466                                         }
467                                     }
468                                 }
469                                 if let Some(span_idx) = span_idx {
470                                     let binding = &info.binding.binding;
471                                     let span = quote!(#binding.#span_idx);
472                                     let applicability = applicability_idx
473                                         .map(
474                                             |applicability_idx| quote!(#binding.#applicability_idx),
475                                         )
476                                         .unwrap_or_else(|| {
477                                             quote!(rustc_errors::Applicability::Unspecified)
478                                         });
479                                     return Ok((span, applicability));
480                                 }
481                                 throw_span_err!(
482                                     info.span.clone().unwrap(),
483                                     "wrong types for suggestion",
484                                     |diag| {
485                                         diag.help("#[suggestion(...)] on a tuple field must be applied to fields of type (Span, Applicability)")
486                                     }
487                                 );
488                             }
489                             _ => throw_span_err!(
490                                 info.span.clone().unwrap(),
491                                 "wrong field type for suggestion",
492                                 |diag| {
493                                     diag.help("#[suggestion(...)] should be applied to fields of type Span or (Span, Applicability)")
494                                 }
495                             ),
496                         })()?;
497                         // Now read the key-value pairs.
498                         let mut msg = None;
499                         let mut code = None;
500
501                         for arg in list.nested.iter() {
502                             if let syn::NestedMeta::Meta(syn::Meta::NameValue(arg_name_value)) = arg
503                             {
504                                 if let syn::MetaNameValue { lit: syn::Lit::Str(s), .. } =
505                                     arg_name_value
506                                 {
507                                     let name = arg_name_value
508                                         .path
509                                         .segments
510                                         .last()
511                                         .unwrap()
512                                         .ident
513                                         .to_string();
514                                     let name = name.as_str();
515                                     let formatted_str = self.build_format(&s.value(), arg.span());
516                                     match name {
517                                         "message" => {
518                                             msg = Some(formatted_str);
519                                         }
520                                         "code" => {
521                                             code = Some(formatted_str);
522                                         }
523                                         other => throw_span_err!(
524                                             arg.span().unwrap(),
525                                             &format!(
526                                                 "`{}` is not a valid key for `#[suggestion(...)]`",
527                                                 other
528                                             )
529                                         ),
530                                     }
531                                 }
532                             }
533                         }
534                         let msg = if let Some(msg) = msg {
535                             quote!(#msg.as_str())
536                         } else {
537                             throw_span_err!(
538                                 list.span().unwrap(),
539                                 "missing suggestion message",
540                                 |diag| {
541                                     diag.help("provide a suggestion message using #[suggestion(message = \"...\")]")
542                                 }
543                             );
544                         };
545                         let code = code.unwrap_or_else(|| quote! { String::new() });
546                         // Now build it out:
547                         let suggestion_method = format_ident!("span_{}", suggestion_kind);
548                         quote! {
549                             #diag.#suggestion_method(#span, #msg, #code, #applicability);
550                         }
551                     }
552                     other => throw_span_err!(
553                         list.span().unwrap(),
554                         &format!("invalid annotation list `#[{}(...)]`", other)
555                     ),
556                 }
557             }
558             _ => panic!("unhandled meta kind"),
559         })
560     }
561
562     /// In the strings in the attributes supplied to this macro, we want callers to be able to
563     /// reference fields in the format string. Take this, for example:
564     /// ```ignore (not-usage-example)
565     /// struct Point {
566     ///     #[error = "Expected a point greater than ({x}, {y})"]
567     ///     x: i32,
568     ///     y: i32,
569     /// }
570     /// ```
571     /// We want to automatically pick up that {x} refers `self.x` and {y} refers to `self.y`, then
572     /// generate this call to format!:
573     /// ```ignore (not-usage-example)
574     /// format!("Expected a point greater than ({x}, {y})", x = self.x, y = self.y)
575     /// ```
576     /// This function builds the entire call to format!.
577     fn build_format(&self, input: &str, span: proc_macro2::Span) -> proc_macro2::TokenStream {
578         // This set is used later to generate the final format string. To keep builds reproducible,
579         // the iteration order needs to be deterministic, hence why we use a BTreeSet here instead
580         // of a HashSet.
581         let mut referenced_fields: BTreeSet<String> = BTreeSet::new();
582
583         // At this point, we can start parsing the format string.
584         let mut it = input.chars().peekable();
585         // Once the start of a format string has been found, process the format string and spit out
586         // the referenced fields. Leaves `it` sitting on the closing brace of the format string, so the
587         // next call to `it.next()` retrieves the next character.
588         while let Some(c) = it.next() {
589             if c == '{' && *it.peek().unwrap_or(&'\0') != '{' {
590                 #[must_use]
591                 let mut eat_argument = || -> Option<String> {
592                     let mut result = String::new();
593                     // Format specifiers look like
594                     // format   := '{' [ argument ] [ ':' format_spec ] '}' .
595                     // Therefore, we only need to eat until ':' or '}' to find the argument.
596                     while let Some(c) = it.next() {
597                         result.push(c);
598                         let next = *it.peek().unwrap_or(&'\0');
599                         if next == '}' {
600                             break;
601                         } else if next == ':' {
602                             // Eat the ':' character.
603                             assert_eq!(it.next().unwrap(), ':');
604                             break;
605                         }
606                     }
607                     // Eat until (and including) the matching '}'
608                     while it.next()? != '}' {
609                         continue;
610                     }
611                     Some(result)
612                 };
613
614                 if let Some(referenced_field) = eat_argument() {
615                     referenced_fields.insert(referenced_field);
616                 }
617             }
618         }
619         // At this point, `referenced_fields` contains a set of the unique fields that were
620         // referenced in the format string. Generate the corresponding "x = self.x" format
621         // string parameters:
622         let args = referenced_fields.into_iter().map(|field: String| {
623             let field_ident = format_ident!("{}", field);
624             let value = if self.fields.contains_key(&field) {
625                 quote! {
626                     &self.#field_ident
627                 }
628             } else {
629                 // This field doesn't exist. Emit a diagnostic.
630                 Diagnostic::spanned(
631                     span.unwrap(),
632                     proc_macro::Level::Error,
633                     format!("`{}` doesn't refer to a field on this type", field),
634                 )
635                 .emit();
636                 quote! {
637                     "{#field}"
638                 }
639             };
640             quote! {
641                 #field_ident = #value
642             }
643         });
644         quote! {
645             format!(#input #(,#args)*)
646         }
647     }
648 }
649
650 /// If `ty` is an Option, returns Some(inner type). Else, returns None.
651 fn option_inner_ty(ty: &syn::Type) -> Option<&syn::Type> {
652     if type_matches_path(ty, &["std", "option", "Option"]) {
653         if let syn::Type::Path(ty_path) = ty {
654             let path = &ty_path.path;
655             let ty = path.segments.iter().last().unwrap();
656             if let syn::PathArguments::AngleBracketed(bracketed) = &ty.arguments {
657                 if bracketed.args.len() == 1 {
658                     if let syn::GenericArgument::Type(ty) = &bracketed.args[0] {
659                         return Some(ty);
660                     }
661                 }
662             }
663         }
664     }
665     None
666 }