macros: translatable struct attrs and warnings
Signed-off-by: David Wood <david.wood@huawei.com>
This commit is contained in:
parent
f0de7df204
commit
d0fd8d7880
9 changed files with 729 additions and 276 deletions
|
@ -63,9 +63,14 @@ decl_derive!([TypeFoldable, attributes(type_foldable)] => type_foldable::type_fo
|
|||
decl_derive!([Lift, attributes(lift)] => lift::lift_derive);
|
||||
decl_derive!(
|
||||
[SessionDiagnostic, attributes(
|
||||
message,
|
||||
lint,
|
||||
// struct attributes
|
||||
warning,
|
||||
error,
|
||||
// nested parts of struct attributes
|
||||
code,
|
||||
slug,
|
||||
// field attributes
|
||||
message,
|
||||
label,
|
||||
suggestion,
|
||||
suggestion_short,
|
||||
|
|
|
@ -16,11 +16,11 @@ use std::collections::{BTreeSet, HashMap};
|
|||
/// # extern crate rust_middle;
|
||||
/// # use rustc_middle::ty::Ty;
|
||||
/// #[derive(SessionDiagnostic)]
|
||||
/// #[code = "E0505"]
|
||||
/// #[error = "cannot move out of {name} because it is borrowed"]
|
||||
/// #[error(code = "E0505", slug = "move-out-of-borrow-error")]
|
||||
/// pub struct MoveOutOfBorrowError<'tcx> {
|
||||
/// pub name: Ident,
|
||||
/// pub ty: Ty<'tcx>,
|
||||
/// #[message]
|
||||
/// #[label = "cannot move out of borrow"]
|
||||
/// pub span: Span,
|
||||
/// #[label = "`{ty}` first borrowed here"]
|
||||
|
@ -79,13 +79,6 @@ impl std::convert::From<syn::Error> for SessionDiagnosticDeriveError {
|
|||
}
|
||||
}
|
||||
|
||||
/// Equivalent to `rustc:errors::diagnostic::DiagnosticId`, except stores the quoted expression to
|
||||
/// initialise the code with.
|
||||
enum DiagnosticId {
|
||||
Error(proc_macro2::TokenStream),
|
||||
Lint(proc_macro2::TokenStream),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum SessionDiagnosticDeriveError {
|
||||
SynError(syn::Error),
|
||||
|
@ -100,7 +93,7 @@ impl SessionDiagnosticDeriveError {
|
|||
// Return ! to avoid having to create a blank DiagnosticBuilder to return when an
|
||||
// error has already been emitted to the compiler.
|
||||
quote! {
|
||||
unreachable!()
|
||||
{ unreachable!(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -152,17 +145,25 @@ impl<'a> SessionDiagnosticDerive<'a> {
|
|||
}
|
||||
|
||||
Self {
|
||||
builder: SessionDiagnosticDeriveBuilder { diag, sess, fields: fields_map, kind: None },
|
||||
builder: SessionDiagnosticDeriveBuilder {
|
||||
diag,
|
||||
sess,
|
||||
fields: fields_map,
|
||||
kind: None,
|
||||
code: None,
|
||||
slug: None,
|
||||
},
|
||||
structure,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_tokens(self) -> proc_macro2::TokenStream {
|
||||
let SessionDiagnosticDerive { mut structure, mut builder } = self;
|
||||
|
||||
let ast = structure.ast();
|
||||
let attrs = &ast.attrs;
|
||||
|
||||
let implementation = {
|
||||
let (implementation, param_ty) = {
|
||||
if let syn::Data::Struct(..) = ast.data {
|
||||
let preamble = {
|
||||
let preamble = attrs.iter().map(|attr| {
|
||||
|
@ -170,6 +171,7 @@ impl<'a> SessionDiagnosticDerive<'a> {
|
|||
.generate_structure_code(attr)
|
||||
.unwrap_or_else(|v| v.to_compile_error())
|
||||
});
|
||||
|
||||
quote! {
|
||||
#(#preamble)*;
|
||||
}
|
||||
|
@ -213,58 +215,94 @@ impl<'a> SessionDiagnosticDerive<'a> {
|
|||
// it can be referred to by Fluent messages.
|
||||
if field.attrs.is_empty() {
|
||||
let diag = &builder.diag;
|
||||
let ident = &field_binding.binding;
|
||||
let ident = field_binding.ast().ident.as_ref().unwrap();
|
||||
quote! { #diag.set_arg(stringify!(#ident), #field_binding.into_diagnostic_arg()); }
|
||||
} else {
|
||||
quote! {}
|
||||
}
|
||||
});
|
||||
|
||||
// Finally, putting it altogether.
|
||||
match builder.kind {
|
||||
None => {
|
||||
span_err(ast.span().unwrap(), "`code` not specified")
|
||||
.help("use the `#[code = \"...\"]` attribute to set this diagnostic's error code ")
|
||||
.emit();
|
||||
SessionDiagnosticDeriveError::ErrorHandled.to_compile_error()
|
||||
let span = ast.span().unwrap();
|
||||
let (diag, sess) = (&builder.diag, &builder.sess);
|
||||
let init = match (builder.kind, builder.slug, builder.code) {
|
||||
(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, _)) => match kind {
|
||||
DiagnosticId::Lint(_lint) => todo!(),
|
||||
DiagnosticId::Error(code) => {
|
||||
let (diag, sess) = (&builder.diag, &builder.sess);
|
||||
quote! {
|
||||
let mut #diag = #sess.struct_err_with_code("", rustc_errors::DiagnosticId::Error(#code));
|
||||
#preamble
|
||||
match self {
|
||||
#attrs
|
||||
}
|
||||
match self {
|
||||
#args
|
||||
}
|
||||
#diag
|
||||
}
|
||||
(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((kind, _)), _, None) => {
|
||||
span_err(span, "`code` not specified")
|
||||
.help(&format!("use the `#[{}(code = \"...\")]` attribute to set this diagnostic's error code", kind.descr()))
|
||||
.emit();
|
||||
return SessionDiagnosticDeriveError::ErrorHandled.to_compile_error();
|
||||
}
|
||||
(Some((SessionDiagnosticKind::Error, _)), Some((slug, _)), Some((code, _))) => {
|
||||
quote! {
|
||||
let mut #diag = #sess.struct_err_with_code(
|
||||
rustc_errors::DiagnosticMessage::fluent(#slug),
|
||||
rustc_errors::DiagnosticId::Error(#code.to_string())
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
(Some((SessionDiagnosticKind::Warn, _)), Some((slug, _)), Some((code, _))) => {
|
||||
quote! {
|
||||
let mut #diag = #sess.struct_warn_with_code(
|
||||
rustc_errors::DiagnosticMessage::fluent(#slug),
|
||||
rustc_errors::DiagnosticId::Error(#code.to_string())
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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();
|
||||
SessionDiagnosticDeriveError::ErrorHandled.to_compile_error()
|
||||
|
||||
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>
|
||||
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::Session
|
||||
) -> rustc_errors::DiagnosticBuilder<'__session_diagnostic_sess, rustc_errors::ErrorGuaranteed> {
|
||||
) -> rustc_errors::DiagnosticBuilder<'__session_diagnostic_sess, #param_ty> {
|
||||
use rustc_errors::IntoDiagnosticArg;
|
||||
#implementation
|
||||
}
|
||||
|
@ -282,6 +320,25 @@ struct FieldInfo<'a> {
|
|||
span: &'a proc_macro2::Span,
|
||||
}
|
||||
|
||||
/// 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
|
||||
|
@ -289,89 +346,182 @@ struct FieldInfo<'a> {
|
|||
struct SessionDiagnosticDeriveBuilder<'a> {
|
||||
/// 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, &'a syn::Field>,
|
||||
|
||||
/// The identifier to use for the generated `DiagnosticBuilder` instance.
|
||||
diag: syn::Ident,
|
||||
|
||||
/// Whether this is a lint or an error. This dictates how the diag will be initialised. `Span`
|
||||
/// stores at what `Span` the kind was first set at (for error reporting purposes, if the kind
|
||||
/// was multiply specified).
|
||||
kind: Option<(DiagnosticId, proc_macro2::Span)>,
|
||||
/// 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 mandatory part of the struct attribute. Slugs may replace error codes
|
||||
/// in future but it is desirable to mandate error codes until such a time.
|
||||
code: Option<(String, proc_macro::Span)>,
|
||||
}
|
||||
|
||||
impl<'a> SessionDiagnosticDeriveBuilder<'a> {
|
||||
/// Establishes state in the `SessionDiagnosticDeriveBuilder` resulting from the struct
|
||||
/// attributes like `#[error(..)#`, such as the diagnostic kind, slug and code.
|
||||
///
|
||||
/// Returns a `proc_macro2::TokenStream` so that the `Err(..)` variant can be transformed into
|
||||
/// the same type via `to_compile_error`.
|
||||
fn generate_structure_code(
|
||||
&mut self,
|
||||
attr: &syn::Attribute,
|
||||
) -> Result<proc_macro2::TokenStream, SessionDiagnosticDeriveError> {
|
||||
Ok(match attr.parse_meta()? {
|
||||
syn::Meta::NameValue(syn::MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
|
||||
let formatted_str = self.build_format(&s.value(), attr.span());
|
||||
let name = attr.path.segments.last().unwrap().ident.to_string();
|
||||
let name = name.as_str();
|
||||
match name {
|
||||
"message" => {
|
||||
let diag = &self.diag;
|
||||
quote! {
|
||||
#diag.set_primary_message(#formatted_str);
|
||||
let span = attr.span().unwrap();
|
||||
let name = attr.path.segments.last().unwrap().ident.to_string();
|
||||
|
||||
let nested = match attr.parse_meta()? {
|
||||
syn::Meta::List(syn::MetaList { nested, .. }) => nested,
|
||||
syn::Meta::Path(..) => throw_span_err!(
|
||||
span,
|
||||
&format!("`#[{}]` is not a valid `SessionDiagnostic` struct attribute", name)
|
||||
),
|
||||
syn::Meta::NameValue(..) => throw_span_err!(
|
||||
span,
|
||||
&format!("`#[{} = ...]` is not a valid `SessionDiagnostic` struct attribute", name)
|
||||
),
|
||||
};
|
||||
|
||||
let kind = match name.as_str() {
|
||||
"error" => SessionDiagnosticKind::Error,
|
||||
"warning" => SessionDiagnosticKind::Warn,
|
||||
other => throw_span_err!(
|
||||
span,
|
||||
&format!("`#[{}(...)]` is not a valid `SessionDiagnostic` struct attribute", other)
|
||||
),
|
||||
};
|
||||
self.set_kind_once(kind, span)?;
|
||||
|
||||
for attr in nested {
|
||||
let span = attr.span().unwrap();
|
||||
let meta = match attr {
|
||||
syn::NestedMeta::Meta(meta) => meta,
|
||||
syn::NestedMeta::Lit(_) => throw_span_err!(
|
||||
span,
|
||||
&format!(
|
||||
"`#[{}(\"...\")]` is not a valid `SessionDiagnostic` struct attribute",
|
||||
name
|
||||
)
|
||||
),
|
||||
};
|
||||
|
||||
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.
|
||||
syn::Meta::NameValue(syn::MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
|
||||
match nested_name.as_str() {
|
||||
"slug" => {
|
||||
self.set_slug_once(s.value(), s.span().unwrap());
|
||||
}
|
||||
"code" => {
|
||||
self.set_code_once(s.value(), s.span().unwrap());
|
||||
}
|
||||
other => {
|
||||
let diag = span_err(
|
||||
span,
|
||||
&format!(
|
||||
"`#[{}({} = ...)]` is not a valid `SessionDiagnostic` struct attribute",
|
||||
name, other
|
||||
),
|
||||
);
|
||||
diag.emit();
|
||||
}
|
||||
}
|
||||
attr @ "error" | attr @ "lint" => {
|
||||
self.set_kind_once(
|
||||
if attr == "error" {
|
||||
DiagnosticId::Error(formatted_str)
|
||||
} else if attr == "lint" {
|
||||
DiagnosticId::Lint(formatted_str)
|
||||
} else {
|
||||
unreachable!()
|
||||
},
|
||||
s.span(),
|
||||
)?;
|
||||
// This attribute is only allowed to be applied once, and the attribute
|
||||
// will be set in the initialisation code.
|
||||
quote! {}
|
||||
}
|
||||
other => throw_span_err!(
|
||||
attr.span().unwrap(),
|
||||
}
|
||||
syn::Meta::NameValue(..) => {
|
||||
span_err(
|
||||
span,
|
||||
&format!(
|
||||
"`#[{} = ...]` is not a valid `SessionDiagnostic` struct attribute",
|
||||
other
|
||||
)
|
||||
),
|
||||
"`#[{}({} = ...)]` is not a valid `SessionDiagnostic` struct attribute",
|
||||
name, nested_name
|
||||
),
|
||||
)
|
||||
.help("value must be a string")
|
||||
.emit();
|
||||
}
|
||||
syn::Meta::Path(..) => {
|
||||
span_err(
|
||||
span,
|
||||
&format!(
|
||||
"`#[{}({})]` is not a valid `SessionDiagnostic` struct attribute",
|
||||
name, nested_name
|
||||
),
|
||||
)
|
||||
.emit();
|
||||
}
|
||||
syn::Meta::List(..) => {
|
||||
span_err(
|
||||
span,
|
||||
&format!(
|
||||
"`#[{}({}(...))]` is not a valid `SessionDiagnostic` struct attribute",
|
||||
name, nested_name
|
||||
),
|
||||
)
|
||||
.emit();
|
||||
}
|
||||
}
|
||||
_ => todo!("unhandled meta kind"),
|
||||
})
|
||||
}
|
||||
|
||||
Ok(quote! {})
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn set_kind_once(
|
||||
&mut self,
|
||||
kind: DiagnosticId,
|
||||
span: proc_macro2::Span,
|
||||
kind: SessionDiagnosticKind,
|
||||
span: proc_macro::Span,
|
||||
) -> Result<(), SessionDiagnosticDeriveError> {
|
||||
if self.kind.is_none() {
|
||||
self.kind = Some((kind, span));
|
||||
Ok(())
|
||||
} else {
|
||||
let kind_str = |kind: &DiagnosticId| match kind {
|
||||
DiagnosticId::Lint(..) => "lint",
|
||||
DiagnosticId::Error(..) => "error",
|
||||
};
|
||||
match self.kind {
|
||||
None => {
|
||||
self.kind = Some((kind, span));
|
||||
Ok(())
|
||||
}
|
||||
Some((prev_kind, prev_span)) => {
|
||||
let existing = prev_kind.descr();
|
||||
let current = kind.descr();
|
||||
|
||||
let existing_kind = kind_str(&self.kind.as_ref().unwrap().0);
|
||||
let this_kind = kind_str(&kind);
|
||||
let msg = if current == existing {
|
||||
format!("`{}` specified multiple times", existing)
|
||||
} else {
|
||||
format!("`{}` specified when `{}` was already specified", current, existing)
|
||||
};
|
||||
throw_span_err!(span, &msg, |diag| diag
|
||||
.span_note(prev_span, "previously specified here"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let msg = if this_kind == existing_kind {
|
||||
format!("`{}` specified multiple times", existing_kind)
|
||||
} else {
|
||||
format!("`{}` specified when `{}` was already specified", this_kind, existing_kind)
|
||||
};
|
||||
throw_span_err!(span.unwrap(), &msg);
|
||||
fn set_code_once(&mut self, code: String, span: proc_macro::Span) {
|
||||
match self.code {
|
||||
None => {
|
||||
self.code = Some((code, span));
|
||||
}
|
||||
Some((_, prev_span)) => {
|
||||
span_err(span, "`code` specified multiple times")
|
||||
.span_note(prev_span, "previously specified here")
|
||||
.emit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_slug_once(&mut self, slug: String, span: proc_macro::Span) {
|
||||
match self.slug {
|
||||
None => {
|
||||
self.slug = Some((slug, span));
|
||||
}
|
||||
Some((_, prev_span)) => {
|
||||
span_err(span, "`slug` specified multiple times")
|
||||
.span_note(prev_span, "previously specified here")
|
||||
.emit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -413,26 +563,29 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
|
|||
let name = attr.path.segments.last().unwrap().ident.to_string();
|
||||
let name = name.as_str();
|
||||
|
||||
// At this point, we need to dispatch based on the attribute key + the
|
||||
// type.
|
||||
let meta = attr.parse_meta()?;
|
||||
match meta {
|
||||
syn::Meta::Path(_) => match name {
|
||||
"message" => {
|
||||
if type_matches_path(&info.ty, &["rustc_span", "Span"]) {
|
||||
return Ok(quote! {
|
||||
#diag.set_span(*#field_binding);
|
||||
});
|
||||
} else {
|
||||
throw_span_err!(
|
||||
attr.span().unwrap(),
|
||||
"the `#[message]` attribute can only be applied to fields of type `Span`"
|
||||
);
|
||||
}
|
||||
}
|
||||
other => throw_span_err!(
|
||||
attr.span().unwrap(),
|
||||
&format!("`#[{}]` is not a valid `SessionDiagnostic` field attribute", other)
|
||||
),
|
||||
},
|
||||
syn::Meta::NameValue(syn::MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
|
||||
let formatted_str = self.build_format(&s.value(), attr.span());
|
||||
match name {
|
||||
"message" => {
|
||||
if type_matches_path(&info.ty, &["rustc_span", "Span"]) {
|
||||
return Ok(quote! {
|
||||
#diag.set_span(*#field_binding);
|
||||
#diag.set_primary_message(#formatted_str);
|
||||
});
|
||||
} else {
|
||||
throw_span_err!(
|
||||
attr.span().unwrap(),
|
||||
"the `#[message = \"...\"]` attribute can only be applied to fields of type `Span`"
|
||||
);
|
||||
}
|
||||
}
|
||||
"label" => {
|
||||
if type_matches_path(&info.ty, &["rustc_span", "Span"]) {
|
||||
return Ok(quote! {
|
||||
|
@ -441,7 +594,7 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
|
|||
} else {
|
||||
throw_span_err!(
|
||||
attr.span().unwrap(),
|
||||
"The `#[label = ...]` attribute can only be applied to fields of type `Span`"
|
||||
"the `#[label = ...]` attribute can only be applied to fields of type `Span`"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue