]> git.proxmox.com Git - rustc.git/blame - 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
CommitLineData
064997fb
FG
1#![deny(unused_must_use)]
2
3use crate::diagnostics::error::{
4 invalid_nested_attr, span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err,
5 DiagnosticDeriveError,
6};
7use crate::diagnostics::utils::{
2b03887a
FG
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,
064997fb
FG
11};
12use proc_macro2::{Ident, Span, TokenStream};
13use quote::{format_ident, quote};
064997fb 14use syn::{
2b03887a 15 parse_quote, spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, NestedMeta, Path, Type,
064997fb 16};
2b03887a 17use synstructure::{BindingInfo, Structure, VariantInfo};
064997fb 18
f2b60f7d 19/// What kind of diagnostic is being derived - a fatal/error/warning or a lint?
2b03887a 20#[derive(Clone, PartialEq, Eq)]
064997fb 21pub(crate) enum DiagnosticDeriveKind {
2b03887a 22 Diagnostic { handler: syn::Ident },
f2b60f7d 23 LintDiagnostic,
064997fb
FG
24}
25
2b03887a
FG
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.
064997fb
FG
29pub(crate) struct DiagnosticDeriveBuilder {
30 /// The identifier to use for the generated `DiagnosticBuilder` instance.
31 pub diag: syn::Ident,
2b03887a
FG
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.
39pub(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,
064997fb
FG
48
49 /// Store a map of field name to its corresponding field. This is built on construction of the
50 /// derive builder.
2b03887a 51 pub field_map: FieldMap,
064997fb 52
064997fb
FG
53 /// Slug is a mandatory part of the struct attribute as corresponds to the Fluent message that
54 /// has the actual diagnostic message.
2b03887a 55 pub slug: SpannedOption<Path>,
064997fb
FG
56 /// Error codes are a optional part of the struct attribute - this is only set to detect
57 /// multiple specifications.
2b03887a 58 pub code: SpannedOption<()>,
064997fb
FG
59}
60
2b03887a 61impl<'a> HasFieldMap for DiagnosticDeriveVariantBuilder<'a> {
064997fb 62 fn get_field_binding(&self, field: &String) -> Option<&TokenStream> {
2b03887a 63 self.field_map.get(field)
064997fb
FG
64 }
65}
66
67impl DiagnosticDeriveBuilder {
2b03887a
FG
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 {
064997fb 75 let ast = structure.ast();
2b03887a
FG
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 {
487cf647 103 parent: self,
2b03887a
FG
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
121impl<'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();
064997fb
FG
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
2b03887a
FG
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
064997fb
FG
149 }
150
2b03887a
FG
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 };
064997fb 161
2b03887a
FG
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)))
064997fb
FG
178 }
179
180 /// Establishes state in the `DiagnosticDeriveBuilder` resulting from the struct
f2b60f7d 181 /// attributes like `#[diag(..)]`, such as the slug and error code. Generates
064997fb
FG
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> {
2b03887a
FG
187 let diag = &self.parent.diag;
188
189 // Always allow documentation comments.
190 if is_doc_comment(attr) {
191 return Ok(quote! {});
192 }
064997fb
FG
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
2b03887a
FG
198 if name == "diag" {
199 let Meta::List(MetaList { ref nested, .. }) = meta else {
200 throw_invalid_attr!(
201 attr,
202 &meta
203 );
204 };
064997fb 205
2b03887a 206 let mut nested_iter = nested.into_iter().peekable();
064997fb 207
2b03887a
FG
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();
064997fb 212 }
2b03887a 213 Some(NestedMeta::Meta(Meta::NameValue { .. })) => {}
487cf647 214 Some(nested_attr) => throw_invalid_nested_attr!(attr, nested_attr, |diag| diag
2b03887a
FG
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")),
064997fb 218 };
064997fb 219
2b03887a
FG
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(_)) => {
487cf647 230 invalid_nested_attr(attr, nested_attr)
2b03887a
FG
231 .help("diagnostic slug must be the first argument")
232 .emit();
233 continue;
234 }
235 _ => {
487cf647 236 invalid_nested_attr(attr, nested_attr).emit();
2b03887a
FG
237 continue;
238 }
239 };
064997fb 240
2b03887a
FG
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();
064997fb
FG
245 match nested_name.as_str() {
246 "code" => {
2b03887a
FG
247 self.code.set_once((), span);
248
249 let code = value.value();
250 tokens.extend(quote! {
064997fb
FG
251 #diag.code(rustc_errors::DiagnosticId::Error(#code.to_string()));
252 });
253 }
487cf647 254 _ => invalid_nested_attr(attr, nested_attr)
064997fb
FG
255 .help("only `code` is a valid nested attributes following the slug")
256 .emit(),
257 }
064997fb 258 }
2b03887a 259 return Ok(tokens);
064997fb
FG
260 }
261
2b03887a
FG
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 }
064997fb
FG
278 }
279
2b03887a
FG
280 fn generate_field_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream {
281 let diag = &self.parent.diag;
282
064997fb
FG
283 let field = binding_info.ast();
284 let field_binding = &binding_info.binding;
285
2b03887a
FG
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 );
064997fb 294 }
2b03887a
FG
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;
064997fb 300
064997fb
FG
301 let inner_ty = FieldInnerTy::from_type(&field.ty);
302
303 field
304 .attrs
305 .iter()
306 .map(move |attr| {
2b03887a
FG
307 // Always allow documentation comments.
308 if is_doc_comment(attr) {
309 return quote! {};
310 }
311
064997fb
FG
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)
064997fb 318 } else {
2b03887a 319 (quote! { #field_binding }, true)
064997fb
FG
320 };
321
322 let generated_code = self
323 .generate_inner_field_code(
324 attr,
9ffffee4 325 FieldInfo { binding: binding_info, ty: inner_ty, span: &field.span() },
064997fb
FG
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> {
2b03887a 345 let diag = &self.parent.diag;
064997fb
FG
346 let meta = attr.parse_meta()?;
347
348 let ident = &attr.path.segments.last().unwrap().ident;
349 let name = ident.to_string();
2b03887a
FG
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 { .. } => {
f2b60f7d
FG
357 report_error_if_not_applied_to_span(attr, &info)?;
358
2b03887a 359 return Ok(quote! {
f2b60f7d 360 #diag.set_span(#binding);
2b03887a 361 });
f2b60f7d
FG
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 }
064997fb 369 }
2b03887a 370 (Meta::Path(_), "subdiagnostic") => {
9c376795
FG
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 }
064997fb 380 }
2b03887a 381 (Meta::List(MetaList { ref nested, .. }), "subdiagnostic") => {
9c376795
FG
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 {
2b03887a
FG
395 throw_invalid_attr!(attr, &meta, |diag| {
396 diag.help(
397 "`eager` is the only supported nested attribute for `subdiagnostic`",
398 )
399 })
400 }
064997fb 401 }
2b03887a 402 _ => (),
064997fb
FG
403 }
404
2b03887a
FG
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! {});
064997fb 409 };
2b03887a
FG
410 let fn_ident = format_ident!("{}", subdiag);
411 match subdiag {
412 SubdiagnosticKind::Label => {
064997fb 413 report_error_if_not_applied_to_span(attr, &info)?;
2b03887a 414 Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, slug))
064997fb 415 }
2b03887a 416 SubdiagnosticKind::Note | SubdiagnosticKind::Help | SubdiagnosticKind::Warn => {
9ffffee4 417 if type_matches_path(info.ty.inner_type(), &["rustc_span", "Span"]) {
2b03887a 418 Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, slug))
9ffffee4 419 } else if type_is_unit(info.ty.inner_type()) {
2b03887a
FG
420 Ok(self.add_subdiagnostic(&fn_ident, slug))
421 } else {
422 report_type_error(attr, "`Span` or `()`")?
423 }
064997fb 424 }
2b03887a
FG
425 SubdiagnosticKind::Suggestion {
426 suggestion_kind,
427 applicability: static_applicability,
428 code_field,
429 code_init,
430 } => {
9ffffee4
FG
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
2b03887a
FG
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);
064997fb 444 }
2b03887a
FG
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,
9ffffee4 455 crate::fluent_generated::#slug,
2b03887a
FG
456 #code_field,
457 #applicability,
458 #style
459 );
460 })
064997fb 461 }
2b03887a 462 SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(),
064997fb 463 }
064997fb
FG
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 {
2b03887a 474 let diag = &self.parent.diag;
064997fb
FG
475 let fn_name = format_ident!("span_{}", kind);
476 quote! {
477 #diag.#fn_name(
478 #field_binding,
9ffffee4 479 crate::fluent_generated::#fluent_attr_identifier
064997fb
FG
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 {
2b03887a 487 let diag = &self.parent.diag;
064997fb 488 quote! {
9ffffee4 489 #diag.#kind(crate::fluent_generated::#fluent_attr_identifier);
064997fb
FG
490 }
491 }
492
493 fn span_and_applicability_of_ty(
494 &self,
495 info: FieldInfo<'_>,
2b03887a 496 ) -> Result<(TokenStream, SpannedOption<TokenStream>), DiagnosticDeriveError> {
9ffffee4 497 match &info.ty.inner_type() {
064997fb
FG
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;
2b03887a 501 Ok((quote!(#binding), None))
064997fb
FG
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
2b03887a
FG
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
064997fb
FG
518 for (idx, elem) in tup.elems.iter().enumerate() {
519 if type_matches_path(elem, &["rustc_span", "Span"]) {
2b03887a 520 span_idx.set_once(syn::Index::from(idx), elem.span().unwrap());
064997fb 521 } else if type_matches_path(elem, &["rustc_errors", "Applicability"]) {
2b03887a
FG
522 applicability_idx.set_once(syn::Index::from(idx), elem.span().unwrap());
523 } else {
524 type_err(&elem.span())?;
064997fb
FG
525 }
526 }
527
2b03887a
FG
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);
064997fb 537
2b03887a 538 Ok((span, Some((applicability, applicability_span))))
064997fb
FG
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}