1 #![deny(unused_must_use)]
3 use crate::diagnostics::error::{
4 span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err, DiagnosticDeriveError,
6 use crate::diagnostics::utils::{
7 report_error_if_not_applied_to_applicability, report_error_if_not_applied_to_span,
8 Applicability, FieldInfo, FieldInnerTy, HasFieldMap, SetOnce,
10 use proc_macro2::TokenStream;
11 use quote::{format_ident, quote};
12 use std::collections::HashMap;
14 use std::str::FromStr;
15 use syn::{spanned::Spanned, Meta, MetaList, MetaNameValue, NestedMeta, Path};
16 use synstructure::{BindingInfo, Structure, VariantInfo};
18 /// Which kind of suggestion is being created?
19 #[derive(Clone, Copy)]
20 enum SubdiagnosticSuggestionKind {
23 /// `#[suggestion_short]`
25 /// `#[suggestion_hidden]`
27 /// `#[suggestion_verbose]`
31 /// Which kind of subdiagnostic is being created from a variant?
32 #[derive(Clone, Copy)]
33 enum SubdiagnosticKind {
42 /// `#[suggestion{,_short,_hidden,_verbose}]`
43 Suggestion(SubdiagnosticSuggestionKind),
46 impl FromStr for SubdiagnosticKind {
49 fn from_str(s: &str) -> Result<Self, Self::Err> {
51 "label" => Ok(SubdiagnosticKind::Label),
52 "note" => Ok(SubdiagnosticKind::Note),
53 "help" => Ok(SubdiagnosticKind::Help),
54 "warning" => Ok(SubdiagnosticKind::Warn),
55 "suggestion" => Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Normal)),
56 "suggestion_short" => {
57 Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Short))
59 "suggestion_hidden" => {
60 Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Hidden))
62 "suggestion_verbose" => {
63 Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Verbose))
70 impl quote::IdentFragment for SubdiagnosticKind {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 SubdiagnosticKind::Label => write!(f, "label"),
74 SubdiagnosticKind::Note => write!(f, "note"),
75 SubdiagnosticKind::Help => write!(f, "help"),
76 SubdiagnosticKind::Warn => write!(f, "warn"),
77 SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Normal) => {
78 write!(f, "suggestion")
80 SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Short) => {
81 write!(f, "suggestion_short")
83 SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Hidden) => {
84 write!(f, "suggestion_hidden")
86 SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Verbose) => {
87 write!(f, "suggestion_verbose")
92 fn span(&self) -> Option<proc_macro2::Span> {
97 /// The central struct for constructing the `add_to_diagnostic` method from an annotated struct.
98 pub(crate) struct SessionSubdiagnosticDerive<'a> {
99 structure: Structure<'a>,
103 impl<'a> SessionSubdiagnosticDerive<'a> {
104 pub(crate) fn new(structure: Structure<'a>) -> Self {
105 let diag = format_ident!("diag");
106 Self { structure, diag }
109 pub(crate) fn into_tokens(self) -> TokenStream {
110 let SessionSubdiagnosticDerive { mut structure, diag } = self;
111 let implementation = {
112 let ast = structure.ast();
113 let span = ast.span().unwrap();
115 syn::Data::Struct(..) | syn::Data::Enum(..) => (),
116 syn::Data::Union(..) => {
119 "`#[derive(SessionSubdiagnostic)]` can only be used on structs and enums",
124 if matches!(ast.data, syn::Data::Enum(..)) {
125 for attr in &ast.attrs {
127 attr.span().unwrap(),
128 "unsupported type attribute for subdiagnostic enum",
134 structure.bind_with(|_| synstructure::BindStyle::Move);
135 let variants_ = structure.each_variant(|variant| {
136 // Build the mapping of field names to fields. This allows attributes to peek
137 // values from other fields.
138 let mut fields_map = HashMap::new();
139 for binding in variant.bindings() {
140 let field = binding.ast();
141 if let Some(ident) = &field.ident {
142 fields_map.insert(ident.to_string(), quote! { #binding });
146 let mut builder = SessionSubdiagnosticDeriveBuilder {
157 builder.into_tokens().unwrap_or_else(|v| v.to_compile_error())
167 let ret = structure.gen_impl(quote! {
168 gen impl rustc_errors::AddSubdiagnostic for @Self {
169 fn add_to_diagnostic(self, #diag: &mut rustc_errors::Diagnostic) {
170 use rustc_errors::{Applicability, IntoDiagnosticArg};
179 /// Tracks persistent information required for building up the call to add to the diagnostic
180 /// for the final generated method. This is a separate struct to `SessionSubdiagnosticDerive`
181 /// only to be able to destructure and split `self.builder` and the `self.structure` up to avoid a
182 /// double mut borrow later on.
183 struct SessionSubdiagnosticDeriveBuilder<'a> {
184 /// The identifier to use for the generated `DiagnosticBuilder` instance.
185 diag: &'a syn::Ident,
187 /// Info for the current variant (or the type if not an enum).
188 variant: &'a VariantInfo<'a>,
189 /// Span for the entire type.
190 span: proc_macro::Span,
192 /// Store a map of field name to its corresponding field. This is built on construction of the
194 fields: HashMap<String, TokenStream>,
196 /// Subdiagnostic kind of the type/variant.
197 kinds: Vec<(SubdiagnosticKind, proc_macro::Span)>,
199 /// Slugs of the subdiagnostic - corresponds to the Fluent identifier for the message - from the
200 /// `#[kind(slug)]` attribute on the type or variant.
201 slugs: Vec<(Path, proc_macro::Span)>,
202 /// If a suggestion, the code to suggest as a replacement - from the `#[kind(code = "...")]`
203 /// attribute on the type or variant.
204 code: Option<(TokenStream, proc_macro::Span)>,
206 /// Identifier for the binding to the `#[primary_span]` field.
207 span_field: Option<(proc_macro2::Ident, proc_macro::Span)>,
208 /// If a suggestion, the identifier for the binding to the `#[applicability]` field or a
209 /// `rustc_errors::Applicability::*` variant directly.
210 applicability: Option<(TokenStream, proc_macro::Span)>,
213 impl<'a> HasFieldMap for SessionSubdiagnosticDeriveBuilder<'a> {
214 fn get_field_binding(&self, field: &String) -> Option<&TokenStream> {
215 self.fields.get(field)
219 impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
220 fn identify_kind(&mut self) -> Result<(), DiagnosticDeriveError> {
221 for (i, attr) in self.variant.ast().attrs.iter().enumerate() {
222 let span = attr.span().unwrap();
224 let name = attr.path.segments.last().unwrap().ident.to_string();
225 let name = name.as_str();
227 let meta = attr.parse_meta()?;
228 let kind = match meta {
229 Meta::List(MetaList { ref nested, .. }) => {
230 let mut nested_iter = nested.into_iter();
231 if let Some(nested_attr) = nested_iter.next() {
233 NestedMeta::Meta(Meta::Path(path)) => {
234 self.slugs.push((path.clone(), span));
236 NestedMeta::Meta(meta @ Meta::NameValue(_))
238 meta.path().segments.last().unwrap().ident.to_string().as_str(),
239 "code" | "applicability"
242 // don't error for valid follow-up attributes
245 throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
247 "first argument of the attribute should be the diagnostic \
255 for nested_attr in nested_iter {
256 let meta = match nested_attr {
257 NestedMeta::Meta(ref meta) => meta,
258 _ => throw_invalid_nested_attr!(attr, &nested_attr),
261 let span = meta.span().unwrap();
262 let nested_name = meta.path().segments.last().unwrap().ident.to_string();
263 let nested_name = nested_name.as_str();
266 Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
269 let formatted_str = self.build_format(&s.value(), s.span());
270 self.code.set_once((formatted_str, span));
273 let value = match Applicability::from_str(&s.value()) {
276 span_err(span, "invalid applicability").emit();
277 Applicability::Unspecified
280 self.applicability.set_once((quote! { #value }, span));
282 _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
284 "only `code` and `applicability` are valid nested \
290 _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
291 if matches!(meta, Meta::Path(_)) {
293 "a diagnostic slug must be the first argument to the \
303 let Ok(kind) = SubdiagnosticKind::from_str(name) else {
304 throw_invalid_attr!(attr, &meta)
309 _ => throw_invalid_attr!(attr, &meta),
314 SubdiagnosticKind::Label | SubdiagnosticKind::Help | SubdiagnosticKind::Note
315 ) && self.code.is_some()
319 &format!("`code` is not a valid nested attribute of a `{}` attribute", name)
325 SubdiagnosticKind::Label | SubdiagnosticKind::Help | SubdiagnosticKind::Note
326 ) && self.applicability.is_some()
331 "`applicability` is not a valid nested attribute of a `{}` attribute",
337 if self.slugs.len() != i + 1 {
341 "diagnostic slug must be first argument of a `#[{}(...)]` attribute",
347 self.kinds.push((kind, span));
353 fn generate_field_code(
355 binding: &BindingInfo<'_>,
356 have_suggestion: bool,
357 ) -> Result<TokenStream, DiagnosticDeriveError> {
358 let ast = binding.ast();
360 let inner_ty = FieldInnerTy::from_type(&ast.ty);
361 let info = FieldInfo {
363 ty: inner_ty.inner_type().unwrap_or(&ast.ty),
367 for attr in &ast.attrs {
368 let name = attr.path.segments.last().unwrap().ident.to_string();
369 let name = name.as_str();
370 let span = attr.span().unwrap();
372 let meta = attr.parse_meta()?;
374 Meta::Path(_) => match name {
376 report_error_if_not_applied_to_span(attr, &info)?;
377 self.span_field.set_once((binding.binding.clone(), span));
378 return Ok(quote! {});
380 "applicability" if have_suggestion => {
381 report_error_if_not_applied_to_applicability(attr, &info)?;
382 let binding = binding.binding.clone();
383 self.applicability.set_once((quote! { #binding }, span));
384 return Ok(quote! {});
387 span_err(span, "`#[applicability]` is only valid on suggestions").emit();
388 return Ok(quote! {});
391 return Ok(quote! {});
393 _ => throw_invalid_attr!(attr, &meta, |diag| {
395 "only `primary_span`, `applicability` and `skip_arg` are valid field \
400 _ => throw_invalid_attr!(attr, &meta),
404 let ident = ast.ident.as_ref().unwrap();
406 let diag = &self.diag;
407 let generated = quote! {
414 Ok(inner_ty.with(binding, generated))
417 fn into_tokens(&mut self) -> Result<TokenStream, DiagnosticDeriveError> {
418 self.identify_kind()?;
419 if self.kinds.is_empty() {
421 self.variant.ast().ident.span().unwrap(),
422 "subdiagnostic kind not specified"
425 let have_suggestion =
426 self.kinds.iter().any(|(k, _)| matches!(k, SubdiagnosticKind::Suggestion(_)));
427 let mut args = TokenStream::new();
428 for binding in self.variant.bindings() {
430 .generate_field_code(binding, have_suggestion)
431 .unwrap_or_else(|v| v.to_compile_error());
434 let mut tokens = TokenStream::new();
435 for ((kind, _), (slug, _)) in self.kinds.iter().zip(&self.slugs) {
436 let code = match self.code.as_ref() {
437 Some((code, _)) => Some(quote! { #code }),
438 None if have_suggestion => {
439 span_err(self.span, "suggestion without `code = \"...\"`").emit();
440 Some(quote! { /* macro error */ "..." })
445 let span_field = self.span_field.as_ref().map(|(span, _)| span);
446 let applicability = match self.applicability.clone() {
447 Some((applicability, _)) => Some(applicability),
448 None if have_suggestion => {
449 span_err(self.span, "suggestion without `applicability`").emit();
450 Some(quote! { rustc_errors::Applicability::Unspecified })
455 let diag = &self.diag;
456 let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind);
457 let message = quote! { rustc_errors::fluent::#slug };
458 let call = if matches!(kind, SubdiagnosticKind::Suggestion(..)) {
459 if let Some(span) = span_field {
460 quote! { #diag.#name(#span, #message, #code, #applicability); }
462 span_err(self.span, "suggestion without `#[primary_span]` field").emit();
463 quote! { unreachable!(); }
465 } else if matches!(kind, SubdiagnosticKind::Label) {
466 if let Some(span) = span_field {
467 quote! { #diag.#name(#span, #message); }
469 span_err(self.span, "label without `#[primary_span]` field").emit();
470 quote! { unreachable!(); }
473 if let Some(span) = span_field {
474 quote! { #diag.#name(#span, #message); }
476 quote! { #diag.#name(#message); }
479 tokens.extend(quote! {