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
;
15 use syn
::{spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, Type}
;
16 use synstructure
::{BindingInfo, 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 // Keep track of which fields are subdiagnostics or have no attributes.
75 let mut subdiagnostics_or_empty
= std
::collections
::HashSet
::new();
77 // Generates calls to `span_label` and similar functions based on the attributes
78 // on fields. Code for suggestions uses formatting machinery and the value of
79 // other fields - because any given field can be referenced multiple times, it
80 // should be accessed through a borrow. When passing fields to `add_subdiagnostic`
81 // or `set_arg` (which happens below) for Fluent, we want to move the data, so that
82 // has to happen in a separate pass over the fields.
85 .filter(|field_binding
| {
86 let attrs
= &field_binding
.ast().attrs
;
89 && attrs
.iter().all(|attr
| {
91 != attr
.path
.segments
.last().unwrap().ident
.to_string()
94 subdiagnostics_or_empty
.insert(field_binding
.binding
.clone());
98 .each(|field_binding
| builder
.generate_field_attrs_code(field_binding
));
100 structure
.bind_with(|_
| synstructure
::BindStyle
::Move
);
101 // When a field has attributes like `#[label]` or `#[note]` then it doesn't
102 // need to be passed as an argument to the diagnostic. But when a field has no
103 // attributes or a `#[subdiagnostic]` attribute then it must be passed as an
104 // argument to the diagnostic so that it can be referred to by Fluent messages.
106 .filter(|field_binding
| {
107 subdiagnostics_or_empty
.contains(&field_binding
.binding
)
109 .each(|field_binding
| builder
.generate_field_attrs_code(field_binding
));
111 let span
= ast
.span().unwrap();
112 let (diag
, sess
) = (&builder
.diag
, &builder
.sess
);
113 let init
= match (builder
.kind
, builder
.slug
) {
115 span_err(span
, "diagnostic kind not specified")
116 .help("use the `#[error(...)]` attribute to create an error")
118 return SessionDiagnosticDeriveError
::ErrorHandled
.to_compile_error();
120 (Some((kind
, _
)), None
) => {
121 span_err(span
, "`slug` not specified")
122 .help(&format
!("use the `#[{}(slug = \"...\")]` attribute to set this diagnostic's slug", kind
.descr()))
124 return SessionDiagnosticDeriveError
::ErrorHandled
.to_compile_error();
126 (Some((SessionDiagnosticKind
::Error
, _
)), Some((slug
, _
))) => {
128 let mut #diag = #sess.struct_err(
129 rustc_errors
::DiagnosticMessage
::new(#slug),
133 (Some((SessionDiagnosticKind
::Warn
, _
)), Some((slug
, _
))) => {
135 let mut #diag = #sess.struct_warn(
136 rustc_errors
::DiagnosticMessage
::new(#slug),
142 let implementation
= quote
! {
153 let param_ty
= match builder
.kind
{
154 Some((SessionDiagnosticKind
::Error
, _
)) => {
155 quote
! { rustc_errors::ErrorGuaranteed }
157 Some((SessionDiagnosticKind
::Warn
, _
)) => quote
! { () }
,
161 (implementation
, param_ty
)
165 "`#[derive(SessionDiagnostic)]` can only be used on structs",
169 let implementation
= SessionDiagnosticDeriveError
::ErrorHandled
.to_compile_error();
170 let param_ty
= quote
! { rustc_errors::ErrorGuaranteed }
;
171 (implementation
, param_ty
)
175 let sess
= &builder
.sess
;
176 structure
.gen_impl(quote
! {
177 gen
impl<'__session_diagnostic_sess
> rustc_session
::SessionDiagnostic
<'__session_diagnostic_sess
, #param_ty>
182 #sess: &'__session_diagnostic_sess rustc_session::parse::ParseSess
183 ) -> rustc_errors
::DiagnosticBuilder
<'__session_diagnostic_sess
, #param_ty> {
184 use rustc_errors
::IntoDiagnosticArg
;
192 /// What kind of session diagnostic is being derived - an error or a warning?
193 #[derive(Copy, Clone)]
194 enum SessionDiagnosticKind
{
201 impl SessionDiagnosticKind
{
202 /// Returns human-readable string corresponding to the kind.
203 fn descr(&self) -> &'
static str {
205 SessionDiagnosticKind
::Error
=> "error",
206 SessionDiagnosticKind
::Warn
=> "warning",
211 /// Tracks persistent information required for building up the individual calls to diagnostic
212 /// methods for the final generated method. This is a separate struct to `SessionDiagnosticDerive`
213 /// only to be able to destructure and split `self.builder` and the `self.structure` up to avoid a
214 /// double mut borrow later on.
215 struct SessionDiagnosticDeriveBuilder
{
216 /// Name of the session parameter that's passed in to the `as_error` method.
218 /// The identifier to use for the generated `DiagnosticBuilder` instance.
221 /// Store a map of field name to its corresponding field. This is built on construction of the
223 fields
: HashMap
<String
, TokenStream
>,
225 /// Kind of diagnostic requested via the struct attribute.
226 kind
: Option
<(SessionDiagnosticKind
, proc_macro
::Span
)>,
227 /// Slug is a mandatory part of the struct attribute as corresponds to the Fluent message that
228 /// has the actual diagnostic message.
229 slug
: Option
<(String
, proc_macro
::Span
)>,
230 /// Error codes are a optional part of the struct attribute - this is only set to detect
231 /// multiple specifications.
232 code
: Option
<(String
, proc_macro
::Span
)>,
235 impl HasFieldMap
for SessionDiagnosticDeriveBuilder
{
236 fn get_field_binding(&self, field
: &String
) -> Option
<&TokenStream
> {
237 self.fields
.get(field
)
241 impl SessionDiagnosticDeriveBuilder
{
242 /// Establishes state in the `SessionDiagnosticDeriveBuilder` resulting from the struct
243 /// attributes like `#[error(..)#`, such as the diagnostic kind and slug. Generates
244 /// diagnostic builder calls for setting error code and creating note/help messages.
245 fn generate_structure_code(
248 ) -> Result
<TokenStream
, SessionDiagnosticDeriveError
> {
249 let span
= attr
.span().unwrap();
251 let name
= attr
.path
.segments
.last().unwrap().ident
.to_string();
252 let name
= name
.as_str();
253 let meta
= attr
.parse_meta()?
;
255 if matches
!(name
, "help" | "note") && matches
!(meta
, Meta
::Path(_
) | Meta
::NameValue(_
)) {
256 let diag
= &self.diag
;
257 let id
= match meta
{
258 Meta
::Path(..) => quote
! { #name }
,
259 Meta
::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }
) => {
264 let fn_name
= proc_macro2
::Ident
::new(name
, attr
.span());
267 #diag.#fn_name(rustc_errors::SubdiagnosticMessage::attr(#id));
271 let nested
= match meta
{
272 Meta
::List(MetaList { ref nested, .. }
) => nested
,
273 _
=> throw_invalid_attr
!(attr
, &meta
),
276 let kind
= match name
{
277 "error" => SessionDiagnosticKind
::Error
,
278 "warning" => SessionDiagnosticKind
::Warn
,
279 _
=> throw_invalid_attr
!(attr
, &meta
, |diag
| {
280 diag
.help("only `error` and `warning` are valid attributes")
283 self.kind
.set_once((kind
, span
));
285 let mut tokens
= Vec
::new();
286 for nested_attr
in nested
{
287 let meta
= match nested_attr
{
288 syn
::NestedMeta
::Meta(meta
) => meta
,
289 _
=> throw_invalid_nested_attr
!(attr
, &nested_attr
),
292 let path
= meta
.path();
293 let nested_name
= path
.segments
.last().unwrap().ident
.to_string();
295 // Struct attributes are only allowed to be applied once, and the diagnostic
296 // changes will be set in the initialisation code.
297 Meta
::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }
) => {
298 let span
= s
.span().unwrap();
299 match nested_name
.as_str() {
301 self.slug
.set_once((s
.value(), span
));
304 self.code
.set_once((s
.value(), span
));
305 let (diag
, code
) = (&self.diag
, &self.code
.as_ref().map(|(v
, _
)| v
));
307 #diag.code(rustc_errors::DiagnosticId::Error(#code.to_string()));
310 _
=> invalid_nested_attr(attr
, &nested_attr
)
311 .help("only `slug` and `code` are valid nested attributes")
315 _
=> invalid_nested_attr(attr
, &nested_attr
).emit(),
319 Ok(tokens
.drain(..).collect())
322 fn generate_field_attrs_code(&mut self, binding_info
: &BindingInfo
<'_
>) -> TokenStream
{
323 let field
= binding_info
.ast();
324 let field_binding
= &binding_info
.binding
;
326 let inner_ty
= FieldInnerTy
::from_type(&field
.ty
);
328 // When generating `set_arg` or `add_subdiagnostic` calls, move data rather than
329 // borrow it to avoid requiring clones - this must therefore be the last use of
330 // each field (for example, any formatting machinery that might refer to a field
331 // should be generated already).
332 if field
.attrs
.is_empty() {
333 let diag
= &self.diag
;
334 let ident
= field
.ident
.as_ref().unwrap();
346 let name
= attr
.path
.segments
.last().unwrap().ident
.to_string();
347 let (binding
, needs_destructure
) = match (name
.as_str(), &inner_ty
) {
348 // `primary_span` can accept a `Vec<Span>` so don't destructure that.
349 ("primary_span", FieldInnerTy
::Vec(_
)) => {
350 (quote
! { #field_binding.clone() }
, false)
352 // `subdiagnostics` are not derefed because they are bound by value.
353 ("subdiagnostic", _
) => (quote
! { #field_binding }
, true),
354 _
=> (quote
! { *#field_binding }
, true),
357 let generated_code
= self
358 .generate_inner_field_code(
361 binding
: binding_info
,
362 ty
: inner_ty
.inner_type().unwrap_or(&field
.ty
),
367 .unwrap_or_else(|v
| v
.to_compile_error());
369 if needs_destructure
{
370 inner_ty
.with(field_binding
, generated_code
)
379 fn generate_inner_field_code(
383 binding
: TokenStream
,
384 ) -> Result
<TokenStream
, SessionDiagnosticDeriveError
> {
385 let diag
= &self.diag
;
387 let ident
= &attr
.path
.segments
.last().unwrap().ident
;
388 let name
= ident
.to_string();
389 let name
= name
.as_str();
391 let meta
= attr
.parse_meta()?
;
393 Meta
::Path(_
) => match name
{
395 // Don't need to do anything - by virtue of the attribute existing, the
396 // `set_arg` call will not be generated.
400 report_error_if_not_applied_to_span(attr
, &info
)?
;
402 #diag.set_span(#binding);
406 report_error_if_not_applied_to_span(attr
, &info
)?
;
407 Ok(self.add_spanned_subdiagnostic(binding
, ident
, name
))
410 if type_matches_path(&info
.ty
, &["rustc_span", "Span"]) {
411 Ok(self.add_spanned_subdiagnostic(binding
, ident
, name
))
412 } else if type_is_unit(&info
.ty
) {
413 Ok(self.add_subdiagnostic(ident
, name
))
415 report_type_error(attr
, "`Span` or `()`")?
;
418 "subdiagnostic" => Ok(quote
! { #diag.subdiagnostic(#binding); }
),
419 _
=> throw_invalid_attr
!(attr
, &meta
, |diag
| {
421 .help("only `skip_arg`, `primary_span`, `label`, `note`, `help` and `subdiagnostic` are valid field attributes")
424 Meta
::NameValue(MetaNameValue { lit: syn::Lit::Str(ref s), .. }
) => match name
{
426 report_error_if_not_applied_to_span(attr
, &info
)?
;
427 Ok(self.add_spanned_subdiagnostic(binding
, ident
, &s
.value()))
430 if type_matches_path(&info
.ty
, &["rustc_span", "Span"]) {
431 Ok(self.add_spanned_subdiagnostic(binding
, ident
, &s
.value()))
432 } else if type_is_unit(&info
.ty
) {
433 Ok(self.add_subdiagnostic(ident
, &s
.value()))
435 report_type_error(attr
, "`Span` or `()`")?
;
438 _
=> throw_invalid_attr
!(attr
, &meta
, |diag
| {
439 diag
.help("only `label`, `note` and `help` are valid field attributes")
442 Meta
::List(MetaList { ref path, ref nested, .. }
) => {
443 let name
= path
.segments
.last().unwrap().ident
.to_string();
444 let name
= name
.as_ref();
447 "suggestion" | "suggestion_short" | "suggestion_hidden"
448 | "suggestion_verbose" => (),
449 _
=> throw_invalid_attr
!(attr
, &meta
, |diag
| {
451 .help("only `suggestion{,_short,_hidden,_verbose}` are valid field attributes")
455 let (span_field
, mut applicability
) = self.span_and_applicability_of_ty(info
)?
;
460 for nested_attr
in nested
{
461 let meta
= match nested_attr
{
462 syn
::NestedMeta
::Meta(ref meta
) => meta
,
463 syn
::NestedMeta
::Lit(_
) => throw_invalid_nested_attr
!(attr
, &nested_attr
),
466 let nested_name
= meta
.path().segments
.last().unwrap().ident
.to_string();
467 let nested_name
= nested_name
.as_str();
469 Meta
::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }
) => {
470 let span
= meta
.span().unwrap();
473 msg
= Some(s
.value());
476 let formatted_str
= self.build_format(&s
.value(), s
.span());
477 code
= Some(formatted_str
);
480 applicability
= match applicability
{
484 "applicability cannot be set in both the field and attribute"
488 None
=> match Applicability
::from_str(&s
.value()) {
489 Ok(v
) => Some(quote
! { #v }
),
491 span_err(span
, "invalid applicability").emit();
497 _
=> throw_invalid_nested_attr
!(attr
, &nested_attr
, |diag
| {
499 "only `message`, `code` and `applicability` are valid field attributes",
504 _
=> throw_invalid_nested_attr
!(attr
, &nested_attr
),
508 let applicability
= applicability
509 .unwrap_or_else(|| quote
!(rustc_errors
::Applicability
::Unspecified
));
511 let method
= format_ident
!("span_{}", name
);
513 let msg
= msg
.as_deref().unwrap_or("suggestion");
514 let msg
= quote
! { rustc_errors::SubdiagnosticMessage::attr(#msg) }
;
515 let code
= code
.unwrap_or_else(|| quote
! { String::new() }
);
517 Ok(quote
! { #diag.#method(#span_field, #msg, #code, #applicability); }
)
519 _
=> throw_invalid_attr
!(attr
, &meta
),
523 /// Adds a spanned subdiagnostic by generating a `diag.span_$kind` call with the current slug
524 /// and `fluent_attr_identifier`.
525 fn add_spanned_subdiagnostic(
527 field_binding
: TokenStream
,
529 fluent_attr_identifier
: &str,
531 let diag
= &self.diag
;
532 let fn_name
= format_ident
!("span_{}", kind
);
536 rustc_errors
::SubdiagnosticMessage
::attr(#fluent_attr_identifier)
541 /// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current slug
542 /// and `fluent_attr_identifier`.
543 fn add_subdiagnostic(&self, kind
: &Ident
, fluent_attr_identifier
: &str) -> TokenStream
{
544 let diag
= &self.diag
;
546 #diag.#kind(rustc_errors::SubdiagnosticMessage::attr(#fluent_attr_identifier));
550 fn span_and_applicability_of_ty(
553 ) -> Result
<(TokenStream
, Option
<TokenStream
>), SessionDiagnosticDeriveError
> {
555 // If `ty` is `Span` w/out applicability, then use `Applicability::Unspecified`.
556 ty @ Type
::Path(..) if type_matches_path(ty
, &["rustc_span", "Span"]) => {
557 let binding
= &info
.binding
.binding
;
558 Ok((quote
!(*#binding), None))
560 // If `ty` is `(Span, Applicability)` then return tokens accessing those.
561 Type
::Tuple(tup
) => {
562 let mut span_idx
= None
;
563 let mut applicability_idx
= None
;
565 for (idx
, elem
) in tup
.elems
.iter().enumerate() {
566 if type_matches_path(elem
, &["rustc_span", "Span"]) {
567 if span_idx
.is_none() {
568 span_idx
= Some(syn
::Index
::from(idx
));
572 "type of field annotated with `#[suggestion(...)]` contains more than one `Span`"
575 } else if type_matches_path(elem
, &["rustc_errors", "Applicability"]) {
576 if applicability_idx
.is_none() {
577 applicability_idx
= Some(syn
::Index
::from(idx
));
581 "type of field annotated with `#[suggestion(...)]` contains more than one Applicability"
587 if let Some(span_idx
) = span_idx
{
588 let binding
= &info
.binding
.binding
;
589 let span
= quote
!(#binding.#span_idx);
590 let applicability
= applicability_idx
591 .map(|applicability_idx
| quote
!(#binding.#applicability_idx))
592 .unwrap_or_else(|| quote
!(rustc_errors
::Applicability
::Unspecified
));
594 return Ok((span
, Some(applicability
)));
597 throw_span_err
!(info
.span
.unwrap(), "wrong types for suggestion", |diag
| {
598 diag
.help("`#[suggestion(...)]` on a tuple field must be applied to fields of type `(Span, Applicability)`")
601 // If `ty` isn't a `Span` or `(Span, Applicability)` then emit an error.
602 _
=> throw_span_err
!(info
.span
.unwrap(), "wrong field type for suggestion", |diag
| {
603 diag
.help("`#[suggestion(...)]` should be applied to fields of type `Span` or `(Span, Applicability)`")