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