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