2022-04-27 02:57:44 +01:00
|
|
|
#![deny(unused_must_use)]
|
|
|
|
|
|
2022-04-27 04:06:13 +01:00
|
|
|
use crate::diagnostics::error::{
|
|
|
|
|
invalid_nested_attr, span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err,
|
|
|
|
|
SessionDiagnosticDeriveError,
|
|
|
|
|
};
|
2022-04-27 02:57:44 +01:00
|
|
|
use crate::diagnostics::utils::{
|
2022-04-27 04:28:21 +01:00
|
|
|
option_inner_ty, report_error_if_not_applied_to_span, type_matches_path, FieldInfo,
|
|
|
|
|
HasFieldMap, SetOnce,
|
2022-04-27 02:57:44 +01:00
|
|
|
};
|
|
|
|
|
use proc_macro2::TokenStream;
|
|
|
|
|
use quote::{format_ident, quote};
|
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
use syn::{spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, Type};
|
|
|
|
|
use synstructure::Structure;
|
|
|
|
|
|
|
|
|
|
/// The central struct for constructing the `as_error` method from an annotated struct.
|
|
|
|
|
pub(crate) struct SessionDiagnosticDerive<'a> {
|
|
|
|
|
structure: Structure<'a>,
|
|
|
|
|
builder: SessionDiagnosticDeriveBuilder,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> SessionDiagnosticDerive<'a> {
|
|
|
|
|
pub(crate) fn new(diag: syn::Ident, sess: syn::Ident, structure: Structure<'a>) -> Self {
|
|
|
|
|
// Build the mapping of field names to fields. This allows attributes to peek values from
|
|
|
|
|
// other fields.
|
|
|
|
|
let mut fields_map = HashMap::new();
|
|
|
|
|
|
|
|
|
|
// Convenience bindings.
|
|
|
|
|
let ast = structure.ast();
|
|
|
|
|
|
|
|
|
|
if let syn::Data::Struct(syn::DataStruct { fields, .. }) = &ast.data {
|
|
|
|
|
for field in fields.iter() {
|
|
|
|
|
if let Some(ident) = &field.ident {
|
|
|
|
|
fields_map.insert(ident.to_string(), quote! { &self.#ident });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Self {
|
|
|
|
|
builder: SessionDiagnosticDeriveBuilder {
|
|
|
|
|
diag,
|
|
|
|
|
sess,
|
|
|
|
|
fields: fields_map,
|
|
|
|
|
kind: None,
|
|
|
|
|
code: None,
|
|
|
|
|
slug: None,
|
|
|
|
|
},
|
|
|
|
|
structure,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn into_tokens(self) -> TokenStream {
|
|
|
|
|
let SessionDiagnosticDerive { mut structure, mut builder } = self;
|
|
|
|
|
|
|
|
|
|
let ast = structure.ast();
|
|
|
|
|
let attrs = &ast.attrs;
|
|
|
|
|
|
|
|
|
|
let (implementation, param_ty) = {
|
|
|
|
|
if let syn::Data::Struct(..) = ast.data {
|
|
|
|
|
let preamble = {
|
|
|
|
|
let preamble = attrs.iter().map(|attr| {
|
|
|
|
|
builder
|
|
|
|
|
.generate_structure_code(attr)
|
|
|
|
|
.unwrap_or_else(|v| v.to_compile_error())
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
quote! {
|
|
|
|
|
#(#preamble)*;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Generates calls to `span_label` and similar functions based on the attributes
|
|
|
|
|
// on fields. Code for suggestions uses formatting machinery and the value of
|
|
|
|
|
// other fields - because any given field can be referenced multiple times, it
|
|
|
|
|
// should be accessed through a borrow. When passing fields to `set_arg` (which
|
|
|
|
|
// happens below) for Fluent, we want to move the data, so that has to happen
|
|
|
|
|
// in a separate pass over the fields.
|
|
|
|
|
let attrs = structure.each(|field_binding| {
|
|
|
|
|
let field = field_binding.ast();
|
|
|
|
|
let result = field.attrs.iter().map(|attr| {
|
|
|
|
|
builder
|
|
|
|
|
.generate_field_attr_code(
|
|
|
|
|
attr,
|
|
|
|
|
FieldInfo {
|
|
|
|
|
vis: &field.vis,
|
|
|
|
|
binding: field_binding,
|
|
|
|
|
ty: &field.ty,
|
|
|
|
|
span: &field.span(),
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.unwrap_or_else(|v| v.to_compile_error())
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
quote! { #(#result);* }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// When generating `set_arg` calls, move data rather than borrow it to avoid
|
|
|
|
|
// requiring clones - this must therefore be the last use of each field (for
|
|
|
|
|
// example, any formatting machinery that might refer to a field should be
|
|
|
|
|
// generated already).
|
|
|
|
|
structure.bind_with(|_| synstructure::BindStyle::Move);
|
|
|
|
|
let args = structure.each(|field_binding| {
|
|
|
|
|
let field = field_binding.ast();
|
|
|
|
|
// When a field has attributes like `#[label]` or `#[note]` then it doesn't
|
|
|
|
|
// need to be passed as an argument to the diagnostic. But when a field has no
|
|
|
|
|
// attributes then it must be passed as an argument to the diagnostic so that
|
|
|
|
|
// it can be referred to by Fluent messages.
|
|
|
|
|
if field.attrs.is_empty() {
|
|
|
|
|
let diag = &builder.diag;
|
|
|
|
|
let ident = field_binding.ast().ident.as_ref().unwrap();
|
|
|
|
|
quote! {
|
|
|
|
|
#diag.set_arg(
|
|
|
|
|
stringify!(#ident),
|
|
|
|
|
#field_binding.into_diagnostic_arg()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
quote! {}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let span = ast.span().unwrap();
|
|
|
|
|
let (diag, sess) = (&builder.diag, &builder.sess);
|
|
|
|
|
let init = match (builder.kind, builder.slug) {
|
|
|
|
|
(None, _) => {
|
|
|
|
|
span_err(span, "diagnostic kind not specified")
|
|
|
|
|
.help("use the `#[error(...)]` attribute to create an error")
|
|
|
|
|
.emit();
|
|
|
|
|
return SessionDiagnosticDeriveError::ErrorHandled.to_compile_error();
|
|
|
|
|
}
|
|
|
|
|
(Some((kind, _)), None) => {
|
|
|
|
|
span_err(span, "`slug` not specified")
|
|
|
|
|
.help(&format!("use the `#[{}(slug = \"...\")]` attribute to set this diagnostic's slug", kind.descr()))
|
|
|
|
|
.emit();
|
|
|
|
|
return SessionDiagnosticDeriveError::ErrorHandled.to_compile_error();
|
|
|
|
|
}
|
|
|
|
|
(Some((SessionDiagnosticKind::Error, _)), Some((slug, _))) => {
|
|
|
|
|
quote! {
|
|
|
|
|
let mut #diag = #sess.struct_err(
|
|
|
|
|
rustc_errors::DiagnosticMessage::fluent(#slug),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
(Some((SessionDiagnosticKind::Warn, _)), Some((slug, _))) => {
|
|
|
|
|
quote! {
|
|
|
|
|
let mut #diag = #sess.struct_warn(
|
|
|
|
|
rustc_errors::DiagnosticMessage::fluent(#slug),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let implementation = quote! {
|
|
|
|
|
#init
|
|
|
|
|
#preamble
|
|
|
|
|
match self {
|
|
|
|
|
#attrs
|
|
|
|
|
}
|
|
|
|
|
match self {
|
|
|
|
|
#args
|
|
|
|
|
}
|
|
|
|
|
#diag
|
|
|
|
|
};
|
|
|
|
|
let param_ty = match builder.kind {
|
|
|
|
|
Some((SessionDiagnosticKind::Error, _)) => {
|
|
|
|
|
quote! { rustc_errors::ErrorGuaranteed }
|
|
|
|
|
}
|
|
|
|
|
Some((SessionDiagnosticKind::Warn, _)) => quote! { () },
|
|
|
|
|
_ => unreachable!(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
(implementation, param_ty)
|
|
|
|
|
} else {
|
|
|
|
|
span_err(
|
|
|
|
|
ast.span().unwrap(),
|
|
|
|
|
"`#[derive(SessionDiagnostic)]` can only be used on structs",
|
|
|
|
|
)
|
|
|
|
|
.emit();
|
|
|
|
|
|
|
|
|
|
let implementation = SessionDiagnosticDeriveError::ErrorHandled.to_compile_error();
|
|
|
|
|
let param_ty = quote! { rustc_errors::ErrorGuaranteed };
|
|
|
|
|
(implementation, param_ty)
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let sess = &builder.sess;
|
|
|
|
|
structure.gen_impl(quote! {
|
|
|
|
|
gen impl<'__session_diagnostic_sess> rustc_session::SessionDiagnostic<'__session_diagnostic_sess, #param_ty>
|
|
|
|
|
for @Self
|
|
|
|
|
{
|
|
|
|
|
fn into_diagnostic(
|
|
|
|
|
self,
|
|
|
|
|
#sess: &'__session_diagnostic_sess rustc_session::parse::ParseSess
|
|
|
|
|
) -> rustc_errors::DiagnosticBuilder<'__session_diagnostic_sess, #param_ty> {
|
|
|
|
|
use rustc_errors::IntoDiagnosticArg;
|
|
|
|
|
#implementation
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// What kind of session diagnostic is being derived - an error or a warning?
|
|
|
|
|
#[derive(Copy, Clone)]
|
|
|
|
|
enum SessionDiagnosticKind {
|
|
|
|
|
/// `#[error(..)]`
|
|
|
|
|
Error,
|
|
|
|
|
/// `#[warn(..)]`
|
|
|
|
|
Warn,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl SessionDiagnosticKind {
|
|
|
|
|
/// Returns human-readable string corresponding to the kind.
|
|
|
|
|
fn descr(&self) -> &'static str {
|
|
|
|
|
match self {
|
|
|
|
|
SessionDiagnosticKind::Error => "error",
|
|
|
|
|
SessionDiagnosticKind::Warn => "warning",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Tracks persistent information required for building up the individual calls to diagnostic
|
|
|
|
|
/// methods for the final generated method. This is a separate struct to `SessionDiagnosticDerive`
|
|
|
|
|
/// only to be able to destructure and split `self.builder` and the `self.structure` up to avoid a
|
|
|
|
|
/// double mut borrow later on.
|
|
|
|
|
struct SessionDiagnosticDeriveBuilder {
|
|
|
|
|
/// Name of the session parameter that's passed in to the `as_error` method.
|
|
|
|
|
sess: syn::Ident,
|
|
|
|
|
/// The identifier to use for the generated `DiagnosticBuilder` instance.
|
|
|
|
|
diag: syn::Ident,
|
|
|
|
|
|
|
|
|
|
/// Store a map of field name to its corresponding field. This is built on construction of the
|
|
|
|
|
/// derive builder.
|
|
|
|
|
fields: HashMap<String, TokenStream>,
|
|
|
|
|
|
|
|
|
|
/// Kind of diagnostic requested via the struct attribute.
|
|
|
|
|
kind: Option<(SessionDiagnosticKind, proc_macro::Span)>,
|
|
|
|
|
/// Slug is a mandatory part of the struct attribute as corresponds to the Fluent message that
|
|
|
|
|
/// has the actual diagnostic message.
|
|
|
|
|
slug: Option<(String, proc_macro::Span)>,
|
|
|
|
|
/// Error codes are a optional part of the struct attribute - this is only set to detect
|
|
|
|
|
/// multiple specifications.
|
2022-04-27 04:28:21 +01:00
|
|
|
code: Option<(String, proc_macro::Span)>,
|
2022-04-27 02:57:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl HasFieldMap for SessionDiagnosticDeriveBuilder {
|
|
|
|
|
fn get_field_binding(&self, field: &String) -> Option<&TokenStream> {
|
|
|
|
|
self.fields.get(field)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl SessionDiagnosticDeriveBuilder {
|
|
|
|
|
/// Establishes state in the `SessionDiagnosticDeriveBuilder` resulting from the struct
|
|
|
|
|
/// attributes like `#[error(..)#`, such as the diagnostic kind and slug. Generates
|
|
|
|
|
/// diagnostic builder calls for setting error code and creating note/help messages.
|
|
|
|
|
fn generate_structure_code(
|
|
|
|
|
&mut self,
|
|
|
|
|
attr: &Attribute,
|
|
|
|
|
) -> Result<TokenStream, SessionDiagnosticDeriveError> {
|
|
|
|
|
let span = attr.span().unwrap();
|
|
|
|
|
|
|
|
|
|
let name = attr.path.segments.last().unwrap().ident.to_string();
|
|
|
|
|
let name = name.as_str();
|
|
|
|
|
let meta = attr.parse_meta()?;
|
|
|
|
|
|
|
|
|
|
if matches!(name, "help" | "note") && matches!(meta, Meta::Path(_) | Meta::NameValue(_)) {
|
|
|
|
|
let diag = &self.diag;
|
|
|
|
|
let slug = match &self.slug {
|
|
|
|
|
Some((slug, _)) => slug.as_str(),
|
|
|
|
|
None => throw_span_err!(
|
|
|
|
|
span,
|
|
|
|
|
&format!(
|
|
|
|
|
"`#[{}{}]` must come after `#[error(..)]` or `#[warn(..)]`",
|
|
|
|
|
name,
|
|
|
|
|
match meta {
|
|
|
|
|
Meta::Path(_) => "",
|
|
|
|
|
Meta::NameValue(_) => " = ...",
|
|
|
|
|
_ => unreachable!(),
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
),
|
|
|
|
|
};
|
|
|
|
|
let id = match meta {
|
|
|
|
|
Meta::Path(..) => quote! { #name },
|
|
|
|
|
Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
|
|
|
|
|
quote! { #s }
|
|
|
|
|
}
|
|
|
|
|
_ => unreachable!(),
|
|
|
|
|
};
|
|
|
|
|
let fn_name = proc_macro2::Ident::new(name, attr.span());
|
|
|
|
|
|
|
|
|
|
return Ok(quote! {
|
|
|
|
|
#diag.#fn_name(rustc_errors::DiagnosticMessage::fluent_attr(#slug, #id));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let nested = match meta {
|
2022-04-27 04:06:13 +01:00
|
|
|
Meta::List(MetaList { ref nested, .. }) => nested,
|
|
|
|
|
_ => throw_invalid_attr!(attr, &meta),
|
2022-04-27 02:57:44 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let kind = match name {
|
|
|
|
|
"error" => SessionDiagnosticKind::Error,
|
|
|
|
|
"warning" => SessionDiagnosticKind::Warn,
|
2022-04-27 04:06:13 +01:00
|
|
|
_ => throw_invalid_attr!(attr, &meta, |diag| {
|
|
|
|
|
diag.help("only `error` and `warning` are valid attributes")
|
|
|
|
|
}),
|
2022-04-27 02:57:44 +01:00
|
|
|
};
|
2022-04-27 04:28:21 +01:00
|
|
|
self.kind.set_once((kind, span));
|
2022-04-27 02:57:44 +01:00
|
|
|
|
|
|
|
|
let mut tokens = Vec::new();
|
2022-04-27 04:06:13 +01:00
|
|
|
for nested_attr in nested {
|
|
|
|
|
let meta = match nested_attr {
|
2022-04-27 02:57:44 +01:00
|
|
|
syn::NestedMeta::Meta(meta) => meta,
|
2022-04-27 04:06:13 +01:00
|
|
|
_ => throw_invalid_nested_attr!(attr, &nested_attr),
|
2022-04-27 02:57:44 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let path = meta.path();
|
|
|
|
|
let nested_name = path.segments.last().unwrap().ident.to_string();
|
|
|
|
|
match &meta {
|
|
|
|
|
// Struct attributes are only allowed to be applied once, and the diagnostic
|
|
|
|
|
// changes will be set in the initialisation code.
|
|
|
|
|
Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
|
2022-04-27 04:28:21 +01:00
|
|
|
let span = s.span().unwrap();
|
2022-04-27 02:57:44 +01:00
|
|
|
match nested_name.as_str() {
|
|
|
|
|
"slug" => {
|
2022-04-27 04:28:21 +01:00
|
|
|
self.slug.set_once((s.value(), span));
|
2022-04-27 02:57:44 +01:00
|
|
|
}
|
|
|
|
|
"code" => {
|
2022-04-27 04:28:21 +01:00
|
|
|
self.code.set_once((s.value(), span));
|
|
|
|
|
let (diag, code) = (&self.diag, &self.code.as_ref().map(|(v, _)| v));
|
|
|
|
|
tokens.push(quote! {
|
|
|
|
|
#diag.code(rustc_errors::DiagnosticId::Error(#code.to_string()));
|
|
|
|
|
});
|
2022-04-27 02:57:44 +01:00
|
|
|
}
|
2022-04-27 04:06:13 +01:00
|
|
|
_ => invalid_nested_attr(attr, &nested_attr)
|
|
|
|
|
.help("only `slug` and `code` are valid nested attributes")
|
|
|
|
|
.emit(),
|
2022-04-27 02:57:44 +01:00
|
|
|
}
|
|
|
|
|
}
|
2022-04-27 04:06:13 +01:00
|
|
|
_ => invalid_nested_attr(attr, &nested_attr).emit(),
|
2022-04-27 02:57:44 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(tokens.drain(..).collect())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn generate_field_attr_code(
|
|
|
|
|
&mut self,
|
|
|
|
|
attr: &syn::Attribute,
|
|
|
|
|
info: FieldInfo<'_>,
|
|
|
|
|
) -> Result<TokenStream, SessionDiagnosticDeriveError> {
|
|
|
|
|
let field_binding = &info.binding.binding;
|
|
|
|
|
let option_ty = option_inner_ty(&info.ty);
|
|
|
|
|
let generated_code = self.generate_non_option_field_code(
|
|
|
|
|
attr,
|
|
|
|
|
FieldInfo {
|
|
|
|
|
vis: info.vis,
|
|
|
|
|
binding: info.binding,
|
|
|
|
|
ty: option_ty.unwrap_or(&info.ty),
|
|
|
|
|
span: info.span,
|
|
|
|
|
},
|
|
|
|
|
)?;
|
|
|
|
|
|
|
|
|
|
if option_ty.is_none() {
|
|
|
|
|
Ok(quote! { #generated_code })
|
|
|
|
|
} else {
|
|
|
|
|
Ok(quote! {
|
|
|
|
|
if let Some(#field_binding) = #field_binding {
|
|
|
|
|
#generated_code
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn generate_non_option_field_code(
|
|
|
|
|
&mut self,
|
|
|
|
|
attr: &Attribute,
|
|
|
|
|
info: FieldInfo<'_>,
|
|
|
|
|
) -> Result<TokenStream, SessionDiagnosticDeriveError> {
|
|
|
|
|
let diag = &self.diag;
|
|
|
|
|
let field_binding = &info.binding.binding;
|
|
|
|
|
|
|
|
|
|
let name = attr.path.segments.last().unwrap().ident.to_string();
|
|
|
|
|
let name = name.as_str();
|
|
|
|
|
|
|
|
|
|
let meta = attr.parse_meta()?;
|
|
|
|
|
match meta {
|
|
|
|
|
Meta::Path(_) => match name {
|
|
|
|
|
"skip_arg" => {
|
|
|
|
|
// Don't need to do anything - by virtue of the attribute existing, the
|
|
|
|
|
// `set_arg` call will not be generated.
|
|
|
|
|
Ok(quote! {})
|
|
|
|
|
}
|
|
|
|
|
"primary_span" => {
|
|
|
|
|
report_error_if_not_applied_to_span(attr, &info)?;
|
|
|
|
|
Ok(quote! {
|
|
|
|
|
#diag.set_span(*#field_binding);
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
"label" | "note" | "help" => {
|
|
|
|
|
report_error_if_not_applied_to_span(attr, &info)?;
|
|
|
|
|
Ok(self.add_subdiagnostic(field_binding, name, name))
|
|
|
|
|
}
|
2022-04-27 04:06:13 +01:00
|
|
|
_ => throw_invalid_attr!(attr, &meta, |diag| {
|
|
|
|
|
diag
|
|
|
|
|
.help("only `skip_arg`, `primary_span`, `label`, `note` and `help` are valid field attributes")
|
|
|
|
|
}),
|
2022-04-27 02:57:44 +01:00
|
|
|
},
|
2022-04-27 04:06:13 +01:00
|
|
|
Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(ref s), .. }) => match name {
|
2022-04-27 02:57:44 +01:00
|
|
|
"label" | "note" | "help" => {
|
|
|
|
|
report_error_if_not_applied_to_span(attr, &info)?;
|
|
|
|
|
Ok(self.add_subdiagnostic(field_binding, name, &s.value()))
|
|
|
|
|
}
|
2022-04-27 04:06:13 +01:00
|
|
|
_ => throw_invalid_attr!(attr, &meta, |diag| {
|
|
|
|
|
diag.help("only `label`, `note` and `help` are valid field attributes")
|
|
|
|
|
}),
|
2022-04-27 02:57:44 +01:00
|
|
|
},
|
2022-04-27 04:06:13 +01:00
|
|
|
Meta::List(MetaList { ref path, ref nested, .. }) => {
|
2022-04-27 02:57:44 +01:00
|
|
|
let name = path.segments.last().unwrap().ident.to_string();
|
|
|
|
|
let name = name.as_ref();
|
|
|
|
|
|
|
|
|
|
match name {
|
|
|
|
|
"suggestion" | "suggestion_short" | "suggestion_hidden"
|
|
|
|
|
| "suggestion_verbose" => (),
|
2022-04-27 04:06:13 +01:00
|
|
|
_ => throw_invalid_attr!(attr, &meta, |diag| {
|
|
|
|
|
diag
|
|
|
|
|
.help("only `suggestion{,_short,_hidden,_verbose}` are valid field attributes")
|
|
|
|
|
}),
|
2022-04-27 02:57:44 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let (span_, applicability) = self.span_and_applicability_of_ty(info)?;
|
|
|
|
|
|
|
|
|
|
let mut msg = None;
|
|
|
|
|
let mut code = None;
|
|
|
|
|
|
2022-04-27 04:06:13 +01:00
|
|
|
for nested_attr in nested {
|
|
|
|
|
let meta = match nested_attr {
|
|
|
|
|
syn::NestedMeta::Meta(ref meta) => meta,
|
|
|
|
|
syn::NestedMeta::Lit(_) => throw_invalid_nested_attr!(attr, &nested_attr),
|
2022-04-27 02:57:44 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let nested_name = meta.path().segments.last().unwrap().ident.to_string();
|
|
|
|
|
let nested_name = nested_name.as_str();
|
|
|
|
|
match meta {
|
|
|
|
|
Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
|
|
|
|
|
match nested_name {
|
|
|
|
|
"message" => {
|
|
|
|
|
msg = Some(s.value());
|
|
|
|
|
}
|
|
|
|
|
"code" => {
|
|
|
|
|
let formatted_str = self.build_format(&s.value(), s.span());
|
|
|
|
|
code = Some(formatted_str);
|
|
|
|
|
}
|
2022-04-27 04:06:13 +01:00
|
|
|
_ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
|
|
|
|
|
diag.help(
|
|
|
|
|
"only `message` and `code` are valid field attributes",
|
2022-04-27 02:57:44 +01:00
|
|
|
)
|
2022-04-27 04:06:13 +01:00
|
|
|
}),
|
2022-04-27 02:57:44 +01:00
|
|
|
}
|
|
|
|
|
}
|
2022-04-27 04:06:13 +01:00
|
|
|
_ => throw_invalid_nested_attr!(attr, &nested_attr),
|
2022-04-27 02:57:44 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let method = format_ident!("span_{}", name);
|
|
|
|
|
|
|
|
|
|
let slug = self
|
|
|
|
|
.slug
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map(|(slug, _)| slug.as_str())
|
|
|
|
|
.unwrap_or_else(|| "missing-slug");
|
|
|
|
|
let msg = msg.as_deref().unwrap_or("suggestion");
|
|
|
|
|
let msg = quote! { rustc_errors::DiagnosticMessage::fluent_attr(#slug, #msg) };
|
|
|
|
|
let code = code.unwrap_or_else(|| quote! { String::new() });
|
|
|
|
|
|
|
|
|
|
Ok(quote! { #diag.#method(#span_, #msg, #code, #applicability); })
|
|
|
|
|
}
|
2022-04-27 04:06:13 +01:00
|
|
|
_ => throw_invalid_attr!(attr, &meta),
|
2022-04-27 02:57:44 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current slug and
|
|
|
|
|
/// `fluent_attr_identifier`.
|
|
|
|
|
fn add_subdiagnostic(
|
|
|
|
|
&self,
|
|
|
|
|
field_binding: &proc_macro2::Ident,
|
|
|
|
|
kind: &str,
|
|
|
|
|
fluent_attr_identifier: &str,
|
|
|
|
|
) -> TokenStream {
|
|
|
|
|
let diag = &self.diag;
|
|
|
|
|
|
|
|
|
|
let slug =
|
|
|
|
|
self.slug.as_ref().map(|(slug, _)| slug.as_str()).unwrap_or_else(|| "missing-slug");
|
|
|
|
|
let fn_name = format_ident!("span_{}", kind);
|
|
|
|
|
quote! {
|
|
|
|
|
#diag.#fn_name(
|
|
|
|
|
*#field_binding,
|
|
|
|
|
rustc_errors::DiagnosticMessage::fluent_attr(#slug, #fluent_attr_identifier)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn span_and_applicability_of_ty(
|
|
|
|
|
&self,
|
|
|
|
|
info: FieldInfo<'_>,
|
|
|
|
|
) -> Result<(TokenStream, TokenStream), SessionDiagnosticDeriveError> {
|
|
|
|
|
match &info.ty {
|
|
|
|
|
// If `ty` is `Span` w/out applicability, then use `Applicability::Unspecified`.
|
|
|
|
|
ty @ Type::Path(..) if type_matches_path(ty, &["rustc_span", "Span"]) => {
|
|
|
|
|
let binding = &info.binding.binding;
|
|
|
|
|
Ok((quote!(*#binding), quote!(rustc_errors::Applicability::Unspecified)))
|
|
|
|
|
}
|
|
|
|
|
// If `ty` is `(Span, Applicability)` then return tokens accessing those.
|
|
|
|
|
Type::Tuple(tup) => {
|
|
|
|
|
let mut span_idx = None;
|
|
|
|
|
let mut applicability_idx = None;
|
|
|
|
|
|
|
|
|
|
for (idx, elem) in tup.elems.iter().enumerate() {
|
|
|
|
|
if type_matches_path(elem, &["rustc_span", "Span"]) {
|
|
|
|
|
if span_idx.is_none() {
|
|
|
|
|
span_idx = Some(syn::Index::from(idx));
|
|
|
|
|
} else {
|
|
|
|
|
throw_span_err!(
|
|
|
|
|
info.span.unwrap(),
|
|
|
|
|
"type of field annotated with `#[suggestion(...)]` contains more than one `Span`"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} else if type_matches_path(elem, &["rustc_errors", "Applicability"]) {
|
|
|
|
|
if applicability_idx.is_none() {
|
|
|
|
|
applicability_idx = Some(syn::Index::from(idx));
|
|
|
|
|
} else {
|
|
|
|
|
throw_span_err!(
|
|
|
|
|
info.span.unwrap(),
|
|
|
|
|
"type of field annotated with `#[suggestion(...)]` contains more than one Applicability"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(span_idx) = span_idx {
|
|
|
|
|
let binding = &info.binding.binding;
|
|
|
|
|
let span = quote!(#binding.#span_idx);
|
|
|
|
|
let applicability = applicability_idx
|
|
|
|
|
.map(|applicability_idx| quote!(#binding.#applicability_idx))
|
|
|
|
|
.unwrap_or_else(|| quote!(rustc_errors::Applicability::Unspecified));
|
|
|
|
|
|
|
|
|
|
return Ok((span, applicability));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
throw_span_err!(info.span.unwrap(), "wrong types for suggestion", |diag| {
|
|
|
|
|
diag.help("`#[suggestion(...)]` on a tuple field must be applied to fields of type `(Span, Applicability)`")
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
// If `ty` isn't a `Span` or `(Span, Applicability)` then emit an error.
|
|
|
|
|
_ => throw_span_err!(info.span.unwrap(), "wrong field type for suggestion", |diag| {
|
|
|
|
|
diag.help("`#[suggestion(...)]` should be applied to fields of type `Span` or `(Span, Applicability)`")
|
|
|
|
|
}),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|