]> git.proxmox.com Git - rustc.git/blob - compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
New upstream version 1.69.0+dfsg1
[rustc.git] / compiler / rustc_macros / src / diagnostics / diagnostic_builder.rs
1 #![deny(unused_must_use)]
2
3 use crate::diagnostics::error::{
4 invalid_nested_attr, span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err,
5 DiagnosticDeriveError,
6 };
7 use crate::diagnostics::utils::{
8 build_field_mapping, is_doc_comment, report_error_if_not_applied_to_span, report_type_error,
9 should_generate_set_arg, type_is_unit, type_matches_path, FieldInfo, FieldInnerTy, FieldMap,
10 HasFieldMap, SetOnce, SpannedOption, SubdiagnosticKind,
11 };
12 use proc_macro2::{Ident, Span, TokenStream};
13 use quote::{format_ident, quote};
14 use syn::{
15 parse_quote, spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, NestedMeta, Path, Type,
16 };
17 use synstructure::{BindingInfo, Structure, VariantInfo};
18
19 /// What kind of diagnostic is being derived - a fatal/error/warning or a lint?
20 #[derive(Clone, PartialEq, Eq)]
21 pub(crate) enum DiagnosticDeriveKind {
22 Diagnostic { handler: syn::Ident },
23 LintDiagnostic,
24 }
25
26 /// Tracks persistent information required for the entire type when building up individual calls to
27 /// diagnostic methods for generated diagnostic derives - both `Diagnostic` for
28 /// fatal/errors/warnings and `LintDiagnostic` for lints.
29 pub(crate) struct DiagnosticDeriveBuilder {
30 /// The identifier to use for the generated `DiagnosticBuilder` instance.
31 pub diag: syn::Ident,
32 /// Kind of diagnostic that should be derived.
33 pub kind: DiagnosticDeriveKind,
34 }
35
36 /// Tracks persistent information required for a specific variant when building up individual calls
37 /// to diagnostic methods for generated diagnostic derives - both `Diagnostic` for
38 /// fatal/errors/warnings and `LintDiagnostic` for lints.
39 pub(crate) struct DiagnosticDeriveVariantBuilder<'parent> {
40 /// The parent builder for the entire type.
41 pub parent: &'parent DiagnosticDeriveBuilder,
42
43 /// Initialization of format strings for code suggestions.
44 pub formatting_init: TokenStream,
45
46 /// Span of the struct or the enum variant.
47 pub span: proc_macro::Span,
48
49 /// Store a map of field name to its corresponding field. This is built on construction of the
50 /// derive builder.
51 pub field_map: FieldMap,
52
53 /// Slug is a mandatory part of the struct attribute as corresponds to the Fluent message that
54 /// has the actual diagnostic message.
55 pub slug: SpannedOption<Path>,
56 /// Error codes are a optional part of the struct attribute - this is only set to detect
57 /// multiple specifications.
58 pub code: SpannedOption<()>,
59 }
60
61 impl<'a> HasFieldMap for DiagnosticDeriveVariantBuilder<'a> {
62 fn get_field_binding(&self, field: &String) -> Option<&TokenStream> {
63 self.field_map.get(field)
64 }
65 }
66
67 impl DiagnosticDeriveBuilder {
68 /// Call `f` for the struct or for each variant of the enum, returning a `TokenStream` with the
69 /// tokens from `f` wrapped in an `match` expression. Emits errors for use of derive on unions
70 /// or attributes on the type itself when input is an enum.
71 pub fn each_variant<'s, F>(&mut self, structure: &mut Structure<'s>, f: F) -> TokenStream
72 where
73 F: for<'a, 'v> Fn(DiagnosticDeriveVariantBuilder<'a>, &VariantInfo<'v>) -> TokenStream,
74 {
75 let ast = structure.ast();
76 let span = ast.span().unwrap();
77 match ast.data {
78 syn::Data::Struct(..) | syn::Data::Enum(..) => (),
79 syn::Data::Union(..) => {
80 span_err(span, "diagnostic derives can only be used on structs and enums");
81 }
82 }
83
84 if matches!(ast.data, syn::Data::Enum(..)) {
85 for attr in &ast.attrs {
86 span_err(
87 attr.span().unwrap(),
88 "unsupported type attribute for diagnostic derive enum",
89 )
90 .emit();
91 }
92 }
93
94 structure.bind_with(|_| synstructure::BindStyle::Move);
95 let variants = structure.each_variant(|variant| {
96 let span = match structure.ast().data {
97 syn::Data::Struct(..) => span,
98 // There isn't a good way to get the span of the variant, so the variant's
99 // name will need to do.
100 _ => variant.ast().ident.span().unwrap(),
101 };
102 let builder = DiagnosticDeriveVariantBuilder {
103 parent: self,
104 span,
105 field_map: build_field_mapping(variant),
106 formatting_init: TokenStream::new(),
107 slug: None,
108 code: None,
109 };
110 f(builder, variant)
111 });
112
113 quote! {
114 match self {
115 #variants
116 }
117 }
118 }
119 }
120
121 impl<'a> DiagnosticDeriveVariantBuilder<'a> {
122 /// Generates calls to `code` and similar functions based on the attributes on the type or
123 /// variant.
124 pub fn preamble<'s>(&mut self, variant: &VariantInfo<'s>) -> TokenStream {
125 let ast = variant.ast();
126 let attrs = &ast.attrs;
127 let preamble = attrs.iter().map(|attr| {
128 self.generate_structure_code_for_attr(attr).unwrap_or_else(|v| v.to_compile_error())
129 });
130
131 quote! {
132 #(#preamble)*;
133 }
134 }
135
136 /// Generates calls to `span_label` and similar functions based on the attributes on fields or
137 /// calls to `set_arg` when no attributes are present.
138 pub fn body<'s>(&mut self, variant: &VariantInfo<'s>) -> TokenStream {
139 let mut body = quote! {};
140 // Generate `set_arg` calls first..
141 for binding in variant.bindings().iter().filter(|bi| should_generate_set_arg(bi.ast())) {
142 body.extend(self.generate_field_code(binding));
143 }
144 // ..and then subdiagnostic additions.
145 for binding in variant.bindings().iter().filter(|bi| !should_generate_set_arg(bi.ast())) {
146 body.extend(self.generate_field_attrs_code(binding));
147 }
148 body
149 }
150
151 /// Parse a `SubdiagnosticKind` from an `Attribute`.
152 fn parse_subdiag_attribute(
153 &self,
154 attr: &Attribute,
155 ) -> Result<Option<(SubdiagnosticKind, Path)>, DiagnosticDeriveError> {
156 let Some((subdiag, slug)) = SubdiagnosticKind::from_attr(attr, self)? else {
157 // Some attributes aren't errors - like documentation comments - but also aren't
158 // subdiagnostics.
159 return Ok(None);
160 };
161
162 if let SubdiagnosticKind::MultipartSuggestion { .. } = subdiag {
163 let meta = attr.parse_meta()?;
164 throw_invalid_attr!(attr, &meta, |diag| diag
165 .help("consider creating a `Subdiagnostic` instead"));
166 }
167
168 let slug = slug.unwrap_or_else(|| match subdiag {
169 SubdiagnosticKind::Label => parse_quote! { _subdiag::label },
170 SubdiagnosticKind::Note => parse_quote! { _subdiag::note },
171 SubdiagnosticKind::Help => parse_quote! { _subdiag::help },
172 SubdiagnosticKind::Warn => parse_quote! { _subdiag::warn },
173 SubdiagnosticKind::Suggestion { .. } => parse_quote! { _subdiag::suggestion },
174 SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(),
175 });
176
177 Ok(Some((subdiag, slug)))
178 }
179
180 /// Establishes state in the `DiagnosticDeriveBuilder` resulting from the struct
181 /// attributes like `#[diag(..)]`, such as the slug and error code. Generates
182 /// diagnostic builder calls for setting error code and creating note/help messages.
183 fn generate_structure_code_for_attr(
184 &mut self,
185 attr: &Attribute,
186 ) -> Result<TokenStream, DiagnosticDeriveError> {
187 let diag = &self.parent.diag;
188
189 // Always allow documentation comments.
190 if is_doc_comment(attr) {
191 return Ok(quote! {});
192 }
193
194 let name = attr.path.segments.last().unwrap().ident.to_string();
195 let name = name.as_str();
196 let meta = attr.parse_meta()?;
197
198 if name == "diag" {
199 let Meta::List(MetaList { ref nested, .. }) = meta else {
200 throw_invalid_attr!(
201 attr,
202 &meta
203 );
204 };
205
206 let mut nested_iter = nested.into_iter().peekable();
207
208 match nested_iter.peek() {
209 Some(NestedMeta::Meta(Meta::Path(slug))) => {
210 self.slug.set_once(slug.clone(), slug.span().unwrap());
211 nested_iter.next();
212 }
213 Some(NestedMeta::Meta(Meta::NameValue { .. })) => {}
214 Some(nested_attr) => throw_invalid_nested_attr!(attr, nested_attr, |diag| diag
215 .help("a diagnostic slug is required as the first argument")),
216 None => throw_invalid_attr!(attr, &meta, |diag| diag
217 .help("a diagnostic slug is required as the first argument")),
218 };
219
220 // Remaining attributes are optional, only `code = ".."` at the moment.
221 let mut tokens = TokenStream::new();
222 for nested_attr in nested_iter {
223 let (value, path) = match nested_attr {
224 NestedMeta::Meta(Meta::NameValue(MetaNameValue {
225 lit: syn::Lit::Str(value),
226 path,
227 ..
228 })) => (value, path),
229 NestedMeta::Meta(Meta::Path(_)) => {
230 invalid_nested_attr(attr, nested_attr)
231 .help("diagnostic slug must be the first argument")
232 .emit();
233 continue;
234 }
235 _ => {
236 invalid_nested_attr(attr, nested_attr).emit();
237 continue;
238 }
239 };
240
241 let nested_name = path.segments.last().unwrap().ident.to_string();
242 // Struct attributes are only allowed to be applied once, and the diagnostic
243 // changes will be set in the initialisation code.
244 let span = value.span().unwrap();
245 match nested_name.as_str() {
246 "code" => {
247 self.code.set_once((), span);
248
249 let code = value.value();
250 tokens.extend(quote! {
251 #diag.code(rustc_errors::DiagnosticId::Error(#code.to_string()));
252 });
253 }
254 _ => invalid_nested_attr(attr, nested_attr)
255 .help("only `code` is a valid nested attributes following the slug")
256 .emit(),
257 }
258 }
259 return Ok(tokens);
260 }
261
262 let Some((subdiag, slug)) = self.parse_subdiag_attribute(attr)? else {
263 // Some attributes aren't errors - like documentation comments - but also aren't
264 // subdiagnostics.
265 return Ok(quote! {});
266 };
267 let fn_ident = format_ident!("{}", subdiag);
268 match subdiag {
269 SubdiagnosticKind::Note | SubdiagnosticKind::Help | SubdiagnosticKind::Warn => {
270 Ok(self.add_subdiagnostic(&fn_ident, slug))
271 }
272 SubdiagnosticKind::Label | SubdiagnosticKind::Suggestion { .. } => {
273 throw_invalid_attr!(attr, &meta, |diag| diag
274 .help("`#[label]` and `#[suggestion]` can only be applied to fields"));
275 }
276 SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(),
277 }
278 }
279
280 fn generate_field_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream {
281 let diag = &self.parent.diag;
282
283 let field = binding_info.ast();
284 let field_binding = &binding_info.binding;
285
286 let ident = field.ident.as_ref().unwrap();
287 let ident = format_ident!("{}", ident); // strip `r#` prefix, if present
288
289 quote! {
290 #diag.set_arg(
291 stringify!(#ident),
292 #field_binding
293 );
294 }
295 }
296
297 fn generate_field_attrs_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream {
298 let field = binding_info.ast();
299 let field_binding = &binding_info.binding;
300
301 let inner_ty = FieldInnerTy::from_type(&field.ty);
302
303 field
304 .attrs
305 .iter()
306 .map(move |attr| {
307 // Always allow documentation comments.
308 if is_doc_comment(attr) {
309 return quote! {};
310 }
311
312 let name = attr.path.segments.last().unwrap().ident.to_string();
313 let needs_clone =
314 name == "primary_span" && matches!(inner_ty, FieldInnerTy::Vec(_));
315 let (binding, needs_destructure) = if needs_clone {
316 // `primary_span` can accept a `Vec<Span>` so don't destructure that.
317 (quote! { #field_binding.clone() }, false)
318 } else {
319 (quote! { #field_binding }, true)
320 };
321
322 let generated_code = self
323 .generate_inner_field_code(
324 attr,
325 FieldInfo { binding: binding_info, ty: inner_ty, span: &field.span() },
326 binding,
327 )
328 .unwrap_or_else(|v| v.to_compile_error());
329
330 if needs_destructure {
331 inner_ty.with(field_binding, generated_code)
332 } else {
333 generated_code
334 }
335 })
336 .collect()
337 }
338
339 fn generate_inner_field_code(
340 &mut self,
341 attr: &Attribute,
342 info: FieldInfo<'_>,
343 binding: TokenStream,
344 ) -> Result<TokenStream, DiagnosticDeriveError> {
345 let diag = &self.parent.diag;
346 let meta = attr.parse_meta()?;
347
348 let ident = &attr.path.segments.last().unwrap().ident;
349 let name = ident.to_string();
350 match (&meta, name.as_str()) {
351 // Don't need to do anything - by virtue of the attribute existing, the
352 // `set_arg` call will not be generated.
353 (Meta::Path(_), "skip_arg") => return Ok(quote! {}),
354 (Meta::Path(_), "primary_span") => {
355 match self.parent.kind {
356 DiagnosticDeriveKind::Diagnostic { .. } => {
357 report_error_if_not_applied_to_span(attr, &info)?;
358
359 return Ok(quote! {
360 #diag.set_span(#binding);
361 });
362 }
363 DiagnosticDeriveKind::LintDiagnostic => {
364 throw_invalid_attr!(attr, &meta, |diag| {
365 diag.help("the `primary_span` field attribute is not valid for lint diagnostics")
366 })
367 }
368 }
369 }
370 (Meta::Path(_), "subdiagnostic") => {
371 if FieldInnerTy::from_type(&info.binding.ast().ty).will_iterate() {
372 let DiagnosticDeriveKind::Diagnostic { handler } = &self.parent.kind else {
373 // No eager translation for lints.
374 return Ok(quote! { #diag.subdiagnostic(#binding); });
375 };
376 return Ok(quote! { #diag.eager_subdiagnostic(#handler, #binding); });
377 } else {
378 return Ok(quote! { #diag.subdiagnostic(#binding); });
379 }
380 }
381 (Meta::List(MetaList { ref nested, .. }), "subdiagnostic") => {
382 if nested.len() == 1
383 && let Some(NestedMeta::Meta(Meta::Path(path))) = nested.first()
384 && path.is_ident("eager") {
385 let handler = match &self.parent.kind {
386 DiagnosticDeriveKind::Diagnostic { handler } => handler,
387 DiagnosticDeriveKind::LintDiagnostic => {
388 throw_invalid_attr!(attr, &meta, |diag| {
389 diag.help("eager subdiagnostics are not supported on lints")
390 })
391 }
392 };
393 return Ok(quote! { #diag.eager_subdiagnostic(#handler, #binding); });
394 } else {
395 throw_invalid_attr!(attr, &meta, |diag| {
396 diag.help(
397 "`eager` is the only supported nested attribute for `subdiagnostic`",
398 )
399 })
400 }
401 }
402 _ => (),
403 }
404
405 let Some((subdiag, slug)) = self.parse_subdiag_attribute(attr)? else {
406 // Some attributes aren't errors - like documentation comments - but also aren't
407 // subdiagnostics.
408 return Ok(quote! {});
409 };
410 let fn_ident = format_ident!("{}", subdiag);
411 match subdiag {
412 SubdiagnosticKind::Label => {
413 report_error_if_not_applied_to_span(attr, &info)?;
414 Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, slug))
415 }
416 SubdiagnosticKind::Note | SubdiagnosticKind::Help | SubdiagnosticKind::Warn => {
417 if type_matches_path(info.ty.inner_type(), &["rustc_span", "Span"]) {
418 Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, slug))
419 } else if type_is_unit(info.ty.inner_type()) {
420 Ok(self.add_subdiagnostic(&fn_ident, slug))
421 } else {
422 report_type_error(attr, "`Span` or `()`")?
423 }
424 }
425 SubdiagnosticKind::Suggestion {
426 suggestion_kind,
427 applicability: static_applicability,
428 code_field,
429 code_init,
430 } => {
431 if let FieldInnerTy::Vec(_) = info.ty {
432 throw_invalid_attr!(attr, &meta, |diag| {
433 diag
434 .note("`#[suggestion(...)]` applied to `Vec` field is ambiguous")
435 .help("to show a suggestion consisting of multiple parts, use a `Subdiagnostic` annotated with `#[multipart_suggestion(...)]`")
436 .help("to show a variable set of suggestions, use a `Vec` of `Subdiagnostic`s annotated with `#[suggestion(...)]`")
437 });
438 }
439
440 let (span_field, mut applicability) = self.span_and_applicability_of_ty(info)?;
441
442 if let Some((static_applicability, span)) = static_applicability {
443 applicability.set_once(quote! { #static_applicability }, span);
444 }
445
446 let applicability = applicability
447 .value()
448 .unwrap_or_else(|| quote! { rustc_errors::Applicability::Unspecified });
449 let style = suggestion_kind.to_suggestion_style();
450
451 self.formatting_init.extend(code_init);
452 Ok(quote! {
453 #diag.span_suggestions_with_style(
454 #span_field,
455 crate::fluent_generated::#slug,
456 #code_field,
457 #applicability,
458 #style
459 );
460 })
461 }
462 SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(),
463 }
464 }
465
466 /// Adds a spanned subdiagnostic by generating a `diag.span_$kind` call with the current slug
467 /// and `fluent_attr_identifier`.
468 fn add_spanned_subdiagnostic(
469 &self,
470 field_binding: TokenStream,
471 kind: &Ident,
472 fluent_attr_identifier: Path,
473 ) -> TokenStream {
474 let diag = &self.parent.diag;
475 let fn_name = format_ident!("span_{}", kind);
476 quote! {
477 #diag.#fn_name(
478 #field_binding,
479 crate::fluent_generated::#fluent_attr_identifier
480 );
481 }
482 }
483
484 /// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current slug
485 /// and `fluent_attr_identifier`.
486 fn add_subdiagnostic(&self, kind: &Ident, fluent_attr_identifier: Path) -> TokenStream {
487 let diag = &self.parent.diag;
488 quote! {
489 #diag.#kind(crate::fluent_generated::#fluent_attr_identifier);
490 }
491 }
492
493 fn span_and_applicability_of_ty(
494 &self,
495 info: FieldInfo<'_>,
496 ) -> Result<(TokenStream, SpannedOption<TokenStream>), DiagnosticDeriveError> {
497 match &info.ty.inner_type() {
498 // If `ty` is `Span` w/out applicability, then use `Applicability::Unspecified`.
499 ty @ Type::Path(..) if type_matches_path(ty, &["rustc_span", "Span"]) => {
500 let binding = &info.binding.binding;
501 Ok((quote!(#binding), None))
502 }
503 // If `ty` is `(Span, Applicability)` then return tokens accessing those.
504 Type::Tuple(tup) => {
505 let mut span_idx = None;
506 let mut applicability_idx = None;
507
508 fn type_err(span: &Span) -> Result<!, DiagnosticDeriveError> {
509 span_err(span.unwrap(), "wrong types for suggestion")
510 .help(
511 "`#[suggestion(...)]` on a tuple field must be applied to fields \
512 of type `(Span, Applicability)`",
513 )
514 .emit();
515 Err(DiagnosticDeriveError::ErrorHandled)
516 }
517
518 for (idx, elem) in tup.elems.iter().enumerate() {
519 if type_matches_path(elem, &["rustc_span", "Span"]) {
520 span_idx.set_once(syn::Index::from(idx), elem.span().unwrap());
521 } else if type_matches_path(elem, &["rustc_errors", "Applicability"]) {
522 applicability_idx.set_once(syn::Index::from(idx), elem.span().unwrap());
523 } else {
524 type_err(&elem.span())?;
525 }
526 }
527
528 let Some((span_idx, _)) = span_idx else {
529 type_err(&tup.span())?;
530 };
531 let Some((applicability_idx, applicability_span)) = applicability_idx else {
532 type_err(&tup.span())?;
533 };
534 let binding = &info.binding.binding;
535 let span = quote!(#binding.#span_idx);
536 let applicability = quote!(#binding.#applicability_idx);
537
538 Ok((span, Some((applicability, applicability_span))))
539 }
540 // If `ty` isn't a `Span` or `(Span, Applicability)` then emit an error.
541 _ => throw_span_err!(info.span.unwrap(), "wrong field type for suggestion", |diag| {
542 diag.help(
543 "`#[suggestion(...)]` should be applied to fields of type `Span` or \
544 `(Span, Applicability)`",
545 )
546 }),
547 }
548 }
549 }