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 option_inner_ty, report_error_if_not_applied_to_span, type_matches_path, Applicability,
9 FieldInfo, HasFieldMap, SetOnce,
11 use proc_macro2::TokenStream;
12 use quote::{format_ident, quote};
13 use std::collections::HashMap;
14 use std::str::FromStr;
15 use syn::{spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, Type};
16 use synstructure::Structure;
18 /// The central struct for constructing the `into_diagnostic` method from an annotated struct.
19 pub(crate) struct SessionDiagnosticDerive<'a> {
20 structure: Structure<'a>,
21 builder: SessionDiagnosticDeriveBuilder,
24 impl<'a> SessionDiagnosticDerive<'a> {
25 pub(crate) fn new(diag: syn::Ident, sess: syn::Ident, structure: Structure<'a>) -> Self {
26 // Build the mapping of field names to fields. This allows attributes to peek values from
28 let mut fields_map = HashMap::new();
30 // Convenience bindings.
31 let ast = structure.ast();
33 if let syn::Data::Struct(syn::DataStruct { fields, .. }) = &ast.data {
34 for field in fields.iter() {
35 if let Some(ident) = &field.ident {
36 fields_map.insert(ident.to_string(), quote! { &self.#ident });
42 builder: SessionDiagnosticDeriveBuilder {
54 pub(crate) fn into_tokens(self) -> TokenStream {
55 let SessionDiagnosticDerive { mut structure, mut builder } = self;
57 let ast = structure.ast();
58 let attrs = &ast.attrs;
60 let (implementation, param_ty) = {
61 if let syn::Data::Struct(..) = ast.data {
63 let preamble = attrs.iter().map(|attr| {
65 .generate_structure_code(attr)
66 .unwrap_or_else(|v| v.to_compile_error())
74 // Generates calls to `span_label` and similar functions based on the attributes
75 // on fields. Code for suggestions uses formatting machinery and the value of
76 // other fields - because any given field can be referenced multiple times, it
77 // should be accessed through a borrow. When passing fields to `set_arg` (which
78 // happens below) for Fluent, we want to move the data, so that has to happen
79 // in a separate pass over the fields.
80 let attrs = structure.each(|field_binding| {
81 let field = field_binding.ast();
82 let result = field.attrs.iter().map(|attr| {
84 .generate_field_attr_code(
88 binding: field_binding,
93 .unwrap_or_else(|v| v.to_compile_error())
96 quote! { #(#result);* }
99 // When generating `set_arg` calls, move data rather than borrow it to avoid
100 // requiring clones - this must therefore be the last use of each field (for
101 // example, any formatting machinery that might refer to a field should be
102 // generated already).
103 structure.bind_with(|_| synstructure::BindStyle::Move);
104 let args = structure.each(|field_binding| {
105 let field = field_binding.ast();
106 // When a field has attributes like `#[label]` or `#[note]` then it doesn't
107 // need to be passed as an argument to the diagnostic. But when a field has no
108 // attributes then it must be passed as an argument to the diagnostic so that
109 // it can be referred to by Fluent messages.
110 if field.attrs.is_empty() {
111 let diag = &builder.diag;
112 let ident = field_binding.ast().ident.as_ref().unwrap();
116 #field_binding.into_diagnostic_arg()
124 let span = ast.span().unwrap();
125 let (diag, sess) = (&builder.diag, &builder.sess);
126 let init = match (builder.kind, builder.slug) {
128 span_err(span, "diagnostic kind not specified")
129 .help("use the `#[error(...)]` attribute to create an error")
131 return SessionDiagnosticDeriveError::ErrorHandled.to_compile_error();
133 (Some((kind, _)), None) => {
134 span_err(span, "`slug` not specified")
135 .help(&format!("use the `#[{}(slug = \"...\")]` attribute to set this diagnostic's slug", kind.descr()))
137 return SessionDiagnosticDeriveError::ErrorHandled.to_compile_error();
139 (Some((SessionDiagnosticKind::Error, _)), Some((slug, _))) => {
141 let mut #diag = #sess.struct_err(
142 rustc_errors::DiagnosticMessage::fluent(#slug),
146 (Some((SessionDiagnosticKind::Warn, _)), Some((slug, _))) => {
148 let mut #diag = #sess.struct_warn(
149 rustc_errors::DiagnosticMessage::fluent(#slug),
155 let implementation = quote! {
166 let param_ty = match builder.kind {
167 Some((SessionDiagnosticKind::Error, _)) => {
168 quote! { rustc_errors::ErrorGuaranteed }
170 Some((SessionDiagnosticKind::Warn, _)) => quote! { () },
174 (implementation, param_ty)
178 "`#[derive(SessionDiagnostic)]` can only be used on structs",
182 let implementation = SessionDiagnosticDeriveError::ErrorHandled.to_compile_error();
183 let param_ty = quote! { rustc_errors::ErrorGuaranteed };
184 (implementation, param_ty)
188 let sess = &builder.sess;
189 structure.gen_impl(quote! {
190 gen impl<'__session_diagnostic_sess> rustc_session::SessionDiagnostic<'__session_diagnostic_sess, #param_ty>
195 #sess: &'__session_diagnostic_sess rustc_session::parse::ParseSess
196 ) -> rustc_errors::DiagnosticBuilder<'__session_diagnostic_sess, #param_ty> {
197 use rustc_errors::IntoDiagnosticArg;
205 /// What kind of session diagnostic is being derived - an error or a warning?
206 #[derive(Copy, Clone)]
207 enum SessionDiagnosticKind {
214 impl SessionDiagnosticKind {
215 /// Returns human-readable string corresponding to the kind.
216 fn descr(&self) -> &'static str {
218 SessionDiagnosticKind::Error => "error",
219 SessionDiagnosticKind::Warn => "warning",
224 /// Tracks persistent information required for building up the individual calls to diagnostic
225 /// methods for the final generated method. This is a separate struct to `SessionDiagnosticDerive`
226 /// only to be able to destructure and split `self.builder` and the `self.structure` up to avoid a
227 /// double mut borrow later on.
228 struct SessionDiagnosticDeriveBuilder {
229 /// Name of the session parameter that's passed in to the `as_error` method.
231 /// The identifier to use for the generated `DiagnosticBuilder` instance.
234 /// Store a map of field name to its corresponding field. This is built on construction of the
236 fields: HashMap<String, TokenStream>,
238 /// Kind of diagnostic requested via the struct attribute.
239 kind: Option<(SessionDiagnosticKind, proc_macro::Span)>,
240 /// Slug is a mandatory part of the struct attribute as corresponds to the Fluent message that
241 /// has the actual diagnostic message.
242 slug: Option<(String, proc_macro::Span)>,
243 /// Error codes are a optional part of the struct attribute - this is only set to detect
244 /// multiple specifications.
245 code: Option<(String, proc_macro::Span)>,
248 impl HasFieldMap for SessionDiagnosticDeriveBuilder {
249 fn get_field_binding(&self, field: &String) -> Option<&TokenStream> {
250 self.fields.get(field)
254 impl SessionDiagnosticDeriveBuilder {
255 /// Establishes state in the `SessionDiagnosticDeriveBuilder` resulting from the struct
256 /// attributes like `#[error(..)#`, such as the diagnostic kind and slug. Generates
257 /// diagnostic builder calls for setting error code and creating note/help messages.
258 fn generate_structure_code(
261 ) -> Result<TokenStream, SessionDiagnosticDeriveError> {
262 let span = attr.span().unwrap();
264 let name = attr.path.segments.last().unwrap().ident.to_string();
265 let name = name.as_str();
266 let meta = attr.parse_meta()?;
268 if matches!(name, "help" | "note") && matches!(meta, Meta::Path(_) | Meta::NameValue(_)) {
269 let diag = &self.diag;
270 let slug = match &self.slug {
271 Some((slug, _)) => slug.as_str(),
272 None => throw_span_err!(
275 "`#[{}{}]` must come after `#[error(..)]` or `#[warn(..)]`",
279 Meta::NameValue(_) => " = ...",
285 let id = match meta {
286 Meta::Path(..) => quote! { #name },
287 Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
292 let fn_name = proc_macro2::Ident::new(name, attr.span());
295 #diag.#fn_name(rustc_errors::DiagnosticMessage::fluent_attr(#slug, #id));
299 let nested = match meta {
300 Meta::List(MetaList { ref nested, .. }) => nested,
301 _ => throw_invalid_attr!(attr, &meta),
304 let kind = match name {
305 "error" => SessionDiagnosticKind::Error,
306 "warning" => SessionDiagnosticKind::Warn,
307 _ => throw_invalid_attr!(attr, &meta, |diag| {
308 diag.help("only `error` and `warning` are valid attributes")
311 self.kind.set_once((kind, span));
313 let mut tokens = Vec::new();
314 for nested_attr in nested {
315 let meta = match nested_attr {
316 syn::NestedMeta::Meta(meta) => meta,
317 _ => throw_invalid_nested_attr!(attr, &nested_attr),
320 let path = meta.path();
321 let nested_name = path.segments.last().unwrap().ident.to_string();
323 // Struct attributes are only allowed to be applied once, and the diagnostic
324 // changes will be set in the initialisation code.
325 Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
326 let span = s.span().unwrap();
327 match nested_name.as_str() {
329 self.slug.set_once((s.value(), span));
332 self.code.set_once((s.value(), span));
333 let (diag, code) = (&self.diag, &self.code.as_ref().map(|(v, _)| v));
335 #diag.code(rustc_errors::DiagnosticId::Error(#code.to_string()));
338 _ => invalid_nested_attr(attr, &nested_attr)
339 .help("only `slug` and `code` are valid nested attributes")
343 _ => invalid_nested_attr(attr, &nested_attr).emit(),
347 Ok(tokens.drain(..).collect())
350 fn generate_field_attr_code(
352 attr: &syn::Attribute,
354 ) -> Result<TokenStream, SessionDiagnosticDeriveError> {
355 let field_binding = &info.binding.binding;
356 let option_ty = option_inner_ty(&info.ty);
357 let generated_code = self.generate_non_option_field_code(
361 binding: info.binding,
362 ty: option_ty.unwrap_or(&info.ty),
367 if option_ty.is_none() {
368 Ok(quote! { #generated_code })
371 if let Some(#field_binding) = #field_binding {
378 fn generate_non_option_field_code(
382 ) -> Result<TokenStream, SessionDiagnosticDeriveError> {
383 let diag = &self.diag;
384 let field_binding = &info.binding.binding;
386 let name = attr.path.segments.last().unwrap().ident.to_string();
387 let name = name.as_str();
389 let meta = attr.parse_meta()?;
391 Meta::Path(_) => match name {
393 // Don't need to do anything - by virtue of the attribute existing, the
394 // `set_arg` call will not be generated.
398 report_error_if_not_applied_to_span(attr, &info)?;
400 #diag.set_span(*#field_binding);
403 "label" | "note" | "help" => {
404 report_error_if_not_applied_to_span(attr, &info)?;
405 Ok(self.add_subdiagnostic(field_binding, name, name))
407 "subdiagnostic" => Ok(quote! { #diag.subdiagnostic(*#field_binding); }),
408 _ => throw_invalid_attr!(attr, &meta, |diag| {
410 .help("only `skip_arg`, `primary_span`, `label`, `note`, `help` and `subdiagnostic` are valid field attributes")
413 Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(ref s), .. }) => match name {
414 "label" | "note" | "help" => {
415 report_error_if_not_applied_to_span(attr, &info)?;
416 Ok(self.add_subdiagnostic(field_binding, name, &s.value()))
418 _ => throw_invalid_attr!(attr, &meta, |diag| {
419 diag.help("only `label`, `note` and `help` are valid field attributes")
422 Meta::List(MetaList { ref path, ref nested, .. }) => {
423 let name = path.segments.last().unwrap().ident.to_string();
424 let name = name.as_ref();
427 "suggestion" | "suggestion_short" | "suggestion_hidden"
428 | "suggestion_verbose" => (),
429 _ => throw_invalid_attr!(attr, &meta, |diag| {
431 .help("only `suggestion{,_short,_hidden,_verbose}` are valid field attributes")
435 let (span_field, mut applicability) = self.span_and_applicability_of_ty(info)?;
440 for nested_attr in nested {
441 let meta = match nested_attr {
442 syn::NestedMeta::Meta(ref meta) => meta,
443 syn::NestedMeta::Lit(_) => throw_invalid_nested_attr!(attr, &nested_attr),
446 let nested_name = meta.path().segments.last().unwrap().ident.to_string();
447 let nested_name = nested_name.as_str();
449 Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
450 let span = meta.span().unwrap();
453 msg = Some(s.value());
456 let formatted_str = self.build_format(&s.value(), s.span());
457 code = Some(formatted_str);
460 applicability = match applicability {
464 "applicability cannot be set in both the field and attribute"
468 None => match Applicability::from_str(&s.value()) {
469 Ok(v) => Some(quote! { #v }),
471 span_err(span, "invalid applicability").emit();
477 _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
479 "only `message`, `code` and `applicability` are valid field attributes",
484 _ => throw_invalid_nested_attr!(attr, &nested_attr),
488 let applicability = applicability
489 .unwrap_or_else(|| quote!(rustc_errors::Applicability::Unspecified));
491 let method = format_ident!("span_{}", name);
496 .map(|(slug, _)| slug.as_str())
497 .unwrap_or_else(|| "missing-slug");
498 let msg = msg.as_deref().unwrap_or("suggestion");
499 let msg = quote! { rustc_errors::DiagnosticMessage::fluent_attr(#slug, #msg) };
500 let code = code.unwrap_or_else(|| quote! { String::new() });
502 Ok(quote! { #diag.#method(#span_field, #msg, #code, #applicability); })
504 _ => throw_invalid_attr!(attr, &meta),
508 /// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current slug and
509 /// `fluent_attr_identifier`.
510 fn add_subdiagnostic(
512 field_binding: &proc_macro2::Ident,
514 fluent_attr_identifier: &str,
516 let diag = &self.diag;
519 self.slug.as_ref().map(|(slug, _)| slug.as_str()).unwrap_or_else(|| "missing-slug");
520 let fn_name = format_ident!("span_{}", kind);
524 rustc_errors::DiagnosticMessage::fluent_attr(#slug, #fluent_attr_identifier)
529 fn span_and_applicability_of_ty(
532 ) -> Result<(TokenStream, Option<TokenStream>), SessionDiagnosticDeriveError> {
534 // If `ty` is `Span` w/out applicability, then use `Applicability::Unspecified`.
535 ty @ Type::Path(..) if type_matches_path(ty, &["rustc_span", "Span"]) => {
536 let binding = &info.binding.binding;
537 Ok((quote!(*#binding), None))
539 // If `ty` is `(Span, Applicability)` then return tokens accessing those.
540 Type::Tuple(tup) => {
541 let mut span_idx = None;
542 let mut applicability_idx = None;
544 for (idx, elem) in tup.elems.iter().enumerate() {
545 if type_matches_path(elem, &["rustc_span", "Span"]) {
546 if span_idx.is_none() {
547 span_idx = Some(syn::Index::from(idx));
551 "type of field annotated with `#[suggestion(...)]` contains more than one `Span`"
554 } else if type_matches_path(elem, &["rustc_errors", "Applicability"]) {
555 if applicability_idx.is_none() {
556 applicability_idx = Some(syn::Index::from(idx));
560 "type of field annotated with `#[suggestion(...)]` contains more than one Applicability"
566 if let Some(span_idx) = span_idx {
567 let binding = &info.binding.binding;
568 let span = quote!(#binding.#span_idx);
569 let applicability = applicability_idx
570 .map(|applicability_idx| quote!(#binding.#applicability_idx))
571 .unwrap_or_else(|| quote!(rustc_errors::Applicability::Unspecified));
573 return Ok((span, Some(applicability)));
576 throw_span_err!(info.span.unwrap(), "wrong types for suggestion", |diag| {
577 diag.help("`#[suggestion(...)]` on a tuple field must be applied to fields of type `(Span, Applicability)`")
580 // If `ty` isn't a `Span` or `(Span, Applicability)` then emit an error.
581 _ => throw_span_err!(info.span.unwrap(), "wrong field type for suggestion", |diag| {
582 diag.help("`#[suggestion(...)]` should be applied to fields of type `Span` or `(Span, Applicability)`")