1#![deny(unused_must_use)]
2
3use proc_macro2::{Ident, Span, TokenStream};
4use quote::{format_ident, quote, quote_spanned};
5use syn::parse::ParseStream;
6use syn::spanned::Spanned;
7use syn::{Attribute, LitStr, Meta, Path, Token, Type};
8use synstructure::{BindingInfo, Structure, VariantInfo};
9
10use super::utils::SubdiagnosticVariant;
11use crate::diagnostics::error::{
12 DiagnosticDeriveError, span_err, throw_invalid_attr, throw_span_err,
13};
14use crate::diagnostics::message::Message;
15use crate::diagnostics::utils::{
16 FieldInfo, FieldInnerTy, FieldMap, SetOnce, SpannedOption, SubdiagnosticKind,
17 build_field_mapping, is_doc_comment, report_error_if_not_applied_to_span, report_type_error,
18 should_generate_arg, type_is_bool, type_is_unit, type_matches_path,
19};
20
21#[derive(Clone, Copy, PartialEq, Eq)]
23pub(crate) enum DiagnosticDeriveKind {
24 Diagnostic,
25 LintDiagnostic,
26}
27
28pub(crate) struct DiagnosticDeriveVariantBuilder {
32 pub kind: DiagnosticDeriveKind,
34
35 pub formatting_init: TokenStream,
37
38 pub span: proc_macro::Span,
40
41 pub field_map: FieldMap,
44
45 pub message: Option<Message>,
48
49 pub code: SpannedOption<()>,
52}
53
54impl DiagnosticDeriveKind {
55 pub(crate) fn each_variant<'s, F>(self, structure: &mut Structure<'s>, f: F) -> TokenStream
59 where
60 F: for<'v> Fn(DiagnosticDeriveVariantBuilder, &VariantInfo<'v>) -> TokenStream,
61 {
62 let ast = structure.ast();
63 let span = ast.span().unwrap();
64 match ast.data {
65 syn::Data::Struct(..) | syn::Data::Enum(..) => (),
66 syn::Data::Union(..) => {
67 span_err(span, "diagnostic derives can only be used on structs and enums").emit();
68 }
69 }
70
71 if matches!(ast.data, syn::Data::Enum(..)) {
72 for attr in &ast.attrs {
73 span_err(
74 attr.span().unwrap(),
75 "unsupported type attribute for diagnostic derive enum",
76 )
77 .emit();
78 }
79 }
80
81 structure.bind_with(|_| synstructure::BindStyle::Move);
82 let variants = structure.each_variant(|variant| {
83 let span = match structure.ast().data {
84 syn::Data::Struct(..) => span,
85 _ => variant.ast().ident.span().unwrap(),
88 };
89 let builder = DiagnosticDeriveVariantBuilder {
90 kind: self,
91 span,
92 field_map: build_field_mapping(variant),
93 formatting_init: TokenStream::new(),
94 message: None,
95 code: None,
96 };
97 f(builder, variant)
98 });
99
100 quote! {
101 match self {
102 #variants
103 }
104 }
105 }
106}
107
108impl DiagnosticDeriveVariantBuilder {
109 pub(crate) fn primary_message(&self) -> Option<&Message> {
110 match self.message.as_ref() {
111 None => {
112 span_err(self.span, "diagnostic message not specified")
113 .help(
114 "specify the message as the first argument to the `#[diag(...)]` \
115 attribute, such as `#[diag(\"Example error\")]`",
116 )
117 .emit();
118 None
119 }
120 Some(msg) => Some(msg),
121 }
122 }
123
124 pub(crate) fn preamble(&mut self, variant: &VariantInfo<'_>) -> TokenStream {
127 let ast = variant.ast();
128 let attrs = &ast.attrs;
129 let preamble = attrs.iter().map(|attr| {
130 self.generate_structure_code_for_attr(attr, variant)
131 .unwrap_or_else(|v| v.to_compile_error())
132 });
133
134 quote! {
135 #(#preamble)*;
136 }
137 }
138
139 pub(crate) fn body(&mut self, variant: &VariantInfo<'_>) -> TokenStream {
142 let mut body = quote! {};
143 for binding in variant.bindings().iter().filter(|bi| should_generate_arg(bi.ast())) {
145 body.extend(self.generate_field_code(binding));
146 }
147 for binding in variant.bindings().iter().filter(|bi| !should_generate_arg(bi.ast())) {
149 body.extend(self.generate_field_attrs_code(binding, variant));
150 }
151 body
152 }
153
154 fn parse_subdiag_attribute(
156 &self,
157 attr: &Attribute,
158 ) -> Result<Option<(SubdiagnosticKind, Message, bool)>, DiagnosticDeriveError> {
159 let Some(subdiag) = SubdiagnosticVariant::from_attr(attr, &self.field_map)? else {
160 return Ok(None);
163 };
164
165 if let SubdiagnosticKind::MultipartSuggestion { .. } = subdiag.kind {
166 throw_invalid_attr!(attr, |diag| diag
167 .help("consider creating a `Subdiagnostic` instead"));
168 }
169
170 let Some(message) = subdiag.message else {
171 throw_invalid_attr!(attr, |diag| diag.help("subdiagnostic message is missing"))
172 };
173
174 Ok(Some((subdiag.kind, message, false)))
175 }
176
177 fn generate_structure_code_for_attr(
181 &mut self,
182 attr: &Attribute,
183 variant: &VariantInfo<'_>,
184 ) -> Result<TokenStream, DiagnosticDeriveError> {
185 if is_doc_comment(attr) {
187 return Ok(quote! {});
188 }
189
190 let name = attr.path().segments.last().unwrap().ident.to_string();
191 let name = name.as_str();
192
193 if name == "diag" {
194 let mut tokens = TokenStream::new();
195 attr.parse_args_with(|input: ParseStream<'_>| {
196 if input.peek(LitStr) {
197 let message = input.parse::<LitStr>()?;
199 if !message.suffix().is_empty() {
200 span_err(
201 message.span().unwrap(),
202 "Inline message is not allowed to have a suffix",
203 )
204 .emit();
205 }
206 self.message = Some(Message {
207 attr_span: attr.span(),
208 message_span: message.span(),
209 value: message.value(),
210 });
211 }
212
213 while !input.is_empty() {
215 input.parse::<Token![,]>()?;
216 if input.is_empty() {
218 break;
219 }
220 let arg_name: Path = input.parse::<Path>()?;
221 if input.peek(Token![,]) {
222 span_err(
223 arg_name.span().unwrap(),
224 "diagnostic message must be the first argument",
225 )
226 .emit();
227 continue;
228 }
229 let arg_name = arg_name.require_ident()?;
230 input.parse::<Token![=]>()?;
231 let arg_value = input.parse::<syn::Expr>()?;
232 match arg_name.to_string().as_str() {
233 "code" => {
234 self.code.set_once((), arg_name.span().unwrap());
235 tokens.extend(quote! {
236 diag.code(#arg_value);
237 });
238 }
239 _ => {
240 span_err(arg_name.span().unwrap(), "unknown argument")
241 .note("only the `code` parameter is valid after the message")
242 .emit();
243 }
244 }
245 }
246 Ok(())
247 })?;
248
249 return Ok(tokens);
250 }
251
252 let Some((subdiag, message, _no_span)) = self.parse_subdiag_attribute(attr)? else {
253 return Ok(quote! {});
256 };
257 let fn_ident = format_ident!("{}", subdiag);
258 match subdiag {
259 SubdiagnosticKind::Note
260 | SubdiagnosticKind::NoteOnce
261 | SubdiagnosticKind::Help
262 | SubdiagnosticKind::HelpOnce
263 | SubdiagnosticKind::Warn => Ok(self.add_subdiagnostic(&fn_ident, message, variant)),
264 SubdiagnosticKind::Label | SubdiagnosticKind::Suggestion { .. } => {
265 throw_invalid_attr!(attr, |diag| diag
266 .help("`#[label]` and `#[suggestion]` can only be applied to fields"));
267 }
268 SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(),
269 }
270 }
271
272 fn generate_field_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream {
273 let field = binding_info.ast();
274 let mut field_binding = binding_info.binding.clone();
275 field_binding.set_span(field.ty.span());
276
277 let Some(ident) = field.ident.as_ref() else {
278 span_err(field.span().unwrap(), "tuple structs are not supported").emit();
279 return TokenStream::new();
280 };
281 let ident = format_ident!("{}", ident); quote! {
284 diag.arg(
285 stringify!(#ident),
286 #field_binding
287 );
288 }
289 }
290
291 fn generate_field_attrs_code(
292 &mut self,
293 binding_info: &BindingInfo<'_>,
294 variant: &VariantInfo<'_>,
295 ) -> TokenStream {
296 let field = binding_info.ast();
297 let field_binding = &binding_info.binding;
298
299 let inner_ty = FieldInnerTy::from_type(&field.ty);
300 let mut seen_label = false;
301
302 field
303 .attrs
304 .iter()
305 .map(move |attr| {
306 if is_doc_comment(attr) {
308 return quote! {};
309 }
310
311 let name = attr.path().segments.last().unwrap().ident.to_string();
312
313 if name == "primary_span" && seen_label {
314 span_err(attr.span().unwrap(), format!("`#[primary_span]` must be placed before labels, since it overwrites the span of the diagnostic")).emit();
315 }
316 if name == "label" {
317 seen_label = true;
318 }
319
320 let needs_clone =
321 name == "primary_span" && matches!(inner_ty, FieldInnerTy::Vec(_));
322 let (binding, needs_destructure) = if needs_clone {
323 (quote_spanned! {inner_ty.span()=> #field_binding.clone() }, false)
325 } else {
326 (quote_spanned! {inner_ty.span()=> #field_binding }, true)
327 };
328
329 let generated_code = self
330 .generate_inner_field_code(
331 attr,
332 FieldInfo { binding: binding_info, ty: inner_ty, span: &field.span() },
333 binding,
334 variant
335 )
336 .unwrap_or_else(|v| v.to_compile_error());
337
338 if needs_destructure {
339 inner_ty.with(field_binding, generated_code)
340 } else {
341 generated_code
342 }
343 })
344 .collect()
345 }
346
347 fn generate_inner_field_code(
348 &mut self,
349 attr: &Attribute,
350 info: FieldInfo<'_>,
351 binding: TokenStream,
352 variant: &VariantInfo<'_>,
353 ) -> Result<TokenStream, DiagnosticDeriveError> {
354 let ident = &attr.path().segments.last().unwrap().ident;
355 let name = ident.to_string();
356 match (&attr.meta, name.as_str()) {
357 (Meta::Path(_), "skip_arg") => return Ok(quote! {}),
360 (Meta::Path(_), "primary_span") => {
361 match self.kind {
362 DiagnosticDeriveKind::Diagnostic => {
363 report_error_if_not_applied_to_span(attr, &info)?;
364
365 return Ok(quote! {
366 diag.span(#binding);
367 });
368 }
369 DiagnosticDeriveKind::LintDiagnostic => {
370 throw_invalid_attr!(attr, |diag| {
371 diag.help("the `primary_span` field attribute is not valid for lint diagnostics")
372 })
373 }
374 }
375 }
376 (Meta::Path(_), "subdiagnostic") => {
377 return Ok(quote! { diag.subdiagnostic(#binding); });
378 }
379 _ => (),
380 }
381
382 let Some((subdiag, message, _no_span)) = self.parse_subdiag_attribute(attr)? else {
383 return Ok(quote! {});
386 };
387 let fn_ident = format_ident!("{}", subdiag);
388 match subdiag {
389 SubdiagnosticKind::Label => {
390 report_error_if_not_applied_to_span(attr, &info)?;
391 Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, message, variant))
392 }
393 SubdiagnosticKind::Note
394 | SubdiagnosticKind::NoteOnce
395 | SubdiagnosticKind::Help
396 | SubdiagnosticKind::HelpOnce
397 | SubdiagnosticKind::Warn => {
398 let inner = info.ty.inner_type();
399 if type_matches_path(inner, &["rustc_span", "Span"])
400 || type_matches_path(inner, &["rustc_span", "MultiSpan"])
401 {
402 Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, message, variant))
403 } else if type_is_unit(inner)
404 || (matches!(info.ty, FieldInnerTy::Plain(_)) && type_is_bool(inner))
405 {
406 Ok(self.add_subdiagnostic(&fn_ident, message, variant))
407 } else {
408 report_type_error(attr, "`Span`, `MultiSpan`, `bool` or `()`")?
409 }
410 }
411 SubdiagnosticKind::Suggestion {
412 suggestion_kind,
413 applicability: static_applicability,
414 code_field,
415 code_init,
416 } => {
417 if let FieldInnerTy::Vec(_) = info.ty {
418 throw_invalid_attr!(attr, |diag| {
419 diag
420 .note("`#[suggestion(...)]` applied to `Vec` field is ambiguous")
421 .help("to show a suggestion consisting of multiple parts, use a `Subdiagnostic` annotated with `#[multipart_suggestion(...)]`")
422 .help("to show a variable set of suggestions, use a `Vec` of `Subdiagnostic`s annotated with `#[suggestion(...)]`")
423 });
424 }
425
426 let (span_field, mut applicability) = self.span_and_applicability_of_ty(info)?;
427
428 if let Some((static_applicability, span)) = static_applicability {
429 applicability.set_once(quote! { #static_applicability }, span);
430 }
431
432 let message = message.diag_message(Some(variant));
433 let applicability = applicability
434 .value()
435 .unwrap_or_else(|| quote! { rustc_errors::Applicability::Unspecified });
436 let style = suggestion_kind.to_suggestion_style();
437
438 self.formatting_init.extend(code_init);
439 Ok(quote! {
440 diag.span_suggestions_with_style(
441 #span_field,
442 #message,
443 #code_field,
444 #applicability,
445 #style
446 );
447 })
448 }
449 SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(),
450 }
451 }
452
453 fn add_spanned_subdiagnostic(
456 &self,
457 field_binding: TokenStream,
458 kind: &Ident,
459 message: Message,
460 variant: &VariantInfo<'_>,
461 ) -> TokenStream {
462 let fn_name = format_ident!("span_{}", kind);
463 let message = message.diag_message(Some(variant));
464 quote! {
465 diag.#fn_name(
466 #field_binding,
467 #message
468 );
469 }
470 }
471
472 fn add_subdiagnostic(
475 &self,
476 kind: &Ident,
477 message: Message,
478 variant: &VariantInfo<'_>,
479 ) -> TokenStream {
480 let message = message.diag_message(Some(variant));
481 quote! {
482 diag.#kind(#message);
483 }
484 }
485
486 fn span_and_applicability_of_ty(
487 &self,
488 info: FieldInfo<'_>,
489 ) -> Result<(TokenStream, SpannedOption<TokenStream>), DiagnosticDeriveError> {
490 match &info.ty.inner_type() {
491 ty @ Type::Path(..) if type_matches_path(ty, &["rustc_span", "Span"]) => {
493 let binding = &info.binding.binding;
494 Ok((quote!(#binding), None))
495 }
496 Type::Tuple(tup) => {
498 let mut span_idx = None;
499 let mut applicability_idx = None;
500
501 fn type_err(span: &Span) -> Result<!, DiagnosticDeriveError> {
502 span_err(span.unwrap(), "wrong types for suggestion")
503 .help(
504 "`#[suggestion(...)]` on a tuple field must be applied to fields \
505 of type `(Span, Applicability)`",
506 )
507 .emit();
508 Err(DiagnosticDeriveError::ErrorHandled)
509 }
510
511 for (idx, elem) in tup.elems.iter().enumerate() {
512 if type_matches_path(elem, &["rustc_span", "Span"]) {
513 span_idx.set_once(syn::Index::from(idx), elem.span().unwrap());
514 } else if type_matches_path(elem, &["rustc_errors", "Applicability"]) {
515 applicability_idx.set_once(syn::Index::from(idx), elem.span().unwrap());
516 } else {
517 type_err(&elem.span())?;
518 }
519 }
520
521 let Some((span_idx, _)) = span_idx else {
522 type_err(&tup.span())?;
523 };
524 let Some((applicability_idx, applicability_span)) = applicability_idx else {
525 type_err(&tup.span())?;
526 };
527 let binding = &info.binding.binding;
528 let span = quote!(#binding.#span_idx);
529 let applicability = quote!(#binding.#applicability_idx);
530
531 Ok((span, Some((applicability, applicability_span))))
532 }
533 _ => throw_span_err!(info.span.unwrap(), "wrong field type for suggestion", |diag| {
535 diag.help(
536 "`#[suggestion(...)]` should be applied to fields of type `Span` or \
537 `(Span, Applicability)`",
538 )
539 }),
540 }
541 }
542}