]> git.proxmox.com Git - rustc.git/blob - compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
New upstream version 1.66.0+dfsg1
[rustc.git] / compiler / rustc_macros / src / diagnostics / diagnostic_builder.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 DiagnosticDeriveError,
6 };
7 use crate::diagnostics::utils::{
8 build_field_mapping, is_doc_comment, report_error_if_not_applied_to_span, report_type_error,
9 should_generate_set_arg, type_is_unit, type_matches_path, FieldInfo, FieldInnerTy, FieldMap,
10 HasFieldMap, SetOnce, SpannedOption, SubdiagnosticKind,
11 };
12 use proc_macro2::{Ident, Span, TokenStream};
13 use quote::{format_ident, quote};
14 use syn::{
15 parse_quote, spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, NestedMeta, Path, Type,
16 };
17 use synstructure::{BindingInfo, Structure, VariantInfo};
18
19 /// What kind of diagnostic is being derived - a fatal/error/warning or a lint?
20 #[derive(Clone, PartialEq, Eq)]
21 pub(crate) enum DiagnosticDeriveKind {
22 Diagnostic { handler: syn::Ident },
23 LintDiagnostic,
24 }
25
26 /// Tracks persistent information required for the entire type when building up individual calls to
27 /// diagnostic methods for generated diagnostic derives - both `Diagnostic` for
28 /// fatal/errors/warnings and `LintDiagnostic` for lints.
29 pub(crate) struct DiagnosticDeriveBuilder {
30 /// The identifier to use for the generated `DiagnosticBuilder` instance.
31 pub diag: syn::Ident,
32 /// Kind of diagnostic that should be derived.
33 pub kind: DiagnosticDeriveKind,
34 }
35
36 /// Tracks persistent information required for a specific variant when building up individual calls
37 /// to diagnostic methods for generated diagnostic derives - both `Diagnostic` for
38 /// fatal/errors/warnings and `LintDiagnostic` for lints.
39 pub(crate) struct DiagnosticDeriveVariantBuilder<'parent> {
40 /// The parent builder for the entire type.
41 pub parent: &'parent DiagnosticDeriveBuilder,
42
43 /// Initialization of format strings for code suggestions.
44 pub formatting_init: TokenStream,
45
46 /// Span of the struct or the enum variant.
47 pub span: proc_macro::Span,
48
49 /// Store a map of field name to its corresponding field. This is built on construction of the
50 /// derive builder.
51 pub field_map: FieldMap,
52
53 /// Slug is a mandatory part of the struct attribute as corresponds to the Fluent message that
54 /// has the actual diagnostic message.
55 pub slug: SpannedOption<Path>,
56 /// Error codes are a optional part of the struct attribute - this is only set to detect
57 /// multiple specifications.
58 pub code: SpannedOption<()>,
59 }
60
61 impl<'a> HasFieldMap for DiagnosticDeriveVariantBuilder<'a> {
62 fn get_field_binding(&self, field: &String) -> Option<&TokenStream> {
63 self.field_map.get(field)
64 }
65 }
66
67 impl DiagnosticDeriveBuilder {
68 /// Call `f` for the struct or for each variant of the enum, returning a `TokenStream` with the
69 /// tokens from `f` wrapped in an `match` expression. Emits errors for use of derive on unions
70 /// or attributes on the type itself when input is an enum.
71 pub fn each_variant<'s, F>(&mut self, structure: &mut Structure<'s>, f: F) -> TokenStream
72 where
73 F: for<'a, 'v> Fn(DiagnosticDeriveVariantBuilder<'a>, &VariantInfo<'v>) -> TokenStream,
74 {
75 let ast = structure.ast();
76 let span = ast.span().unwrap();
77 match ast.data {
78 syn::Data::Struct(..) | syn::Data::Enum(..) => (),
79 syn::Data::Union(..) => {
80 span_err(span, "diagnostic derives can only be used on structs and enums");
81 }
82 }
83
84 if matches!(ast.data, syn::Data::Enum(..)) {
85 for attr in &ast.attrs {
86 span_err(
87 attr.span().unwrap(),
88 "unsupported type attribute for diagnostic derive enum",
89 )
90 .emit();
91 }
92 }
93
94 structure.bind_with(|_| synstructure::BindStyle::Move);
95 let variants = structure.each_variant(|variant| {
96 let span = match structure.ast().data {
97 syn::Data::Struct(..) => span,
98 // There isn't a good way to get the span of the variant, so the variant's
99 // name will need to do.
100 _ => variant.ast().ident.span().unwrap(),
101 };
102 let builder = DiagnosticDeriveVariantBuilder {
103 parent: &self,
104 span,
105 field_map: build_field_mapping(variant),
106 formatting_init: TokenStream::new(),
107 slug: None,
108 code: None,
109 };
110 f(builder, variant)
111 });
112
113 quote! {
114 match self {
115 #variants
116 }
117 }
118 }
119 }
120
121 impl<'a> DiagnosticDeriveVariantBuilder<'a> {
122 /// Generates calls to `code` and similar functions based on the attributes on the type or
123 /// variant.
124 pub fn preamble<'s>(&mut self, variant: &VariantInfo<'s>) -> TokenStream {
125 let ast = variant.ast();
126 let attrs = &ast.attrs;
127 let preamble = attrs.iter().map(|attr| {
128 self.generate_structure_code_for_attr(attr).unwrap_or_else(|v| v.to_compile_error())
129 });
130
131 quote! {
132 #(#preamble)*;
133 }
134 }
135
136 /// Generates calls to `span_label` and similar functions based on the attributes on fields or
137 /// calls to `set_arg` when no attributes are present.
138 pub fn body<'s>(&mut self, variant: &VariantInfo<'s>) -> TokenStream {
139 let mut body = quote! {};
140 // Generate `set_arg` calls first..
141 for binding in variant.bindings().iter().filter(|bi| should_generate_set_arg(bi.ast())) {
142 body.extend(self.generate_field_code(binding));
143 }
144 // ..and then subdiagnostic additions.
145 for binding in variant.bindings().iter().filter(|bi| !should_generate_set_arg(bi.ast())) {
146 body.extend(self.generate_field_attrs_code(binding));
147 }
148 body
149 }
150
151 /// Parse a `SubdiagnosticKind` from an `Attribute`.
152 fn parse_subdiag_attribute(
153 &self,
154 attr: &Attribute,
155 ) -> Result<Option<(SubdiagnosticKind, Path)>, DiagnosticDeriveError> {
156 let Some((subdiag, slug)) = SubdiagnosticKind::from_attr(attr, self)? else {
157 // Some attributes aren't errors - like documentation comments - but also aren't
158 // subdiagnostics.
159 return Ok(None);
160 };
161
162 if let SubdiagnosticKind::MultipartSuggestion { .. } = subdiag {
163 let meta = attr.parse_meta()?;
164 throw_invalid_attr!(attr, &meta, |diag| diag
165 .help("consider creating a `Subdiagnostic` instead"));
166 }
167
168 let slug = slug.unwrap_or_else(|| match subdiag {
169 SubdiagnosticKind::Label => parse_quote! { _subdiag::label },
170 SubdiagnosticKind::Note => parse_quote! { _subdiag::note },
171 SubdiagnosticKind::Help => parse_quote! { _subdiag::help },
172 SubdiagnosticKind::Warn => parse_quote! { _subdiag::warn },
173 SubdiagnosticKind::Suggestion { .. } => parse_quote! { _subdiag::suggestion },
174 SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(),
175 });
176
177 Ok(Some((subdiag, slug)))
178 }
179
180 /// Establishes state in the `DiagnosticDeriveBuilder` resulting from the struct
181 /// attributes like `#[diag(..)]`, such as the slug and error code. Generates
182 /// diagnostic builder calls for setting error code and creating note/help messages.
183 fn generate_structure_code_for_attr(
184 &mut self,
185 attr: &Attribute,
186 ) -> Result<TokenStream, DiagnosticDeriveError> {
187 let diag = &self.parent.diag;
188
189 // Always allow documentation comments.
190 if is_doc_comment(attr) {
191 return Ok(quote! {});
192 }
193
194 let name = attr.path.segments.last().unwrap().ident.to_string();
195 let name = name.as_str();
196 let meta = attr.parse_meta()?;
197
198 if name == "diag" {
199 let Meta::List(MetaList { ref nested, .. }) = meta else {
200 throw_invalid_attr!(
201 attr,
202 &meta
203 );
204 };
205
206 let mut nested_iter = nested.into_iter().peekable();
207
208 match nested_iter.peek() {
209 Some(NestedMeta::Meta(Meta::Path(slug))) => {
210 self.slug.set_once(slug.clone(), slug.span().unwrap());
211 nested_iter.next();
212 }
213 Some(NestedMeta::Meta(Meta::NameValue { .. })) => {}
214 Some(nested_attr) => throw_invalid_nested_attr!(attr, &nested_attr, |diag| diag
215 .help("a diagnostic slug is required as the first argument")),
216 None => throw_invalid_attr!(attr, &meta, |diag| diag
217 .help("a diagnostic slug is required as the first argument")),
218 };
219
220 // Remaining attributes are optional, only `code = ".."` at the moment.
221 let mut tokens = TokenStream::new();
222 for nested_attr in nested_iter {
223 let (value, path) = match nested_attr {
224 NestedMeta::Meta(Meta::NameValue(MetaNameValue {
225 lit: syn::Lit::Str(value),
226 path,
227 ..
228 })) => (value, path),
229 NestedMeta::Meta(Meta::Path(_)) => {
230 invalid_nested_attr(attr, &nested_attr)
231 .help("diagnostic slug must be the first argument")
232 .emit();
233 continue;
234 }
235 _ => {
236 invalid_nested_attr(attr, &nested_attr).emit();
237 continue;
238 }
239 };
240
241 let nested_name = path.segments.last().unwrap().ident.to_string();
242 // Struct attributes are only allowed to be applied once, and the diagnostic
243 // changes will be set in the initialisation code.
244 let span = value.span().unwrap();
245 match nested_name.as_str() {
246 "code" => {
247 self.code.set_once((), span);
248
249 let code = value.value();
250 tokens.extend(quote! {
251 #diag.code(rustc_errors::DiagnosticId::Error(#code.to_string()));
252 });
253 }
254 _ => invalid_nested_attr(attr, &nested_attr)
255 .help("only `code` is a valid nested attributes following the slug")
256 .emit(),
257 }
258 }
259 return Ok(tokens);
260 }
261
262 let Some((subdiag, slug)) = self.parse_subdiag_attribute(attr)? else {
263 // Some attributes aren't errors - like documentation comments - but also aren't
264 // subdiagnostics.
265 return Ok(quote! {});
266 };
267 let fn_ident = format_ident!("{}", subdiag);
268 match subdiag {
269 SubdiagnosticKind::Note | SubdiagnosticKind::Help | SubdiagnosticKind::Warn => {
270 Ok(self.add_subdiagnostic(&fn_ident, slug))
271 }
272 SubdiagnosticKind::Label | SubdiagnosticKind::Suggestion { .. } => {
273 throw_invalid_attr!(attr, &meta, |diag| diag
274 .help("`#[label]` and `#[suggestion]` can only be applied to fields"));
275 }
276 SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(),
277 }
278 }
279
280 fn generate_field_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream {
281 let diag = &self.parent.diag;
282
283 let field = binding_info.ast();
284 let field_binding = &binding_info.binding;
285
286 let ident = field.ident.as_ref().unwrap();
287 let ident = format_ident!("{}", ident); // strip `r#` prefix, if present
288
289 quote! {
290 #diag.set_arg(
291 stringify!(#ident),
292 #field_binding
293 );
294 }
295 }
296
297 fn generate_field_attrs_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream {
298 let field = binding_info.ast();
299 let field_binding = &binding_info.binding;
300
301 let inner_ty = FieldInnerTy::from_type(&field.ty);
302
303 field
304 .attrs
305 .iter()
306 .map(move |attr| {
307 // Always allow documentation comments.
308 if is_doc_comment(attr) {
309 return quote! {};
310 }
311
312 let name = attr.path.segments.last().unwrap().ident.to_string();
313 let needs_clone =
314 name == "primary_span" && matches!(inner_ty, FieldInnerTy::Vec(_));
315 let (binding, needs_destructure) = if needs_clone {
316 // `primary_span` can accept a `Vec<Span>` so don't destructure that.
317 (quote! { #field_binding.clone() }, false)
318 } else {
319 (quote! { #field_binding }, true)
320 };
321
322 let generated_code = self
323 .generate_inner_field_code(
324 attr,
325 FieldInfo {
326 binding: binding_info,
327 ty: inner_ty.inner_type().unwrap_or(&field.ty),
328 span: &field.span(),
329 },
330 binding,
331 )
332 .unwrap_or_else(|v| v.to_compile_error());
333
334 if needs_destructure {
335 inner_ty.with(field_binding, generated_code)
336 } else {
337 generated_code
338 }
339 })
340 .collect()
341 }
342
343 fn generate_inner_field_code(
344 &mut self,
345 attr: &Attribute,
346 info: FieldInfo<'_>,
347 binding: TokenStream,
348 ) -> Result<TokenStream, DiagnosticDeriveError> {
349 let diag = &self.parent.diag;
350 let meta = attr.parse_meta()?;
351
352 let ident = &attr.path.segments.last().unwrap().ident;
353 let name = ident.to_string();
354 match (&meta, name.as_str()) {
355 // Don't need to do anything - by virtue of the attribute existing, the
356 // `set_arg` call will not be generated.
357 (Meta::Path(_), "skip_arg") => return Ok(quote! {}),
358 (Meta::Path(_), "primary_span") => {
359 match self.parent.kind {
360 DiagnosticDeriveKind::Diagnostic { .. } => {
361 report_error_if_not_applied_to_span(attr, &info)?;
362
363 return Ok(quote! {
364 #diag.set_span(#binding);
365 });
366 }
367 DiagnosticDeriveKind::LintDiagnostic => {
368 throw_invalid_attr!(attr, &meta, |diag| {
369 diag.help("the `primary_span` field attribute is not valid for lint diagnostics")
370 })
371 }
372 }
373 }
374 (Meta::Path(_), "subdiagnostic") => {
375 return Ok(quote! { #diag.subdiagnostic(#binding); });
376 }
377 (Meta::NameValue(_), "subdiagnostic") => {
378 throw_invalid_attr!(attr, &meta, |diag| {
379 diag.help("`eager` is the only supported nested attribute for `subdiagnostic`")
380 })
381 }
382 (Meta::List(MetaList { ref nested, .. }), "subdiagnostic") => {
383 if nested.len() != 1 {
384 throw_invalid_attr!(attr, &meta, |diag| {
385 diag.help(
386 "`eager` is the only supported nested attribute for `subdiagnostic`",
387 )
388 })
389 }
390
391 let handler = match &self.parent.kind {
392 DiagnosticDeriveKind::Diagnostic { handler } => handler,
393 DiagnosticDeriveKind::LintDiagnostic => {
394 throw_invalid_attr!(attr, &meta, |diag| {
395 diag.help("eager subdiagnostics are not supported on lints")
396 })
397 }
398 };
399
400 let nested_attr = nested.first().expect("pop failed for single element list");
401 match nested_attr {
402 NestedMeta::Meta(meta @ Meta::Path(_))
403 if meta.path().segments.last().unwrap().ident.to_string().as_str()
404 == "eager" =>
405 {
406 return Ok(quote! { #diag.eager_subdiagnostic(#handler, #binding); });
407 }
408 _ => {
409 throw_invalid_nested_attr!(attr, nested_attr, |diag| {
410 diag.help("`eager` is the only supported nested attribute for `subdiagnostic`")
411 })
412 }
413 }
414 }
415 _ => (),
416 }
417
418 let Some((subdiag, slug)) = self.parse_subdiag_attribute(attr)? else {
419 // Some attributes aren't errors - like documentation comments - but also aren't
420 // subdiagnostics.
421 return Ok(quote! {});
422 };
423 let fn_ident = format_ident!("{}", subdiag);
424 match subdiag {
425 SubdiagnosticKind::Label => {
426 report_error_if_not_applied_to_span(attr, &info)?;
427 Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, slug))
428 }
429 SubdiagnosticKind::Note | SubdiagnosticKind::Help | SubdiagnosticKind::Warn => {
430 if type_matches_path(&info.ty, &["rustc_span", "Span"]) {
431 Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, slug))
432 } else if type_is_unit(&info.ty) {
433 Ok(self.add_subdiagnostic(&fn_ident, slug))
434 } else {
435 report_type_error(attr, "`Span` or `()`")?
436 }
437 }
438 SubdiagnosticKind::Suggestion {
439 suggestion_kind,
440 applicability: static_applicability,
441 code_field,
442 code_init,
443 } => {
444 let (span_field, mut applicability) = self.span_and_applicability_of_ty(info)?;
445
446 if let Some((static_applicability, span)) = static_applicability {
447 applicability.set_once(quote! { #static_applicability }, span);
448 }
449
450 let applicability = applicability
451 .value()
452 .unwrap_or_else(|| quote! { rustc_errors::Applicability::Unspecified });
453 let style = suggestion_kind.to_suggestion_style();
454
455 self.formatting_init.extend(code_init);
456 Ok(quote! {
457 #diag.span_suggestions_with_style(
458 #span_field,
459 rustc_errors::fluent::#slug,
460 #code_field,
461 #applicability,
462 #style
463 );
464 })
465 }
466 SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(),
467 }
468 }
469
470 /// Adds a spanned subdiagnostic by generating a `diag.span_$kind` call with the current slug
471 /// and `fluent_attr_identifier`.
472 fn add_spanned_subdiagnostic(
473 &self,
474 field_binding: TokenStream,
475 kind: &Ident,
476 fluent_attr_identifier: Path,
477 ) -> TokenStream {
478 let diag = &self.parent.diag;
479 let fn_name = format_ident!("span_{}", kind);
480 quote! {
481 #diag.#fn_name(
482 #field_binding,
483 rustc_errors::fluent::#fluent_attr_identifier
484 );
485 }
486 }
487
488 /// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current slug
489 /// and `fluent_attr_identifier`.
490 fn add_subdiagnostic(&self, kind: &Ident, fluent_attr_identifier: Path) -> TokenStream {
491 let diag = &self.parent.diag;
492 quote! {
493 #diag.#kind(rustc_errors::fluent::#fluent_attr_identifier);
494 }
495 }
496
497 fn span_and_applicability_of_ty(
498 &self,
499 info: FieldInfo<'_>,
500 ) -> Result<(TokenStream, SpannedOption<TokenStream>), DiagnosticDeriveError> {
501 match &info.ty {
502 // If `ty` is `Span` w/out applicability, then use `Applicability::Unspecified`.
503 ty @ Type::Path(..) if type_matches_path(ty, &["rustc_span", "Span"]) => {
504 let binding = &info.binding.binding;
505 Ok((quote!(#binding), None))
506 }
507 // If `ty` is `(Span, Applicability)` then return tokens accessing those.
508 Type::Tuple(tup) => {
509 let mut span_idx = None;
510 let mut applicability_idx = None;
511
512 fn type_err(span: &Span) -> Result<!, DiagnosticDeriveError> {
513 span_err(span.unwrap(), "wrong types for suggestion")
514 .help(
515 "`#[suggestion(...)]` on a tuple field must be applied to fields \
516 of type `(Span, Applicability)`",
517 )
518 .emit();
519 Err(DiagnosticDeriveError::ErrorHandled)
520 }
521
522 for (idx, elem) in tup.elems.iter().enumerate() {
523 if type_matches_path(elem, &["rustc_span", "Span"]) {
524 span_idx.set_once(syn::Index::from(idx), elem.span().unwrap());
525 } else if type_matches_path(elem, &["rustc_errors", "Applicability"]) {
526 applicability_idx.set_once(syn::Index::from(idx), elem.span().unwrap());
527 } else {
528 type_err(&elem.span())?;
529 }
530 }
531
532 let Some((span_idx, _)) = span_idx else {
533 type_err(&tup.span())?;
534 };
535 let Some((applicability_idx, applicability_span)) = applicability_idx else {
536 type_err(&tup.span())?;
537 };
538 let binding = &info.binding.binding;
539 let span = quote!(#binding.#span_idx);
540 let applicability = quote!(#binding.#applicability_idx);
541
542 Ok((span, Some((applicability, applicability_span))))
543 }
544 // If `ty` isn't a `Span` or `(Span, Applicability)` then emit an error.
545 _ => throw_span_err!(info.span.unwrap(), "wrong field type for suggestion", |diag| {
546 diag.help(
547 "`#[suggestion(...)]` should be applied to fields of type `Span` or \
548 `(Span, Applicability)`",
549 )
550 }),
551 }
552 }
553 }