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