1#![deny(unused_must_use)]
2
3use proc_macro2::{Ident, TokenStream};
4use quote::{format_ident, quote};
5use syn::parse::ParseStream;
6use syn::spanned::Spanned;
7use syn::{Attribute, Meta, MetaList, Path, Token};
8use synstructure::{BindingInfo, Structure, VariantInfo};
9
10use super::utils::SubdiagnosticVariant;
11use crate::diagnostics::error::{
12 DiagnosticDeriveError, invalid_attr, span_err, throw_invalid_attr, throw_span_err,
13};
14use crate::diagnostics::message::Message;
15use crate::diagnostics::utils::{
16 AllowMultipleAlternatives, FieldInfo, FieldInnerTy, FieldMap, SetOnce, SpannedOption,
17 SubdiagnosticKind, build_field_mapping, build_suggestion_code, is_doc_comment, new_code_ident,
18 report_error_if_not_applied_to_applicability, report_error_if_not_applied_to_span,
19 should_generate_arg,
20};
21
22pub(crate) struct SubdiagnosticDerive {
24 diag: syn::Ident,
25}
26
27impl SubdiagnosticDerive {
28 pub(crate) fn new() -> Self {
29 let diag = format_ident!("diag");
30 Self { diag }
31 }
32
33 pub(crate) fn into_tokens(self, mut structure: Structure<'_>) -> TokenStream {
34 let implementation = {
35 let ast = structure.ast();
36 let span = ast.span().unwrap();
37 match ast.data {
38 syn::Data::Struct(..) | syn::Data::Enum(..) => (),
39 syn::Data::Union(..) => {
40 span_err(
41 span,
42 "`#[derive(Subdiagnostic)]` can only be used on structs and enums",
43 )
44 .emit();
45 }
46 }
47
48 let is_enum = matches!(ast.data, syn::Data::Enum(..));
49 if is_enum {
50 for attr in &ast.attrs {
51 if is_doc_comment(attr) {
53 continue;
54 }
55
56 span_err(
57 attr.span().unwrap(),
58 "unsupported type attribute for subdiagnostic enum",
59 )
60 .emit();
61 }
62 }
63
64 structure.bind_with(|_| synstructure::BindStyle::Move);
65 let variants_ = structure.each_variant(|variant| {
66 let mut builder = SubdiagnosticDeriveVariantBuilder {
67 parent: &self,
68 variant,
69 span,
70 formatting_init: TokenStream::new(),
71 fields: build_field_mapping(variant),
72 span_field: None,
73 applicability: None,
74 has_suggestion_parts: false,
75 has_subdiagnostic: false,
76 is_enum,
77 };
78 builder.into_tokens().unwrap_or_else(|v| v.to_compile_error())
79 });
80
81 quote! {
82 match self {
83 #variants_
84 }
85 }
86 };
87
88 let diag = &self.diag;
89
90 #[allow(keyword_idents_2024)]
92 let ret = structure.gen_impl(quote! {
93 gen impl rustc_errors::Subdiagnostic for @Self {
94 fn add_to_diag<__G>(
95 self,
96 #diag: &mut rustc_errors::Diag<'_, __G>,
97 ) where
98 __G: rustc_errors::EmissionGuarantee,
99 {
100 #implementation
101 }
102 }
103 });
104
105 ret
106 }
107}
108
109struct SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
114 parent: &'parent SubdiagnosticDerive,
116
117 variant: &'a VariantInfo<'a>,
119 span: proc_macro::Span,
121
122 formatting_init: TokenStream,
124
125 fields: FieldMap,
128
129 span_field: SpannedOption<proc_macro2::Ident>,
131
132 applicability: SpannedOption<TokenStream>,
134
135 has_suggestion_parts: bool,
138
139 has_subdiagnostic: bool,
142
143 is_enum: bool,
145}
146
147#[derive(Clone, Copy, Debug)]
149struct KindsStatistics {
150 has_multipart_suggestion: bool,
151 all_multipart_suggestions: bool,
152 has_normal_suggestion: bool,
153 all_applicabilities_static: bool,
154}
155
156impl<'a> FromIterator<&'a SubdiagnosticKind> for KindsStatistics {
157 fn from_iter<T: IntoIterator<Item = &'a SubdiagnosticKind>>(kinds: T) -> Self {
158 let mut ret = Self {
159 has_multipart_suggestion: false,
160 all_multipart_suggestions: true,
161 has_normal_suggestion: false,
162 all_applicabilities_static: true,
163 };
164
165 for kind in kinds {
166 if let SubdiagnosticKind::MultipartSuggestion { applicability: None, .. }
167 | SubdiagnosticKind::Suggestion { applicability: None, .. } = kind
168 {
169 ret.all_applicabilities_static = false;
170 }
171 if let SubdiagnosticKind::MultipartSuggestion { .. } = kind {
172 ret.has_multipart_suggestion = true;
173 } else {
174 ret.all_multipart_suggestions = false;
175 }
176
177 if let SubdiagnosticKind::Suggestion { .. } = kind {
178 ret.has_normal_suggestion = true;
179 }
180 }
181 ret
182 }
183}
184
185impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
186 fn identify_kind(
187 &mut self,
188 ) -> Result<Vec<(SubdiagnosticKind, Message)>, DiagnosticDeriveError> {
189 let mut kind_messages = vec![];
190
191 for attr in self.variant.ast().attrs {
192 let Some(SubdiagnosticVariant { kind, message }) =
193 SubdiagnosticVariant::from_attr(attr, &self.fields)?
194 else {
195 continue;
198 };
199
200 let Some(message) = message else {
201 let name = attr.path().segments.last().unwrap().ident.to_string();
202 let name = name.as_str();
203
204 throw_span_err!(
205 attr.span().unwrap(),
206 format!(
207 "diagnostic message must be first argument of a `#[{name}(...)]` attribute"
208 )
209 );
210 };
211
212 kind_messages.push((kind, message));
213 }
214
215 Ok(kind_messages)
216 }
217
218 fn generate_field_arg(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream {
220 let diag = &self.parent.diag;
221
222 let field = binding_info.ast();
223 let mut field_binding = binding_info.binding.clone();
224 field_binding.set_span(field.ty.span());
225
226 let ident = field.ident.as_ref().unwrap();
227 let ident = format_ident!("{}", ident); quote! {
230 #diag.arg(
231 stringify!(#ident),
232 #field_binding
233 );
234 }
235 }
236
237 fn generate_field_attr_code(
239 &mut self,
240 binding: &BindingInfo<'_>,
241 kind_stats: KindsStatistics,
242 ) -> TokenStream {
243 let ast = binding.ast();
244 assert!(ast.attrs.len() > 0, "field without attributes generating attr code");
245
246 let inner_ty = FieldInnerTy::from_type(&ast.ty);
249 ast.attrs
250 .iter()
251 .map(|attr| {
252 if is_doc_comment(attr) {
254 return quote! {};
255 }
256
257 let info = FieldInfo { binding, ty: inner_ty, span: &ast.span() };
258
259 let generated = self
260 .generate_field_code_inner(kind_stats, attr, info, inner_ty.will_iterate())
261 .unwrap_or_else(|v| v.to_compile_error());
262
263 inner_ty.with(binding, generated)
264 })
265 .collect()
266 }
267
268 fn generate_field_code_inner(
269 &mut self,
270 kind_stats: KindsStatistics,
271 attr: &Attribute,
272 info: FieldInfo<'_>,
273 clone_suggestion_code: bool,
274 ) -> Result<TokenStream, DiagnosticDeriveError> {
275 match &attr.meta {
276 Meta::Path(path) => {
277 self.generate_field_code_inner_path(kind_stats, attr, info, path.clone())
278 }
279 Meta::List(list) => self.generate_field_code_inner_list(
280 kind_stats,
281 attr,
282 info,
283 list,
284 clone_suggestion_code,
285 ),
286 _ => throw_invalid_attr!(attr),
287 }
288 }
289
290 fn generate_field_code_inner_path(
292 &mut self,
293 kind_stats: KindsStatistics,
294 attr: &Attribute,
295 info: FieldInfo<'_>,
296 path: Path,
297 ) -> Result<TokenStream, DiagnosticDeriveError> {
298 let span = attr.span().unwrap();
299 let ident = &path.segments.last().unwrap().ident;
300 let name = ident.to_string();
301 let name = name.as_str();
302
303 match name {
304 "skip_arg" => Ok(quote! {}),
305 "primary_span" => {
306 if kind_stats.has_multipart_suggestion {
307 invalid_attr(attr)
308 .help(
309 "multipart suggestions use one or more `#[suggestion_part]`s rather \
310 than one `#[primary_span]`",
311 )
312 .emit();
313 } else {
314 report_error_if_not_applied_to_span(attr, &info)?;
315
316 let binding = info.binding.binding.clone();
317 if !matches!(info.ty, FieldInnerTy::Plain(_)) {
320 throw_invalid_attr!(attr, |diag| {
321 let diag = diag.note("there must be exactly one primary span");
322
323 if kind_stats.has_normal_suggestion {
324 diag.help(
325 "to create a suggestion with multiple spans, \
326 use `#[multipart_suggestion]` instead",
327 )
328 } else {
329 diag
330 }
331 });
332 }
333
334 self.span_field.set_once(binding, span);
335 }
336
337 Ok(quote! {})
338 }
339 "suggestion_part" => {
340 self.has_suggestion_parts = true;
341
342 if kind_stats.has_multipart_suggestion {
343 span_err(span, "`#[suggestion_part(...)]` attribute without `code = \"...\"`")
344 .emit();
345 } else {
346 invalid_attr(attr)
347 .help(
348 "`#[suggestion_part(...)]` is only valid in multipart suggestions, \
349 use `#[primary_span]` instead",
350 )
351 .emit();
352 }
353
354 Ok(quote! {})
355 }
356 "applicability" => {
357 if kind_stats.has_multipart_suggestion || kind_stats.has_normal_suggestion {
358 report_error_if_not_applied_to_applicability(attr, &info)?;
359
360 if kind_stats.all_applicabilities_static {
361 span_err(
362 span,
363 "`#[applicability]` has no effect if all `#[suggestion]`/\
364 `#[multipart_suggestion]` attributes have a static \
365 `applicability = \"...\"`",
366 )
367 .emit();
368 }
369 let binding = info.binding.binding.clone();
370 self.applicability.set_once(quote! { #binding }, span);
371 } else {
372 span_err(span, "`#[applicability]` is only valid on suggestions").emit();
373 }
374
375 Ok(quote! {})
376 }
377 "subdiagnostic" => {
378 let diag = &self.parent.diag;
379 let binding = &info.binding;
380 self.has_subdiagnostic = true;
381 Ok(quote! { #binding.add_to_diag(#diag); })
382 }
383 _ => {
384 let mut span_attrs = vec![];
385 if kind_stats.has_multipart_suggestion {
386 span_attrs.push("suggestion_part");
387 }
388 if !kind_stats.all_multipart_suggestions {
389 span_attrs.push("primary_span")
390 }
391
392 invalid_attr(attr)
393 .help(format!(
394 "only `{}`, `applicability` and `skip_arg` are valid field attributes",
395 span_attrs.join(", ")
396 ))
397 .emit();
398
399 Ok(quote! {})
400 }
401 }
402 }
403
404 fn generate_field_code_inner_list(
407 &mut self,
408 kind_stats: KindsStatistics,
409 attr: &Attribute,
410 info: FieldInfo<'_>,
411 list: &MetaList,
412 clone_suggestion_code: bool,
413 ) -> Result<TokenStream, DiagnosticDeriveError> {
414 let span = attr.span().unwrap();
415 let mut ident = list.path.segments.last().unwrap().ident.clone();
416 ident.set_span(info.ty.span());
417 let name = ident.to_string();
418 let name = name.as_str();
419
420 match name {
421 "suggestion_part" => {
422 if !kind_stats.has_multipart_suggestion {
423 throw_invalid_attr!(attr, |diag| {
424 diag.help(
425 "`#[suggestion_part(...)]` is only valid in multipart suggestions",
426 )
427 })
428 }
429
430 self.has_suggestion_parts = true;
431
432 report_error_if_not_applied_to_span(attr, &info)?;
433
434 let mut code = None;
435
436 list.parse_args_with(|input: ParseStream<'_>| {
437 while !input.is_empty() {
438 let arg_name = input.parse::<Ident>()?;
439 match arg_name.to_string().as_str() {
440 "code" => {
441 let code_field = new_code_ident();
442 let formatting_init = build_suggestion_code(
443 &code_field,
444 input,
445 &self.fields,
446 AllowMultipleAlternatives::No,
447 )?;
448 code.set_once(
449 (code_field, formatting_init),
450 arg_name.span().unwrap(),
451 );
452 }
453 _ => {
454 span_err(
455 arg_name.span().unwrap(),
456 "`code` is the only valid nested attribute",
457 )
458 .emit();
459 }
460 }
461 if input.is_empty() {
462 break;
463 }
464 input.parse::<Token![,]>()?;
465 }
466 Ok(())
467 })?;
468
469 let Some((code_field, formatting_init)) = code.value() else {
470 span_err(span, "`#[suggestion_part(...)]` attribute without `code = \"...\"`")
471 .emit();
472 return Ok(quote! {});
473 };
474 let binding = info.binding;
475
476 self.formatting_init.extend(formatting_init);
477 let code_field = if clone_suggestion_code {
478 quote! { #code_field.clone() }
479 } else {
480 quote! { #code_field }
481 };
482 Ok(quote! { suggestions.push((#binding, #code_field)); })
483 }
484 _ => throw_invalid_attr!(attr, |diag| {
485 let mut span_attrs = vec![];
486 if kind_stats.has_multipart_suggestion {
487 span_attrs.push("suggestion_part");
488 }
489 if !kind_stats.all_multipart_suggestions {
490 span_attrs.push("primary_span")
491 }
492 diag.help(format!(
493 "only `{}`, `applicability` and `skip_arg` are valid field attributes",
494 span_attrs.join(", ")
495 ))
496 }),
497 }
498 }
499
500 pub(crate) fn into_tokens(&mut self) -> Result<TokenStream, DiagnosticDeriveError> {
501 let kind_messages = self.identify_kind()?;
502
503 let kind_stats: KindsStatistics = kind_messages.iter().map(|(kind, _msg)| kind).collect();
504
505 let init = if kind_stats.has_multipart_suggestion {
506 quote! { let mut suggestions = Vec::new(); }
507 } else {
508 quote! {}
509 };
510
511 let attr_args: TokenStream = self
512 .variant
513 .bindings()
514 .iter()
515 .filter(|binding| !should_generate_arg(binding.ast()))
516 .map(|binding| self.generate_field_attr_code(binding, kind_stats))
517 .collect();
518
519 if kind_messages.is_empty() && !self.has_subdiagnostic {
520 if self.is_enum {
521 return Ok(quote! {});
523 } else {
524 throw_span_err!(
526 self.variant.ast().ident.span().unwrap(),
527 "subdiagnostic kind not specified"
528 );
529 }
530 };
531
532 let span_field = self.span_field.value_ref();
533
534 let diag = &self.parent.diag;
535 let mut calls = TokenStream::new();
536 for (kind, messages) in kind_messages {
537 let message = format_ident!("__message");
538 let message_stream = messages.diag_message(None);
539 calls.extend(quote! { let #message = #diag.eagerly_translate(#message_stream); });
540
541 let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind);
542 let call = match kind {
543 SubdiagnosticKind::Suggestion {
544 suggestion_kind,
545 applicability,
546 code_init,
547 code_field,
548 } => {
549 self.formatting_init.extend(code_init);
550
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 let Some(span) = span_field {
558 let style = suggestion_kind.to_suggestion_style();
559 quote! { #diag.#name(#span, #message, #code_field, #applicability, #style); }
560 } else {
561 span_err(self.span, "suggestion without `#[primary_span]` field").emit();
562 quote! { unreachable!(); }
563 }
564 }
565 SubdiagnosticKind::MultipartSuggestion { suggestion_kind, applicability } => {
566 let applicability = applicability
567 .value()
568 .map(|a| quote! { #a })
569 .or_else(|| self.applicability.take().value())
570 .unwrap_or_else(|| quote! { rustc_errors::Applicability::Unspecified });
571
572 if !self.has_suggestion_parts {
573 span_err(
574 self.span,
575 "multipart suggestion without any `#[suggestion_part(...)]` fields",
576 )
577 .emit();
578 }
579
580 let style = suggestion_kind.to_suggestion_style();
581
582 quote! { #diag.#name(#message, suggestions, #applicability, #style); }
583 }
584 SubdiagnosticKind::Label => {
585 if let Some(span) = span_field {
586 quote! { #diag.#name(#span, #message); }
587 } else {
588 span_err(self.span, "label without `#[primary_span]` field").emit();
589 quote! { unreachable!(); }
590 }
591 }
592 _ => {
593 if let Some(span) = span_field {
594 quote! { #diag.#name(#span, #message); }
595 } else {
596 quote! { #diag.#name(#message); }
597 }
598 }
599 };
600
601 calls.extend(call);
602 }
603 let store_args = quote! {
604 #diag.store_args();
605 };
606 let restore_args = quote! {
607 #diag.restore_args();
608 };
609 let plain_args: TokenStream = self
610 .variant
611 .bindings()
612 .iter()
613 .filter(|binding| should_generate_arg(binding.ast()))
614 .map(|binding| self.generate_field_arg(binding))
615 .collect();
616
617 let formatting_init = &self.formatting_init;
618
619 Ok(quote! {
626 #init
627 #formatting_init
628 #attr_args
629 #store_args
630 #plain_args
631 #calls
632 #restore_args
633 })
634 }
635}