]> git.proxmox.com Git - rustc.git/blob - compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
New upstream version 1.72.1+dfsg1
[rustc.git] / compiler / rustc_macros / src / diagnostics / subdiagnostic.rs
1 #![deny(unused_must_use)]
2
3 use crate::diagnostics::error::{
4 invalid_attr, span_err, throw_invalid_attr, throw_span_err, DiagnosticDeriveError,
5 };
6 use crate::diagnostics::utils::{
7 build_field_mapping, build_suggestion_code, is_doc_comment, new_code_ident,
8 report_error_if_not_applied_to_applicability, report_error_if_not_applied_to_span,
9 should_generate_set_arg, AllowMultipleAlternatives, FieldInfo, FieldInnerTy, FieldMap,
10 HasFieldMap, SetOnce, SpannedOption, SubdiagnosticKind,
11 };
12 use proc_macro2::TokenStream;
13 use quote::{format_ident, quote};
14 use syn::{spanned::Spanned, Attribute, Meta, MetaList, Path};
15 use synstructure::{BindingInfo, Structure, VariantInfo};
16
17 use super::utils::SubdiagnosticVariant;
18
19 /// The central struct for constructing the `add_to_diagnostic` method from an annotated struct.
20 pub(crate) struct SubdiagnosticDeriveBuilder {
21 diag: syn::Ident,
22 f: syn::Ident,
23 }
24
25 impl SubdiagnosticDeriveBuilder {
26 pub(crate) fn new() -> Self {
27 let diag = format_ident!("diag");
28 let f = format_ident!("f");
29 Self { diag, f }
30 }
31
32 pub(crate) fn into_tokens(self, mut structure: Structure<'_>) -> TokenStream {
33 let implementation = {
34 let ast = structure.ast();
35 let span = ast.span().unwrap();
36 match ast.data {
37 syn::Data::Struct(..) | syn::Data::Enum(..) => (),
38 syn::Data::Union(..) => {
39 span_err(
40 span,
41 "`#[derive(Subdiagnostic)]` can only be used on structs and enums",
42 )
43 .emit();
44 }
45 }
46
47 let is_enum = matches!(ast.data, syn::Data::Enum(..));
48 if is_enum {
49 for attr in &ast.attrs {
50 // Always allow documentation comments.
51 if is_doc_comment(attr) {
52 continue;
53 }
54
55 span_err(
56 attr.span().unwrap(),
57 "unsupported type attribute for subdiagnostic enum",
58 )
59 .emit();
60 }
61 }
62
63 structure.bind_with(|_| synstructure::BindStyle::Move);
64 let variants_ = structure.each_variant(|variant| {
65 let mut builder = SubdiagnosticDeriveVariantBuilder {
66 parent: &self,
67 variant,
68 span,
69 formatting_init: TokenStream::new(),
70 fields: build_field_mapping(variant),
71 span_field: None,
72 applicability: None,
73 has_suggestion_parts: false,
74 is_enum,
75 };
76 builder.into_tokens().unwrap_or_else(|v| v.to_compile_error())
77 });
78
79 quote! {
80 match self {
81 #variants_
82 }
83 }
84 };
85
86 let diag = &self.diag;
87 let f = &self.f;
88 let ret = structure.gen_impl(quote! {
89 gen impl rustc_errors::AddToDiagnostic for @Self {
90 fn add_to_diagnostic_with<__F>(self, #diag: &mut rustc_errors::Diagnostic, #f: __F)
91 where
92 __F: core::ops::Fn(
93 &mut rustc_errors::Diagnostic,
94 rustc_errors::SubdiagnosticMessage
95 ) -> rustc_errors::SubdiagnosticMessage,
96 {
97 use rustc_errors::{Applicability, IntoDiagnosticArg};
98 #implementation
99 }
100 }
101 });
102 ret
103 }
104 }
105
106 /// Tracks persistent information required for building up the call to add to the diagnostic
107 /// for the final generated method. This is a separate struct to `SubdiagnosticDerive`
108 /// only to be able to destructure and split `self.builder` and the `self.structure` up to avoid a
109 /// double mut borrow later on.
110 struct SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
111 /// The identifier to use for the generated `DiagnosticBuilder` instance.
112 parent: &'parent SubdiagnosticDeriveBuilder,
113
114 /// Info for the current variant (or the type if not an enum).
115 variant: &'a VariantInfo<'a>,
116 /// Span for the entire type.
117 span: proc_macro::Span,
118
119 /// Initialization of format strings for code suggestions.
120 formatting_init: TokenStream,
121
122 /// Store a map of field name to its corresponding field. This is built on construction of the
123 /// derive builder.
124 fields: FieldMap,
125
126 /// Identifier for the binding to the `#[primary_span]` field.
127 span_field: SpannedOption<proc_macro2::Ident>,
128
129 /// The binding to the `#[applicability]` field, if present.
130 applicability: SpannedOption<TokenStream>,
131
132 /// Set to true when a `#[suggestion_part]` field is encountered, used to generate an error
133 /// during finalization if still `false`.
134 has_suggestion_parts: bool,
135
136 /// Set to true when this variant is an enum variant rather than just the body of a struct.
137 is_enum: bool,
138 }
139
140 impl<'parent, 'a> HasFieldMap for SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
141 fn get_field_binding(&self, field: &String) -> Option<&TokenStream> {
142 self.fields.get(field)
143 }
144 }
145
146 /// Provides frequently-needed information about the diagnostic kinds being derived for this type.
147 #[derive(Clone, Copy, Debug)]
148 struct KindsStatistics {
149 has_multipart_suggestion: bool,
150 all_multipart_suggestions: bool,
151 has_normal_suggestion: bool,
152 all_applicabilities_static: bool,
153 }
154
155 impl<'a> FromIterator<&'a SubdiagnosticKind> for KindsStatistics {
156 fn from_iter<T: IntoIterator<Item = &'a SubdiagnosticKind>>(kinds: T) -> Self {
157 let mut ret = Self {
158 has_multipart_suggestion: false,
159 all_multipart_suggestions: true,
160 has_normal_suggestion: false,
161 all_applicabilities_static: true,
162 };
163
164 for kind in kinds {
165 if let SubdiagnosticKind::MultipartSuggestion { applicability: None, .. }
166 | SubdiagnosticKind::Suggestion { applicability: None, .. } = kind
167 {
168 ret.all_applicabilities_static = false;
169 }
170 if let SubdiagnosticKind::MultipartSuggestion { .. } = kind {
171 ret.has_multipart_suggestion = true;
172 } else {
173 ret.all_multipart_suggestions = false;
174 }
175
176 if let SubdiagnosticKind::Suggestion { .. } = kind {
177 ret.has_normal_suggestion = true;
178 }
179 }
180 ret
181 }
182 }
183
184 impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
185 fn identify_kind(
186 &mut self,
187 ) -> Result<Vec<(SubdiagnosticKind, Path, bool)>, DiagnosticDeriveError> {
188 let mut kind_slugs = vec![];
189
190 for attr in self.variant.ast().attrs {
191 let Some(SubdiagnosticVariant { kind, slug, no_span }) = SubdiagnosticVariant::from_attr(attr, self)? else {
192 // Some attributes aren't errors - like documentation comments - but also aren't
193 // subdiagnostics.
194 continue;
195 };
196
197 let Some(slug) = slug else {
198 let name = attr.path().segments.last().unwrap().ident.to_string();
199 let name = name.as_str();
200
201 throw_span_err!(
202 attr.span().unwrap(),
203 format!(
204 "diagnostic slug must be first argument of a `#[{name}(...)]` attribute"
205 )
206 );
207 };
208
209 kind_slugs.push((kind, slug, no_span));
210 }
211
212 Ok(kind_slugs)
213 }
214
215 /// Generates the code for a field with no attributes.
216 fn generate_field_set_arg(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream {
217 let diag = &self.parent.diag;
218
219 let field = binding_info.ast();
220 let mut field_binding = binding_info.binding.clone();
221 field_binding.set_span(field.ty.span());
222
223 let ident = field.ident.as_ref().unwrap();
224 let ident = format_ident!("{}", ident); // strip `r#` prefix, if present
225
226 quote! {
227 #diag.set_arg(
228 stringify!(#ident),
229 #field_binding
230 );
231 }
232 }
233
234 /// Generates the necessary code for all attributes on a field.
235 fn generate_field_attr_code(
236 &mut self,
237 binding: &BindingInfo<'_>,
238 kind_stats: KindsStatistics,
239 ) -> TokenStream {
240 let ast = binding.ast();
241 assert!(ast.attrs.len() > 0, "field without attributes generating attr code");
242
243 // Abstract over `Vec<T>` and `Option<T>` fields using `FieldInnerTy`, which will
244 // apply the generated code on each element in the `Vec` or `Option`.
245 let inner_ty = FieldInnerTy::from_type(&ast.ty);
246 ast.attrs
247 .iter()
248 .map(|attr| {
249 // Always allow documentation comments.
250 if is_doc_comment(attr) {
251 return quote! {};
252 }
253
254 let info = FieldInfo { binding, ty: inner_ty, span: &ast.span() };
255
256 let generated = self
257 .generate_field_code_inner(kind_stats, attr, info, inner_ty.will_iterate())
258 .unwrap_or_else(|v| v.to_compile_error());
259
260 inner_ty.with(binding, generated)
261 })
262 .collect()
263 }
264
265 fn generate_field_code_inner(
266 &mut self,
267 kind_stats: KindsStatistics,
268 attr: &Attribute,
269 info: FieldInfo<'_>,
270 clone_suggestion_code: bool,
271 ) -> Result<TokenStream, DiagnosticDeriveError> {
272 match &attr.meta {
273 Meta::Path(path) => {
274 self.generate_field_code_inner_path(kind_stats, attr, info, path.clone())
275 }
276 Meta::List(list) => self.generate_field_code_inner_list(
277 kind_stats,
278 attr,
279 info,
280 list,
281 clone_suggestion_code,
282 ),
283 _ => throw_invalid_attr!(attr),
284 }
285 }
286
287 /// Generates the code for a `[Meta::Path]`-like attribute on a field (e.g. `#[primary_span]`).
288 fn generate_field_code_inner_path(
289 &mut self,
290 kind_stats: KindsStatistics,
291 attr: &Attribute,
292 info: FieldInfo<'_>,
293 path: Path,
294 ) -> Result<TokenStream, DiagnosticDeriveError> {
295 let span = attr.span().unwrap();
296 let ident = &path.segments.last().unwrap().ident;
297 let name = ident.to_string();
298 let name = name.as_str();
299
300 match name {
301 "skip_arg" => Ok(quote! {}),
302 "primary_span" => {
303 if kind_stats.has_multipart_suggestion {
304 invalid_attr(attr)
305 .help(
306 "multipart suggestions use one or more `#[suggestion_part]`s rather \
307 than one `#[primary_span]`",
308 )
309 .emit();
310 } else {
311 report_error_if_not_applied_to_span(attr, &info)?;
312
313 let binding = info.binding.binding.clone();
314 // FIXME(#100717): support `Option<Span>` on `primary_span` like in the
315 // diagnostic derive
316 if !matches!(info.ty, FieldInnerTy::Plain(_)) {
317 throw_invalid_attr!(attr, |diag| {
318 let diag = diag.note("there must be exactly one primary span");
319
320 if kind_stats.has_normal_suggestion {
321 diag.help(
322 "to create a suggestion with multiple spans, \
323 use `#[multipart_suggestion]` instead",
324 )
325 } else {
326 diag
327 }
328 });
329 }
330
331 self.span_field.set_once(binding, span);
332 }
333
334 Ok(quote! {})
335 }
336 "suggestion_part" => {
337 self.has_suggestion_parts = true;
338
339 if kind_stats.has_multipart_suggestion {
340 span_err(span, "`#[suggestion_part(...)]` attribute without `code = \"...\"`")
341 .emit();
342 } else {
343 invalid_attr(attr)
344 .help(
345 "`#[suggestion_part(...)]` is only valid in multipart suggestions, \
346 use `#[primary_span]` instead",
347 )
348 .emit();
349 }
350
351 Ok(quote! {})
352 }
353 "applicability" => {
354 if kind_stats.has_multipart_suggestion || kind_stats.has_normal_suggestion {
355 report_error_if_not_applied_to_applicability(attr, &info)?;
356
357 if kind_stats.all_applicabilities_static {
358 span_err(
359 span,
360 "`#[applicability]` has no effect if all `#[suggestion]`/\
361 `#[multipart_suggestion]` attributes have a static \
362 `applicability = \"...\"`",
363 )
364 .emit();
365 }
366 let binding = info.binding.binding.clone();
367 self.applicability.set_once(quote! { #binding }, span);
368 } else {
369 span_err(span, "`#[applicability]` is only valid on suggestions").emit();
370 }
371
372 Ok(quote! {})
373 }
374 _ => {
375 let mut span_attrs = vec![];
376 if kind_stats.has_multipart_suggestion {
377 span_attrs.push("suggestion_part");
378 }
379 if !kind_stats.all_multipart_suggestions {
380 span_attrs.push("primary_span")
381 }
382
383 invalid_attr(attr)
384 .help(format!(
385 "only `{}`, `applicability` and `skip_arg` are valid field attributes",
386 span_attrs.join(", ")
387 ))
388 .emit();
389
390 Ok(quote! {})
391 }
392 }
393 }
394
395 /// Generates the code for a `[Meta::List]`-like attribute on a field (e.g.
396 /// `#[suggestion_part(code = "...")]`).
397 fn generate_field_code_inner_list(
398 &mut self,
399 kind_stats: KindsStatistics,
400 attr: &Attribute,
401 info: FieldInfo<'_>,
402 list: &MetaList,
403 clone_suggestion_code: bool,
404 ) -> Result<TokenStream, DiagnosticDeriveError> {
405 let span = attr.span().unwrap();
406 let mut ident = list.path.segments.last().unwrap().ident.clone();
407 ident.set_span(info.ty.span());
408 let name = ident.to_string();
409 let name = name.as_str();
410
411 match name {
412 "suggestion_part" => {
413 if !kind_stats.has_multipart_suggestion {
414 throw_invalid_attr!(attr, |diag| {
415 diag.help(
416 "`#[suggestion_part(...)]` is only valid in multipart suggestions",
417 )
418 })
419 }
420
421 self.has_suggestion_parts = true;
422
423 report_error_if_not_applied_to_span(attr, &info)?;
424
425 let mut code = None;
426
427 list.parse_nested_meta(|nested| {
428 if nested.path.is_ident("code") {
429 let code_field = new_code_ident();
430 let span = nested.path.span().unwrap();
431 let formatting_init = build_suggestion_code(
432 &code_field,
433 nested,
434 self,
435 AllowMultipleAlternatives::No,
436 );
437 code.set_once((code_field, formatting_init), span);
438 } else {
439 span_err(
440 nested.path.span().unwrap(),
441 "`code` is the only valid nested attribute",
442 )
443 .emit();
444 }
445 Ok(())
446 })?;
447
448 let Some((code_field, formatting_init)) = code.value() else {
449 span_err(span, "`#[suggestion_part(...)]` attribute without `code = \"...\"`")
450 .emit();
451 return Ok(quote! {});
452 };
453 let binding = info.binding;
454
455 self.formatting_init.extend(formatting_init);
456 let code_field = if clone_suggestion_code {
457 quote! { #code_field.clone() }
458 } else {
459 quote! { #code_field }
460 };
461 Ok(quote! { suggestions.push((#binding, #code_field)); })
462 }
463 _ => throw_invalid_attr!(attr, |diag| {
464 let mut span_attrs = vec![];
465 if kind_stats.has_multipart_suggestion {
466 span_attrs.push("suggestion_part");
467 }
468 if !kind_stats.all_multipart_suggestions {
469 span_attrs.push("primary_span")
470 }
471 diag.help(format!(
472 "only `{}`, `applicability` and `skip_arg` are valid field attributes",
473 span_attrs.join(", ")
474 ))
475 }),
476 }
477 }
478
479 pub fn into_tokens(&mut self) -> Result<TokenStream, DiagnosticDeriveError> {
480 let kind_slugs = self.identify_kind()?;
481 if kind_slugs.is_empty() {
482 if self.is_enum {
483 // It's okay for a variant to not be a subdiagnostic at all..
484 return Ok(quote! {});
485 } else {
486 // ..but structs should always be _something_.
487 throw_span_err!(
488 self.variant.ast().ident.span().unwrap(),
489 "subdiagnostic kind not specified"
490 );
491 }
492 };
493
494 let kind_stats: KindsStatistics =
495 kind_slugs.iter().map(|(kind, _slug, _no_span)| kind).collect();
496
497 let init = if kind_stats.has_multipart_suggestion {
498 quote! { let mut suggestions = Vec::new(); }
499 } else {
500 quote! {}
501 };
502
503 let attr_args: TokenStream = self
504 .variant
505 .bindings()
506 .iter()
507 .filter(|binding| !should_generate_set_arg(binding.ast()))
508 .map(|binding| self.generate_field_attr_code(binding, kind_stats))
509 .collect();
510
511 let span_field = self.span_field.value_ref();
512
513 let diag = &self.parent.diag;
514 let f = &self.parent.f;
515 let mut calls = TokenStream::new();
516 for (kind, slug, no_span) in kind_slugs {
517 let message = format_ident!("__message");
518 calls.extend(
519 quote! { let #message = #f(#diag, crate::fluent_generated::#slug.into()); },
520 );
521
522 let name = format_ident!(
523 "{}{}",
524 if span_field.is_some() && !no_span { "span_" } else { "" },
525 kind
526 );
527 let call = match kind {
528 SubdiagnosticKind::Suggestion {
529 suggestion_kind,
530 applicability,
531 code_init,
532 code_field,
533 } => {
534 self.formatting_init.extend(code_init);
535
536 let applicability = applicability
537 .value()
538 .map(|a| quote! { #a })
539 .or_else(|| self.applicability.take().value())
540 .unwrap_or_else(|| quote! { rustc_errors::Applicability::Unspecified });
541
542 if let Some(span) = span_field {
543 let style = suggestion_kind.to_suggestion_style();
544 quote! { #diag.#name(#span, #message, #code_field, #applicability, #style); }
545 } else {
546 span_err(self.span, "suggestion without `#[primary_span]` field").emit();
547 quote! { unreachable!(); }
548 }
549 }
550 SubdiagnosticKind::MultipartSuggestion { suggestion_kind, applicability } => {
551 let applicability = applicability
552 .value()
553 .map(|a| quote! { #a })
554 .or_else(|| self.applicability.take().value())
555 .unwrap_or_else(|| quote! { rustc_errors::Applicability::Unspecified });
556
557 if !self.has_suggestion_parts {
558 span_err(
559 self.span,
560 "multipart suggestion without any `#[suggestion_part(...)]` fields",
561 )
562 .emit();
563 }
564
565 let style = suggestion_kind.to_suggestion_style();
566
567 quote! { #diag.#name(#message, suggestions, #applicability, #style); }
568 }
569 SubdiagnosticKind::Label => {
570 if let Some(span) = span_field {
571 quote! { #diag.#name(#span, #message); }
572 } else {
573 span_err(self.span, "label without `#[primary_span]` field").emit();
574 quote! { unreachable!(); }
575 }
576 }
577 _ => {
578 if let Some(span) = span_field && !no_span {
579 quote! { #diag.#name(#span, #message); }
580 } else {
581 quote! { #diag.#name(#message); }
582 }
583 }
584 };
585
586 calls.extend(call);
587 }
588
589 let plain_args: TokenStream = self
590 .variant
591 .bindings()
592 .iter()
593 .filter(|binding| should_generate_set_arg(binding.ast()))
594 .map(|binding| self.generate_field_set_arg(binding))
595 .collect();
596
597 let formatting_init = &self.formatting_init;
598 Ok(quote! {
599 #init
600 #formatting_init
601 #attr_args
602 #plain_args
603 #calls
604 })
605 }
606 }