1 #![deny(unused_must_use)]
3 use crate::diagnostics::error::{
4 invalid_nested_attr, span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err,
5 SessionDiagnosticDeriveError,
7 use crate::diagnostics::utils::{
8 report_error_if_not_applied_to_span, report_type_error, type_is_unit, type_matches_path,
9 Applicability, FieldInfo, FieldInnerTy, HasFieldMap, SetOnce,
11 use proc_macro2::{Ident, TokenStream};
12 use quote::{format_ident, quote};
13 use std::collections::HashMap;
14 use std::str::FromStr;
16 parse_quote, spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, NestedMeta, Path, Type,
18 use synstructure::{BindingInfo, Structure};
20 /// The central struct for constructing the `into_diagnostic` method from an annotated struct.
21 pub(crate) struct SessionDiagnosticDerive<'a> {
22 structure: Structure<'a>,
23 builder: SessionDiagnosticDeriveBuilder,
26 impl<'a> SessionDiagnosticDerive<'a> {
27 pub(crate) fn new(diag: syn::Ident, sess: syn::Ident, structure: Structure<'a>) -> Self {
28 // Build the mapping of field names to fields. This allows attributes to peek values from
30 let mut fields_map = HashMap::new();
32 // Convenience bindings.
33 let ast = structure.ast();
35 if let syn::Data::Struct(syn::DataStruct { fields, .. }) = &ast.data {
36 for field in fields.iter() {
37 if let Some(ident) = &field.ident {
38 fields_map.insert(ident.to_string(), quote! { &self.#ident });
44 builder: SessionDiagnosticDeriveBuilder {
56 pub(crate) fn into_tokens(self) -> TokenStream {
57 let SessionDiagnosticDerive { mut structure, mut builder } = self;
59 let ast = structure.ast();
60 let attrs = &ast.attrs;
62 let (implementation, param_ty) = {
63 if let syn::Data::Struct(..) = ast.data {
65 let preamble = attrs.iter().map(|attr| {
67 .generate_structure_code(attr)
68 .unwrap_or_else(|v| v.to_compile_error())
76 // Keep track of which fields are subdiagnostics or have no attributes.
77 let mut subdiagnostics_or_empty = std::collections::HashSet::new();
79 // Generates calls to `span_label` and similar functions based on the attributes
80 // on fields. Code for suggestions uses formatting machinery and the value of
81 // other fields - because any given field can be referenced multiple times, it
82 // should be accessed through a borrow. When passing fields to `add_subdiagnostic`
83 // or `set_arg` (which happens below) for Fluent, we want to move the data, so that
84 // has to happen in a separate pass over the fields.
87 .filter(|field_binding| {
88 let attrs = &field_binding.ast().attrs;
91 && attrs.iter().all(|attr| {
93 != attr.path.segments.last().unwrap().ident.to_string()
96 subdiagnostics_or_empty.insert(field_binding.binding.clone());
100 .each(|field_binding| builder.generate_field_attrs_code(field_binding));
102 structure.bind_with(|_| synstructure::BindStyle::Move);
103 // When a field has attributes like `#[label]` or `#[note]` then it doesn't
104 // need to be passed as an argument to the diagnostic. But when a field has no
105 // attributes or a `#[subdiagnostic]` attribute then it must be passed as an
106 // argument to the diagnostic so that it can be referred to by Fluent messages.
108 .filter(|field_binding| {
109 subdiagnostics_or_empty.contains(&field_binding.binding)
111 .each(|field_binding| builder.generate_field_attrs_code(field_binding));
113 let span = ast.span().unwrap();
114 let (diag, sess) = (&builder.diag, &builder.sess);
115 let init = match (builder.kind, builder.slug) {
117 span_err(span, "diagnostic kind not specified")
118 .help("use the `#[error(...)]` attribute to create an error")
120 return SessionDiagnosticDeriveError::ErrorHandled.to_compile_error();
122 (Some((kind, _)), None) => {
123 span_err(span, "diagnostic slug not specified")
125 "specify the slug as the first argument to the attribute, such as \
126 `#[{}(typeck::example_error)]`",
130 return SessionDiagnosticDeriveError::ErrorHandled.to_compile_error();
132 (Some((SessionDiagnosticKind::Error, _)), Some((slug, _))) => {
134 let mut #diag = #sess.struct_err(rustc_errors::fluent::#slug);
137 (Some((SessionDiagnosticKind::Warn, _)), Some((slug, _))) => {
139 let mut #diag = #sess.struct_warn(rustc_errors::fluent::#slug);
144 let implementation = quote! {
155 let param_ty = match builder.kind {
156 Some((SessionDiagnosticKind::Error, _)) => {
157 quote! { rustc_errors::ErrorGuaranteed }
159 Some((SessionDiagnosticKind::Warn, _)) => quote! { () },
163 (implementation, param_ty)
167 "`#[derive(SessionDiagnostic)]` can only be used on structs",
171 let implementation = SessionDiagnosticDeriveError::ErrorHandled.to_compile_error();
172 let param_ty = quote! { rustc_errors::ErrorGuaranteed };
173 (implementation, param_ty)
177 let sess = &builder.sess;
178 structure.gen_impl(quote! {
179 gen impl<'__session_diagnostic_sess> rustc_session::SessionDiagnostic<'__session_diagnostic_sess, #param_ty>
184 #sess: &'__session_diagnostic_sess rustc_session::parse::ParseSess
185 ) -> rustc_errors::DiagnosticBuilder<'__session_diagnostic_sess, #param_ty> {
186 use rustc_errors::IntoDiagnosticArg;
194 /// What kind of session diagnostic is being derived - an error or a warning?
195 #[derive(Copy, Clone)]
196 enum SessionDiagnosticKind {
203 impl SessionDiagnosticKind {
204 /// Returns human-readable string corresponding to the kind.
205 fn descr(&self) -> &'static str {
207 SessionDiagnosticKind::Error => "error",
208 SessionDiagnosticKind::Warn => "warning",
213 /// Tracks persistent information required for building up the individual calls to diagnostic
214 /// methods for the final generated method. This is a separate struct to `SessionDiagnosticDerive`
215 /// only to be able to destructure and split `self.builder` and the `self.structure` up to avoid a
216 /// double mut borrow later on.
217 struct SessionDiagnosticDeriveBuilder {
218 /// Name of the session parameter that's passed in to the `as_error` method.
220 /// The identifier to use for the generated `DiagnosticBuilder` instance.
223 /// Store a map of field name to its corresponding field. This is built on construction of the
225 fields: HashMap<String, TokenStream>,
227 /// Kind of diagnostic requested via the struct attribute.
228 kind: Option<(SessionDiagnosticKind, proc_macro::Span)>,
229 /// Slug is a mandatory part of the struct attribute as corresponds to the Fluent message that
230 /// has the actual diagnostic message.
231 slug: Option<(Path, proc_macro::Span)>,
232 /// Error codes are a optional part of the struct attribute - this is only set to detect
233 /// multiple specifications.
234 code: Option<(String, proc_macro::Span)>,
237 impl HasFieldMap for SessionDiagnosticDeriveBuilder {
238 fn get_field_binding(&self, field: &String) -> Option<&TokenStream> {
239 self.fields.get(field)
243 impl SessionDiagnosticDeriveBuilder {
244 /// Establishes state in the `SessionDiagnosticDeriveBuilder` resulting from the struct
245 /// attributes like `#[error(..)`, such as the diagnostic kind and slug. Generates
246 /// diagnostic builder calls for setting error code and creating note/help messages.
247 fn generate_structure_code(
250 ) -> Result<TokenStream, SessionDiagnosticDeriveError> {
251 let diag = &self.diag;
252 let span = attr.span().unwrap();
254 let name = attr.path.segments.last().unwrap().ident.to_string();
255 let name = name.as_str();
256 let meta = attr.parse_meta()?;
258 let is_help_or_note = matches!(name, "help" | "note");
260 let nested = match meta {
261 // Most attributes are lists, like `#[error(..)]`/`#[warning(..)]` for most cases or
262 // `#[help(..)]`/`#[note(..)]` when the user is specifying a alternative slug.
263 Meta::List(MetaList { ref nested, .. }) => nested,
264 // Subdiagnostics without spans can be applied to the type too, and these are just
265 // paths: `#[help]` and `#[note]`
266 Meta::Path(_) if is_help_or_note => {
267 let fn_name = proc_macro2::Ident::new(name, attr.span());
268 return Ok(quote! { #diag.#fn_name(rustc_errors::fluent::_subdiag::#fn_name); });
270 _ => throw_invalid_attr!(attr, &meta),
273 // Check the kind before doing any further processing so that there aren't misleading
274 // "no kind specified" errors if there are failures later.
276 "error" => self.kind.set_once((SessionDiagnosticKind::Error, span)),
277 "warning" => self.kind.set_once((SessionDiagnosticKind::Warn, span)),
278 "help" | "note" => (),
279 _ => throw_invalid_attr!(attr, &meta, |diag| {
280 diag.help("only `error`, `warning`, `help` and `note` are valid attributes")
284 // First nested element should always be the path, e.g. `#[error(typeck::invalid)]` or
285 // `#[help(typeck::another_help)]`.
286 let mut nested_iter = nested.into_iter();
287 if let Some(nested_attr) = nested_iter.next() {
288 // Report an error if there are any other list items after the path.
289 if is_help_or_note && nested_iter.next().is_some() {
290 throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
291 diag.help("`help` and `note` struct attributes can only have one argument")
296 NestedMeta::Meta(Meta::Path(path)) if is_help_or_note => {
297 let fn_name = proc_macro2::Ident::new(name, attr.span());
298 return Ok(quote! { #diag.#fn_name(rustc_errors::fluent::#path); });
300 NestedMeta::Meta(Meta::Path(path)) => {
301 self.slug.set_once((path.clone(), span));
303 NestedMeta::Meta(meta @ Meta::NameValue(_))
305 && meta.path().segments.last().unwrap().ident.to_string() == "code" =>
307 // don't error for valid follow-up attributes
309 nested_attr => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
310 diag.help("first argument of the attribute should be the diagnostic slug")
315 // Remaining attributes are optional, only `code = ".."` at the moment.
316 let mut tokens = Vec::new();
317 for nested_attr in nested_iter {
318 let meta = match nested_attr {
319 syn::NestedMeta::Meta(meta) => meta,
320 _ => throw_invalid_nested_attr!(attr, &nested_attr),
323 let path = meta.path();
324 let nested_name = path.segments.last().unwrap().ident.to_string();
325 // Struct attributes are only allowed to be applied once, and the diagnostic
326 // changes will be set in the initialisation code.
327 if let Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) = &meta {
328 let span = s.span().unwrap();
329 match nested_name.as_str() {
331 self.code.set_once((s.value(), span));
332 let code = &self.code.as_ref().map(|(v, _)| v);
334 #diag.code(rustc_errors::DiagnosticId::Error(#code.to_string()));
337 _ => invalid_nested_attr(attr, &nested_attr)
338 .help("only `code` is a valid nested attributes following the slug")
342 invalid_nested_attr(attr, &nested_attr).emit()
346 Ok(tokens.drain(..).collect())
349 fn generate_field_attrs_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream {
350 let field = binding_info.ast();
351 let field_binding = &binding_info.binding;
353 let inner_ty = FieldInnerTy::from_type(&field.ty);
355 // When generating `set_arg` or `add_subdiagnostic` calls, move data rather than
356 // borrow it to avoid requiring clones - this must therefore be the last use of
357 // each field (for example, any formatting machinery that might refer to a field
358 // should be generated already).
359 if field.attrs.is_empty() {
360 let diag = &self.diag;
361 let ident = field.ident.as_ref().unwrap();
373 let name = attr.path.segments.last().unwrap().ident.to_string();
374 let (binding, needs_destructure) = match (name.as_str(), &inner_ty) {
375 // `primary_span` can accept a `Vec<Span>` so don't destructure that.
376 ("primary_span", FieldInnerTy::Vec(_)) => {
377 (quote! { #field_binding.clone() }, false)
379 // `subdiagnostics` are not derefed because they are bound by value.
380 ("subdiagnostic", _) => (quote! { #field_binding }, true),
381 _ => (quote! { *#field_binding }, true),
384 let generated_code = self
385 .generate_inner_field_code(
388 binding: binding_info,
389 ty: inner_ty.inner_type().unwrap_or(&field.ty),
394 .unwrap_or_else(|v| v.to_compile_error());
396 if needs_destructure {
397 inner_ty.with(field_binding, generated_code)
406 fn generate_inner_field_code(
410 binding: TokenStream,
411 ) -> Result<TokenStream, SessionDiagnosticDeriveError> {
412 let meta = attr.parse_meta()?;
414 Meta::Path(_) => self.generate_inner_field_code_path(attr, info, binding),
415 Meta::List(MetaList { .. }) => self.generate_inner_field_code_list(attr, info, binding),
416 _ => throw_invalid_attr!(attr, &meta),
420 fn generate_inner_field_code_path(
424 binding: TokenStream,
425 ) -> Result<TokenStream, SessionDiagnosticDeriveError> {
426 assert!(matches!(attr.parse_meta()?, Meta::Path(_)));
427 let diag = &self.diag;
429 let meta = attr.parse_meta()?;
431 let ident = &attr.path.segments.last().unwrap().ident;
432 let name = ident.to_string();
433 let name = name.as_str();
436 // Don't need to do anything - by virtue of the attribute existing, the
437 // `set_arg` call will not be generated.
441 report_error_if_not_applied_to_span(attr, &info)?;
443 #diag.set_span(#binding);
447 report_error_if_not_applied_to_span(attr, &info)?;
448 Ok(self.add_spanned_subdiagnostic(binding, ident, parse_quote! { _subdiag::label }))
451 let path = match name {
452 "note" => parse_quote! { _subdiag::note },
453 "help" => parse_quote! { _subdiag::help },
456 if type_matches_path(&info.ty, &["rustc_span", "Span"]) {
457 Ok(self.add_spanned_subdiagnostic(binding, ident, path))
458 } else if type_is_unit(&info.ty) {
459 Ok(self.add_subdiagnostic(ident, path))
461 report_type_error(attr, "`Span` or `()`")?;
464 "subdiagnostic" => Ok(quote! { #diag.subdiagnostic(#binding); }),
465 _ => throw_invalid_attr!(attr, &meta, |diag| {
467 "only `skip_arg`, `primary_span`, `label`, `note`, `help` and `subdiagnostic` \
468 are valid field attributes",
474 fn generate_inner_field_code_list(
478 binding: TokenStream,
479 ) -> Result<TokenStream, SessionDiagnosticDeriveError> {
480 let meta = attr.parse_meta()?;
481 let Meta::List(MetaList { ref path, ref nested, .. }) = meta else { unreachable!() };
483 let ident = &attr.path.segments.last().unwrap().ident;
484 let name = path.segments.last().unwrap().ident.to_string();
485 let name = name.as_ref();
487 "suggestion" | "suggestion_short" | "suggestion_hidden" | "suggestion_verbose" => {
488 return self.generate_inner_field_code_suggestion(attr, info);
490 "label" | "help" | "note" => (),
491 _ => throw_invalid_attr!(attr, &meta, |diag| {
493 "only `label`, `note`, `help` or `suggestion{,_short,_hidden,_verbose}` are \
494 valid field attributes",
499 // For `#[label(..)]`, `#[note(..)]` and `#[help(..)]`, the first nested element must be a
500 // path, e.g. `#[label(typeck::label)]`.
501 let mut nested_iter = nested.into_iter();
502 let msg = match nested_iter.next() {
503 Some(NestedMeta::Meta(Meta::Path(path))) => path.clone(),
504 Some(nested_attr) => throw_invalid_nested_attr!(attr, &nested_attr),
505 None => throw_invalid_attr!(attr, &meta),
508 // None of these attributes should have anything following the slug.
509 if nested_iter.next().is_some() {
510 throw_invalid_attr!(attr, &meta);
515 report_error_if_not_applied_to_span(attr, &info)?;
516 Ok(self.add_spanned_subdiagnostic(binding, ident, msg))
518 "note" | "help" if type_matches_path(&info.ty, &["rustc_span", "Span"]) => {
519 Ok(self.add_spanned_subdiagnostic(binding, ident, msg))
521 "note" | "help" if type_is_unit(&info.ty) => Ok(self.add_subdiagnostic(ident, msg)),
523 report_type_error(attr, "`Span` or `()`")?;
529 fn generate_inner_field_code_suggestion(
533 ) -> Result<TokenStream, SessionDiagnosticDeriveError> {
534 let diag = &self.diag;
536 let mut meta = attr.parse_meta()?;
537 let Meta::List(MetaList { ref path, ref mut nested, .. }) = meta else { unreachable!() };
539 let (span_field, mut applicability) = self.span_and_applicability_of_ty(info)?;
544 let mut nested_iter = nested.into_iter().peekable();
545 if let Some(nested_attr) = nested_iter.peek() {
546 if let NestedMeta::Meta(Meta::Path(path)) = nested_attr {
547 msg = Some(path.clone());
550 // Move the iterator forward if a path was found (don't otherwise so that
551 // code/applicability can be found or an error emitted).
553 let _ = nested_iter.next();
556 for nested_attr in nested_iter {
557 let meta = match nested_attr {
558 syn::NestedMeta::Meta(ref meta) => meta,
559 syn::NestedMeta::Lit(_) => throw_invalid_nested_attr!(attr, &nested_attr),
562 let nested_name = meta.path().segments.last().unwrap().ident.to_string();
563 let nested_name = nested_name.as_str();
565 Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
566 let span = meta.span().unwrap();
569 let formatted_str = self.build_format(&s.value(), s.span());
570 code = Some(formatted_str);
573 applicability = match applicability {
577 "applicability cannot be set in both the field and \
583 None => match Applicability::from_str(&s.value()) {
584 Ok(v) => Some(quote! { #v }),
586 span_err(span, "invalid applicability").emit();
592 _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
594 "only `message`, `code` and `applicability` are valid field \
600 _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
601 if matches!(meta, Meta::Path(_)) {
602 diag.help("a diagnostic slug must be the first argument to the attribute")
611 applicability.unwrap_or_else(|| quote!(rustc_errors::Applicability::Unspecified));
613 let name = path.segments.last().unwrap().ident.to_string();
614 let method = format_ident!("span_{}", name);
616 let msg = msg.unwrap_or_else(|| parse_quote! { _subdiag::suggestion });
617 let msg = quote! { rustc_errors::fluent::#msg };
618 let code = code.unwrap_or_else(|| quote! { String::new() });
620 Ok(quote! { #diag.#method(#span_field, #msg, #code, #applicability); })
623 /// Adds a spanned subdiagnostic by generating a `diag.span_$kind` call with the current slug
624 /// and `fluent_attr_identifier`.
625 fn add_spanned_subdiagnostic(
627 field_binding: TokenStream,
629 fluent_attr_identifier: Path,
631 let diag = &self.diag;
632 let fn_name = format_ident!("span_{}", kind);
636 rustc_errors::fluent::#fluent_attr_identifier
641 /// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current slug
642 /// and `fluent_attr_identifier`.
643 fn add_subdiagnostic(&self, kind: &Ident, fluent_attr_identifier: Path) -> TokenStream {
644 let diag = &self.diag;
646 #diag.#kind(rustc_errors::fluent::#fluent_attr_identifier);
650 fn span_and_applicability_of_ty(
653 ) -> Result<(TokenStream, Option<TokenStream>), SessionDiagnosticDeriveError> {
655 // If `ty` is `Span` w/out applicability, then use `Applicability::Unspecified`.
656 ty @ Type::Path(..) if type_matches_path(ty, &["rustc_span", "Span"]) => {
657 let binding = &info.binding.binding;
658 Ok((quote!(*#binding), None))
660 // If `ty` is `(Span, Applicability)` then return tokens accessing those.
661 Type::Tuple(tup) => {
662 let mut span_idx = None;
663 let mut applicability_idx = None;
665 for (idx, elem) in tup.elems.iter().enumerate() {
666 if type_matches_path(elem, &["rustc_span", "Span"]) {
667 if span_idx.is_none() {
668 span_idx = Some(syn::Index::from(idx));
672 "type of field annotated with `#[suggestion(...)]` contains more \
676 } else if type_matches_path(elem, &["rustc_errors", "Applicability"]) {
677 if applicability_idx.is_none() {
678 applicability_idx = Some(syn::Index::from(idx));
682 "type of field annotated with `#[suggestion(...)]` contains more \
683 than one Applicability"
689 if let Some(span_idx) = span_idx {
690 let binding = &info.binding.binding;
691 let span = quote!(#binding.#span_idx);
692 let applicability = applicability_idx
693 .map(|applicability_idx| quote!(#binding.#applicability_idx))
694 .unwrap_or_else(|| quote!(rustc_errors::Applicability::Unspecified));
696 return Ok((span, Some(applicability)));
699 throw_span_err!(info.span.unwrap(), "wrong types for suggestion", |diag| {
701 "`#[suggestion(...)]` on a tuple field must be applied to fields of type \
702 `(Span, Applicability)`",
706 // If `ty` isn't a `Span` or `(Span, Applicability)` then emit an error.
707 _ => throw_span_err!(info.span.unwrap(), "wrong field type for suggestion", |diag| {
709 "`#[suggestion(...)]` should be applied to fields of type `Span` or \
710 `(Span, Applicability)`",