Add SessionDiagnostic derive macro.
Co-authored-by: Oliver Scherer <github35764891676564198441@oli-obk.de>
This commit is contained in:
parent
6f1bbf5ee0
commit
93eaf15646
9 changed files with 1106 additions and 10 deletions
|
@ -4086,6 +4086,7 @@ dependencies = [
|
||||||
"rustc_hir_pretty",
|
"rustc_hir_pretty",
|
||||||
"rustc_index",
|
"rustc_index",
|
||||||
"rustc_infer",
|
"rustc_infer",
|
||||||
|
"rustc_macros",
|
||||||
"rustc_middle",
|
"rustc_middle",
|
||||||
"rustc_session",
|
"rustc_session",
|
||||||
"rustc_span",
|
"rustc_span",
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#![feature(proc_macro_diagnostic)]
|
||||||
#![allow(rustc::default_hash_types)]
|
#![allow(rustc::default_hash_types)]
|
||||||
#![recursion_limit = "128"]
|
#![recursion_limit = "128"]
|
||||||
|
|
||||||
|
@ -9,6 +10,7 @@ mod hash_stable;
|
||||||
mod lift;
|
mod lift;
|
||||||
mod query;
|
mod query;
|
||||||
mod serialize;
|
mod serialize;
|
||||||
|
mod session_diagnostic;
|
||||||
mod symbols;
|
mod symbols;
|
||||||
mod type_foldable;
|
mod type_foldable;
|
||||||
|
|
||||||
|
@ -36,3 +38,14 @@ decl_derive!([MetadataDecodable] => serialize::meta_decodable_derive);
|
||||||
decl_derive!([MetadataEncodable] => serialize::meta_encodable_derive);
|
decl_derive!([MetadataEncodable] => serialize::meta_encodable_derive);
|
||||||
decl_derive!([TypeFoldable, attributes(type_foldable)] => type_foldable::type_foldable_derive);
|
decl_derive!([TypeFoldable, attributes(type_foldable)] => type_foldable::type_foldable_derive);
|
||||||
decl_derive!([Lift, attributes(lift)] => lift::lift_derive);
|
decl_derive!([Lift, attributes(lift)] => lift::lift_derive);
|
||||||
|
decl_derive!(
|
||||||
|
[SessionDiagnostic, attributes(
|
||||||
|
message,
|
||||||
|
lint,
|
||||||
|
error,
|
||||||
|
label,
|
||||||
|
suggestion,
|
||||||
|
suggestion_short,
|
||||||
|
suggestion_hidden,
|
||||||
|
suggestion_verbose)] => session_diagnostic::session_diagnostic_derive
|
||||||
|
);
|
||||||
|
|
665
compiler/rustc_macros/src/session_diagnostic.rs
Normal file
665
compiler/rustc_macros/src/session_diagnostic.rs
Normal file
|
@ -0,0 +1,665 @@
|
||||||
|
#![deny(unused_must_use)]
|
||||||
|
use quote::format_ident;
|
||||||
|
use quote::quote;
|
||||||
|
|
||||||
|
use proc_macro::Diagnostic;
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
|
/// Implements #[derive(SessionDiagnostic)], which allows for errors to be specified as a struct, independent
|
||||||
|
/// from the actual diagnostics emitting code.
|
||||||
|
/// ```ignore (pseudo-rust)
|
||||||
|
/// # extern crate rustc_errors;
|
||||||
|
/// # use rustc_errors::Applicability;
|
||||||
|
/// # extern crate rustc_span;
|
||||||
|
/// # use rustc_span::{symbol::Ident, Span};
|
||||||
|
/// # extern crate rust_middle;
|
||||||
|
/// # use rustc_middle::ty::Ty;
|
||||||
|
/// #[derive(SessionDiagnostic)]
|
||||||
|
/// #[code = "E0505"]
|
||||||
|
/// #[error = "cannot move out of {name} because it is borrowed"]
|
||||||
|
/// pub struct MoveOutOfBorrowError<'tcx> {
|
||||||
|
/// pub name: Ident,
|
||||||
|
/// pub ty: Ty<'tcx>,
|
||||||
|
/// #[label = "cannot move out of borrow"]
|
||||||
|
/// pub span: Span,
|
||||||
|
/// #[label = "`{ty}` first borrowed here"]
|
||||||
|
/// pub other_span: Span,
|
||||||
|
/// #[suggestion(message = "consider cloning here", code = "{name}.clone()")]
|
||||||
|
/// pub opt_sugg: Option<(Span, Applicability)>
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
/// Then, later, to emit the error:
|
||||||
|
///
|
||||||
|
/// ```ignore (pseudo-rust)
|
||||||
|
/// sess.emit_err(MoveOutOfBorrowError {
|
||||||
|
/// expected,
|
||||||
|
/// actual,
|
||||||
|
/// span,
|
||||||
|
/// other_span,
|
||||||
|
/// opt_sugg: Some(suggestion, Applicability::MachineApplicable),
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
pub fn session_diagnostic_derive(s: synstructure::Structure<'_>) -> proc_macro2::TokenStream {
|
||||||
|
// Names for the diagnostic we build and the session we build it from.
|
||||||
|
let diag = format_ident!("diag");
|
||||||
|
let sess = format_ident!("sess");
|
||||||
|
|
||||||
|
SessionDiagnosticDerive::new(diag, sess, s).into_tokens()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks whether the type name of `ty` matches `name`.
|
||||||
|
//
|
||||||
|
// Given some struct at a::b::c::Foo, this will return true for c::Foo, b::c::Foo, or
|
||||||
|
// a::b::c::Foo. This reasonably allows qualified names to be used in the macro.
|
||||||
|
fn type_matches_path(ty: &syn::Type, name: &[&str]) -> bool {
|
||||||
|
if let syn::Type::Path(ty) = ty {
|
||||||
|
ty.path
|
||||||
|
.segments
|
||||||
|
.iter()
|
||||||
|
.map(|s| s.ident.to_string())
|
||||||
|
.rev()
|
||||||
|
.zip(name.iter().rev())
|
||||||
|
.all(|(x, y)| &x.as_str() == y)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The central struct for constructing the as_error method from an annotated struct.
|
||||||
|
struct SessionDiagnosticDerive<'a> {
|
||||||
|
structure: synstructure::Structure<'a>,
|
||||||
|
builder: SessionDiagnosticDeriveBuilder<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<syn::Error> for SessionDiagnosticDeriveError {
|
||||||
|
fn from(e: syn::Error) -> Self {
|
||||||
|
SessionDiagnosticDeriveError::SynError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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),
|
||||||
|
ErrorHandled,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SessionDiagnosticDeriveError {
|
||||||
|
fn to_compile_error(self) -> proc_macro2::TokenStream {
|
||||||
|
match self {
|
||||||
|
SessionDiagnosticDeriveError::SynError(e) => e.to_compile_error(),
|
||||||
|
SessionDiagnosticDeriveError::ErrorHandled => {
|
||||||
|
// Return ! to avoid having to create a blank DiagnosticBuilder to return when an
|
||||||
|
// error has already been emitted to the compiler.
|
||||||
|
quote! {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn span_err(span: impl proc_macro::MultiSpan, msg: &str) -> proc_macro::Diagnostic {
|
||||||
|
Diagnostic::spanned(span, proc_macro::Level::Error, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For methods that return a Result<_, SessionDiagnosticDeriveError>: emit a diagnostic on
|
||||||
|
/// span $span with msg $msg (and, optionally, perform additional decoration using the FnOnce
|
||||||
|
/// passed in `diag`). Then, return Err(ErrorHandled).
|
||||||
|
macro_rules! throw_span_err {
|
||||||
|
($span:expr, $msg:expr) => {{ throw_span_err!($span, $msg, |diag| diag) }};
|
||||||
|
($span:expr, $msg:expr, $f:expr) => {{
|
||||||
|
return Err(_throw_span_err($span, $msg, $f));
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When possible, prefer using throw_span_err! over using this function directly. This only exists
|
||||||
|
/// as a function to constrain `f` to an impl FnOnce.
|
||||||
|
fn _throw_span_err(
|
||||||
|
span: impl proc_macro::MultiSpan,
|
||||||
|
msg: &str,
|
||||||
|
f: impl FnOnce(proc_macro::Diagnostic) -> proc_macro::Diagnostic,
|
||||||
|
) -> SessionDiagnosticDeriveError {
|
||||||
|
let diag = span_err(span, msg);
|
||||||
|
f(diag).emit();
|
||||||
|
SessionDiagnosticDeriveError::ErrorHandled
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> SessionDiagnosticDerive<'a> {
|
||||||
|
fn new(diag: syn::Ident, sess: syn::Ident, structure: synstructure::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(), field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
builder: SessionDiagnosticDeriveBuilder { diag, sess, fields: fields_map, kind: None },
|
||||||
|
structure,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn into_tokens(self) -> proc_macro2::TokenStream {
|
||||||
|
let SessionDiagnosticDerive { structure, mut builder } = self;
|
||||||
|
|
||||||
|
let ast = structure.ast();
|
||||||
|
let attrs = &ast.attrs;
|
||||||
|
|
||||||
|
let implementation = {
|
||||||
|
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)*;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let body = structure.each(|field_binding| {
|
||||||
|
let field = field_binding.ast();
|
||||||
|
let result = field.attrs.iter().map(|attr| {
|
||||||
|
builder
|
||||||
|
.generate_field_code(
|
||||||
|
attr,
|
||||||
|
FieldInfo {
|
||||||
|
vis: &field.vis,
|
||||||
|
binding: field_binding,
|
||||||
|
ty: &field.ty,
|
||||||
|
span: &field.span(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap_or_else(|v| v.to_compile_error())
|
||||||
|
});
|
||||||
|
return quote! {
|
||||||
|
#(#result);*
|
||||||
|
};
|
||||||
|
});
|
||||||
|
// 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()
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
#body
|
||||||
|
}
|
||||||
|
#diag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
span_err(
|
||||||
|
ast.span().unwrap(),
|
||||||
|
"`#[derive(SessionDiagnostic)]` can only be used on structs",
|
||||||
|
)
|
||||||
|
.emit();
|
||||||
|
SessionDiagnosticDeriveError::ErrorHandled.to_compile_error()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let sess = &builder.sess;
|
||||||
|
structure.gen_impl(quote! {
|
||||||
|
gen impl<'__session_diagnostic_sess> rustc_session::SessionDiagnostic<'__session_diagnostic_sess>
|
||||||
|
for @Self
|
||||||
|
{
|
||||||
|
fn into_diagnostic(
|
||||||
|
self,
|
||||||
|
#sess: &'__session_diagnostic_sess rustc_session::Session
|
||||||
|
) -> rustc_errors::DiagnosticBuilder<'__session_diagnostic_sess> {
|
||||||
|
#implementation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Field information passed to the builder. Deliberately omits attrs to discourage the generate_*
|
||||||
|
/// methods from walking the attributes themselves.
|
||||||
|
struct FieldInfo<'a> {
|
||||||
|
vis: &'a syn::Visibility,
|
||||||
|
binding: &'a synstructure::BindingInfo<'a>,
|
||||||
|
ty: &'a syn::Type,
|
||||||
|
span: &'a proc_macro2::Span,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tracks persistent information required for building up the individual calls to diagnostic
|
||||||
|
/// methods for the final generated method. This is a separate struct to SessionDerive 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<'a> {
|
||||||
|
/// Name of the session parameter that's passed in to the as_error method.
|
||||||
|
sess: 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)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> SessionDiagnosticDeriveBuilder<'a> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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(),
|
||||||
|
&format!(
|
||||||
|
"`#[{} = ...]` is not a valid SessionDiagnostic struct attribute",
|
||||||
|
other
|
||||||
|
)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => todo!("unhandled meta kind"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
fn set_kind_once(
|
||||||
|
&mut self,
|
||||||
|
kind: DiagnosticId,
|
||||||
|
span: proc_macro2::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",
|
||||||
|
};
|
||||||
|
|
||||||
|
let existing_kind = kind_str(&self.kind.as_ref().unwrap().0);
|
||||||
|
let this_kind = kind_str(&kind);
|
||||||
|
|
||||||
|
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 generate_field_code(
|
||||||
|
&mut self,
|
||||||
|
attr: &syn::Attribute,
|
||||||
|
info: FieldInfo<'_>,
|
||||||
|
) -> Result<proc_macro2::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,
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
Ok(if option_ty.is_none() {
|
||||||
|
quote! { #generated_code }
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
if let Some(#field_binding) = #field_binding {
|
||||||
|
#generated_code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_non_option_field_code(
|
||||||
|
&mut self,
|
||||||
|
attr: &syn::Attribute,
|
||||||
|
info: FieldInfo<'_>,
|
||||||
|
) -> Result<proc_macro2::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();
|
||||||
|
// At this point, we need to dispatch based on the attribute key + the
|
||||||
|
// type.
|
||||||
|
let meta = attr.parse_meta()?;
|
||||||
|
Ok(match meta {
|
||||||
|
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"]) {
|
||||||
|
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"]) {
|
||||||
|
quote! {
|
||||||
|
#diag.span_label(*#field_binding, #formatted_str);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw_span_err!(
|
||||||
|
attr.span().unwrap(),
|
||||||
|
"The `#[label = ...]` 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::List(list) => {
|
||||||
|
match list.path.segments.iter().last().unwrap().ident.to_string().as_str() {
|
||||||
|
suggestion_kind @ "suggestion"
|
||||||
|
| suggestion_kind @ "suggestion_short"
|
||||||
|
| suggestion_kind @ "suggestion_hidden"
|
||||||
|
| suggestion_kind @ "suggestion_verbose" => {
|
||||||
|
// For suggest, we need to ensure we are running on a (Span,
|
||||||
|
// Applicability) pair.
|
||||||
|
let (span, applicability) = (|| match &info.ty {
|
||||||
|
ty @ syn::Type::Path(..)
|
||||||
|
if type_matches_path(ty, &["rustc_span", "Span"]) =>
|
||||||
|
{
|
||||||
|
let binding = &info.binding.binding;
|
||||||
|
Ok((
|
||||||
|
quote!(*#binding),
|
||||||
|
quote!(rustc_errors::Applicability::Unspecified),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
syn::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.clone().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.clone().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(quote!(
|
||||||
|
rustc_errors::Applicability::Unspecified
|
||||||
|
));
|
||||||
|
return Ok((span, applicability));
|
||||||
|
}
|
||||||
|
throw_span_err!(
|
||||||
|
info.span.clone().unwrap(),
|
||||||
|
"wrong types for suggestion",
|
||||||
|
|diag| {
|
||||||
|
diag.help("#[suggestion(...)] on a tuple field must be applied to fields of type (Span, Applicability)")
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => throw_span_err!(
|
||||||
|
info.span.clone().unwrap(),
|
||||||
|
"wrong field type for suggestion",
|
||||||
|
|diag| {
|
||||||
|
diag.help("#[suggestion(...)] should be applied to fields of type Span or (Span, Applicability)")
|
||||||
|
}
|
||||||
|
),
|
||||||
|
})()?;
|
||||||
|
// Now read the key-value pairs.
|
||||||
|
let mut msg = None;
|
||||||
|
let mut code = None;
|
||||||
|
|
||||||
|
for arg in list.nested.iter() {
|
||||||
|
if let syn::NestedMeta::Meta(syn::Meta::NameValue(arg_name_value)) = arg
|
||||||
|
{
|
||||||
|
if let syn::MetaNameValue { lit: syn::Lit::Str(s), .. } =
|
||||||
|
arg_name_value
|
||||||
|
{
|
||||||
|
let name = arg_name_value
|
||||||
|
.path
|
||||||
|
.segments
|
||||||
|
.last()
|
||||||
|
.unwrap()
|
||||||
|
.ident
|
||||||
|
.to_string();
|
||||||
|
let name = name.as_str();
|
||||||
|
let formatted_str = self.build_format(&s.value(), arg.span());
|
||||||
|
match name {
|
||||||
|
"message" => {
|
||||||
|
msg = Some(formatted_str);
|
||||||
|
}
|
||||||
|
"code" => {
|
||||||
|
code = Some(formatted_str);
|
||||||
|
}
|
||||||
|
other => throw_span_err!(
|
||||||
|
arg.span().unwrap(),
|
||||||
|
&format!(
|
||||||
|
"`{}` is not a valid key for `#[suggestion(...)]`",
|
||||||
|
other
|
||||||
|
)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let msg = if let Some(msg) = msg {
|
||||||
|
quote!(#msg.as_str())
|
||||||
|
} else {
|
||||||
|
throw_span_err!(
|
||||||
|
list.span().unwrap(),
|
||||||
|
"missing suggestion message",
|
||||||
|
|diag| {
|
||||||
|
diag.help("provide a suggestion message using #[suggestion(message = \"...\")]")
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
let code = code.unwrap_or_else(|| quote! { String::new() });
|
||||||
|
// Now build it out:
|
||||||
|
let suggestion_method = format_ident!("span_{}", suggestion_kind);
|
||||||
|
quote! {
|
||||||
|
#diag.#suggestion_method(#span, #msg, #code, #applicability);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => throw_span_err!(
|
||||||
|
list.span().unwrap(),
|
||||||
|
&format!("invalid annotation list `#[{}(...)]`", other)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => panic!("unhandled meta kind"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// In the strings in the attributes supplied to this macro, we want callers to be able to
|
||||||
|
/// reference fields in the format string. Take this, for example:
|
||||||
|
/// ```ignore (not-usage-example)
|
||||||
|
/// struct Point {
|
||||||
|
/// #[error = "Expected a point greater than ({x}, {y})"]
|
||||||
|
/// x: i32,
|
||||||
|
/// y: i32,
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
/// We want to automatically pick up that {x} refers `self.x` and {y} refers to `self.y`, then
|
||||||
|
/// generate this call to format!:
|
||||||
|
/// ```ignore (not-usage-example)
|
||||||
|
/// format!("Expected a point greater than ({x}, {y})", x = self.x, y = self.y)
|
||||||
|
/// ```
|
||||||
|
/// This function builds the entire call to format!.
|
||||||
|
fn build_format(&self, input: &String, span: proc_macro2::Span) -> proc_macro2::TokenStream {
|
||||||
|
let mut referenced_fields: HashSet<String> = HashSet::new();
|
||||||
|
|
||||||
|
// At this point, we can start parsing the format string.
|
||||||
|
let mut it = input.chars().peekable();
|
||||||
|
// Once the start of a format string has been found, process the format string and spit out
|
||||||
|
// the referenced fields. Leaves `it` sitting on the closing brace of the format string, so the
|
||||||
|
// next call to `it.next()` retrieves the next character.
|
||||||
|
while let Some(c) = it.next() {
|
||||||
|
if c == '{' && *it.peek().unwrap_or(&'\0') != '{' {
|
||||||
|
#[must_use]
|
||||||
|
let mut eat_argument = || -> Option<String> {
|
||||||
|
let mut result = String::new();
|
||||||
|
// Format specifiers look like
|
||||||
|
// format := '{' [ argument ] [ ':' format_spec ] '}' .
|
||||||
|
// Therefore, we only need to eat until ':' or '}' to find the argument.
|
||||||
|
while let Some(c) = it.next() {
|
||||||
|
result.push(c);
|
||||||
|
let next = *it.peek().unwrap_or(&'\0');
|
||||||
|
if next == '}' {
|
||||||
|
break;
|
||||||
|
} else if next == ':' {
|
||||||
|
// Eat the ':' character.
|
||||||
|
assert_eq!(it.next().unwrap(), ':');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Eat until (and including) the matching '}'
|
||||||
|
while it.next()? != '}' {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Some(result)
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(referenced_field) = eat_argument() {
|
||||||
|
referenced_fields.insert(referenced_field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// At this point, `referenced_fields` contains a set of the unique fields that were
|
||||||
|
// referenced in the format string. Generate the corresponding "x = self.x" format
|
||||||
|
// string parameters:
|
||||||
|
let args = referenced_fields.into_iter().map(|field: String| {
|
||||||
|
let field_ident = format_ident!("{}", field);
|
||||||
|
let value = if self.fields.contains_key(&field) {
|
||||||
|
quote! {
|
||||||
|
&self.#field_ident
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This field doesn't exist. Emit a diagnostic.
|
||||||
|
Diagnostic::spanned(
|
||||||
|
span.unwrap(),
|
||||||
|
proc_macro::Level::Error,
|
||||||
|
format!("`{}` doesn't refer to a field on this type", field),
|
||||||
|
)
|
||||||
|
.emit();
|
||||||
|
quote! {
|
||||||
|
"{#field}"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
quote! {
|
||||||
|
#field_ident = #value
|
||||||
|
}
|
||||||
|
});
|
||||||
|
quote! {
|
||||||
|
format!(#input #(,#args)*)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If `ty` is an Option, returns Some(inner type). Else, returns None.
|
||||||
|
fn option_inner_ty(ty: &syn::Type) -> Option<&syn::Type> {
|
||||||
|
if type_matches_path(ty, &["std", "option", "Option"]) {
|
||||||
|
if let syn::Type::Path(ty_path) = ty {
|
||||||
|
let path = &ty_path.path;
|
||||||
|
let ty = path.segments.iter().last().unwrap();
|
||||||
|
if let syn::PathArguments::AngleBracketed(bracketed) = &ty.arguments {
|
||||||
|
if bracketed.args.len() == 1 {
|
||||||
|
if let syn::GenericArgument::Type(ty) = &bracketed.args[0] {
|
||||||
|
return Some(ty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
|
@ -237,6 +237,14 @@ enum DiagnosticBuilderMethod {
|
||||||
// Add more variants as needed to support one-time diagnostics.
|
// Add more variants as needed to support one-time diagnostics.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Trait implemented by error types. This should not be implemented manually. Instead, use
|
||||||
|
/// `#[derive(SessionDiagnostic)]` -- see [rustc_macros::SessionDiagnostic].
|
||||||
|
pub trait SessionDiagnostic<'a> {
|
||||||
|
/// Write out as a diagnostic out of `sess`.
|
||||||
|
#[must_use]
|
||||||
|
fn into_diagnostic(self, sess: &'a Session) -> DiagnosticBuilder<'a>;
|
||||||
|
}
|
||||||
|
|
||||||
/// Diagnostic message ID, used by `Session.one_time_diagnostics` to avoid
|
/// Diagnostic message ID, used by `Session.one_time_diagnostics` to avoid
|
||||||
/// emitting the same message more than once.
|
/// emitting the same message more than once.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
@ -392,6 +400,9 @@ impl Session {
|
||||||
pub fn err(&self, msg: &str) {
|
pub fn err(&self, msg: &str) {
|
||||||
self.diagnostic().err(msg)
|
self.diagnostic().err(msg)
|
||||||
}
|
}
|
||||||
|
pub fn emit_err<'a>(&'a self, err: impl SessionDiagnostic<'a>) {
|
||||||
|
err.into_diagnostic(self).emit()
|
||||||
|
}
|
||||||
pub fn err_count(&self) -> usize {
|
pub fn err_count(&self) -> usize {
|
||||||
self.diagnostic().err_count()
|
self.diagnostic().err_count()
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ use crate::astconv::{AstConv, SizedByDefault};
|
||||||
use crate::bounds::Bounds;
|
use crate::bounds::Bounds;
|
||||||
use crate::check::intrinsic::intrinsic_operation_unsafety;
|
use crate::check::intrinsic::intrinsic_operation_unsafety;
|
||||||
use crate::constrained_generic_params as cgp;
|
use crate::constrained_generic_params as cgp;
|
||||||
|
use crate::errors;
|
||||||
use crate::middle::resolve_lifetime as rl;
|
use crate::middle::resolve_lifetime as rl;
|
||||||
use rustc_ast as ast;
|
use rustc_ast as ast;
|
||||||
use rustc_ast::MetaItemKind;
|
use rustc_ast::MetaItemKind;
|
||||||
|
@ -834,16 +835,11 @@ fn convert_variant(
|
||||||
let fid = tcx.hir().local_def_id(f.hir_id);
|
let fid = tcx.hir().local_def_id(f.hir_id);
|
||||||
let dup_span = seen_fields.get(&f.ident.normalize_to_macros_2_0()).cloned();
|
let dup_span = seen_fields.get(&f.ident.normalize_to_macros_2_0()).cloned();
|
||||||
if let Some(prev_span) = dup_span {
|
if let Some(prev_span) = dup_span {
|
||||||
struct_span_err!(
|
tcx.sess.emit_err(errors::FieldAlreadyDeclared {
|
||||||
tcx.sess,
|
field_name: f.ident,
|
||||||
f.span,
|
span: f.span,
|
||||||
E0124,
|
prev_span,
|
||||||
"field `{}` is already declared",
|
});
|
||||||
f.ident
|
|
||||||
)
|
|
||||||
.span_label(f.span, "field already declared")
|
|
||||||
.span_label(prev_span, format!("`{}` first declared here", f.ident))
|
|
||||||
.emit();
|
|
||||||
} else {
|
} else {
|
||||||
seen_fields.insert(f.ident.normalize_to_macros_2_0(), f.span);
|
seen_fields.insert(f.ident.normalize_to_macros_2_0(), f.span);
|
||||||
}
|
}
|
||||||
|
|
14
compiler/rustc_typeck/src/errors.rs
Normal file
14
compiler/rustc_typeck/src/errors.rs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
//! Errors emitted by typeck.
|
||||||
|
use rustc_macros::SessionDiagnostic;
|
||||||
|
use rustc_span::{symbol::Ident, Span };
|
||||||
|
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
#[error = "E0124"]
|
||||||
|
pub struct FieldAlreadyDeclared {
|
||||||
|
pub field_name: Ident,
|
||||||
|
#[message = "field `{field_name}` is already declared"]
|
||||||
|
#[label = "field already declared"]
|
||||||
|
pub span: Span,
|
||||||
|
#[label = "`{field_name}` first declared here"]
|
||||||
|
pub prev_span: Span,
|
||||||
|
}
|
|
@ -84,6 +84,7 @@ mod check_unused;
|
||||||
mod coherence;
|
mod coherence;
|
||||||
mod collect;
|
mod collect;
|
||||||
mod constrained_generic_params;
|
mod constrained_generic_params;
|
||||||
|
mod errors;
|
||||||
mod impl_wf_check;
|
mod impl_wf_check;
|
||||||
mod mem_categorization;
|
mod mem_categorization;
|
||||||
mod outlives;
|
mod outlives;
|
||||||
|
|
260
src/test/ui-fulldeps/session-derive-errors.rs
Normal file
260
src/test/ui-fulldeps/session-derive-errors.rs
Normal file
|
@ -0,0 +1,260 @@
|
||||||
|
// check-fail
|
||||||
|
// Tests error conditions for specifying diagnostics using #[derive(SessionDiagnostic)]
|
||||||
|
|
||||||
|
#![feature(rustc_private)]
|
||||||
|
#![crate_type = "lib"]
|
||||||
|
|
||||||
|
extern crate rustc_span;
|
||||||
|
use rustc_span::Span;
|
||||||
|
use rustc_span::symbol::Ident;
|
||||||
|
|
||||||
|
extern crate rustc_macros;
|
||||||
|
use rustc_macros::SessionDiagnostic;
|
||||||
|
|
||||||
|
extern crate rustc_middle;
|
||||||
|
use rustc_middle::ty::Ty;
|
||||||
|
|
||||||
|
extern crate rustc_errors;
|
||||||
|
use rustc_errors::Applicability;
|
||||||
|
|
||||||
|
extern crate rustc_session;
|
||||||
|
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
#[message = "Hello, world!"]
|
||||||
|
#[error = "E0123"]
|
||||||
|
struct Hello {}
|
||||||
|
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
#[error = "E0123"]
|
||||||
|
//~^ ERROR `#[derive(SessionDiagnostic)]` can only be used on structs
|
||||||
|
enum SessionDiagnosticOnEnum {
|
||||||
|
Foo,
|
||||||
|
Bar,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
#[error = "E0123"]
|
||||||
|
#[label = "This is in the wrong place"]
|
||||||
|
//~^ ERROR `#[label = ...]` is not a valid SessionDiagnostic struct attribute
|
||||||
|
struct WrongPlace {}
|
||||||
|
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
#[error = "E0123"]
|
||||||
|
struct WrongPlaceField {
|
||||||
|
#[suggestion = "this is the wrong kind of attribute"]
|
||||||
|
//~^ ERROR `#[suggestion = ...]` is not a valid SessionDiagnostic field attribute
|
||||||
|
sp: Span,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
#[message = "Hello, world!"]
|
||||||
|
#[error = "E0123"]
|
||||||
|
#[error = "E0456"] //~ ERROR `error` specified multiple times
|
||||||
|
struct ErrorSpecifiedTwice {}
|
||||||
|
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
#[message = "Hello, world!"]
|
||||||
|
#[error = "E0123"]
|
||||||
|
#[lint = "some_useful_lint"] //~ ERROR `lint` specified when `error` was already specified
|
||||||
|
struct LintSpecifiedAfterError {}
|
||||||
|
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
#[message = "Some lint message"]
|
||||||
|
#[error = "E0123"]
|
||||||
|
struct LintButHasErrorCode {}
|
||||||
|
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
struct ErrorCodeNotProvided {} //~ ERROR `code` not specified
|
||||||
|
|
||||||
|
// FIXME: Uncomment when emitting lints is supported.
|
||||||
|
/*
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
#[message = "Hello, world!"]
|
||||||
|
#[lint = "clashing_extern_declarations"]
|
||||||
|
#[lint = "improper_ctypes"] // FIXME: ERROR `lint` specified multiple times
|
||||||
|
struct LintSpecifiedTwice {}
|
||||||
|
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
#[lint = "Some lint message"]
|
||||||
|
#[message = "Some error message"]
|
||||||
|
#[error = "E0123"] // ERROR `error` specified when `lint` was already specified
|
||||||
|
struct ErrorSpecifiedAfterLint {}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
#[error = "E0123"]
|
||||||
|
struct ErrorWithField {
|
||||||
|
name: String,
|
||||||
|
#[message = "This error has a field, and references {name}"]
|
||||||
|
span: Span
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
#[error = "E0123"]
|
||||||
|
struct ErrorWithMessageAppliedToField {
|
||||||
|
#[message = "this message is applied to a String field"]
|
||||||
|
//~^ ERROR the `#[message = "..."]` attribute can only be applied to fields of type Span
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
#[error = "E0123"]
|
||||||
|
#[message = "This error has a field, and references {name}"]
|
||||||
|
//~^ ERROR `name` doesn't refer to a field on this type
|
||||||
|
struct ErrorWithNonexistentField {
|
||||||
|
span: Span
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
#[error = "E0123"]
|
||||||
|
#[message = "This is missing a closing brace: {name"]
|
||||||
|
//~^ ERROR invalid format string: expected `'}'`
|
||||||
|
struct ErrorMissingClosingBrace {
|
||||||
|
name: String,
|
||||||
|
span: Span
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
#[error = "E0123"]
|
||||||
|
#[message = "This is missing an opening brace: name}"]
|
||||||
|
//~^ ERROR invalid format string: unmatched `}`
|
||||||
|
struct ErrorMissingOpeningBrace {
|
||||||
|
name: String,
|
||||||
|
span: Span
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
#[error = "E0123"]
|
||||||
|
#[message = "Something something"]
|
||||||
|
struct LabelOnSpan {
|
||||||
|
#[label = "See here"]
|
||||||
|
sp: Span
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
#[error = "E0123"]
|
||||||
|
#[message = "Something something"]
|
||||||
|
struct LabelOnNonSpan {
|
||||||
|
#[label = "See here"]
|
||||||
|
//~^ ERROR The `#[label = ...]` attribute can only be applied to fields of type Span
|
||||||
|
id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
#[error = "E0123"]
|
||||||
|
struct Suggest {
|
||||||
|
#[suggestion(message = "This is a suggestion", code = "This is the suggested code")]
|
||||||
|
#[suggestion_short(message = "This is a suggestion", code = "This is the suggested code")]
|
||||||
|
#[suggestion_hidden(message = "This is a suggestion", code = "This is the suggested code")]
|
||||||
|
#[suggestion_verbose(message = "This is a suggestion", code = "This is the suggested code")]
|
||||||
|
suggestion: (Span, Applicability),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
#[error = "E0123"]
|
||||||
|
struct SuggestWithoutCode {
|
||||||
|
#[suggestion(message = "This is a suggestion")]
|
||||||
|
suggestion: (Span, Applicability),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
#[error = "E0123"]
|
||||||
|
struct SuggestWithBadKey {
|
||||||
|
#[suggestion(nonsense = "This is nonsense")]
|
||||||
|
//~^ ERROR `nonsense` is not a valid key for `#[suggestion(...)]`
|
||||||
|
suggestion: (Span, Applicability),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
#[error = "E0123"]
|
||||||
|
struct SuggestWithShorthandMsg {
|
||||||
|
#[suggestion(msg = "This is a suggestion")]
|
||||||
|
//~^ ERROR `msg` is not a valid key for `#[suggestion(...)]`
|
||||||
|
suggestion: (Span, Applicability),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
#[error = "E0123"]
|
||||||
|
struct SuggestWithoutMsg {
|
||||||
|
#[suggestion(code = "This is suggested code")]
|
||||||
|
//~^ ERROR missing suggestion message
|
||||||
|
suggestion: (Span, Applicability),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
#[error = "E0123"]
|
||||||
|
struct SuggestWithTypesSwapped {
|
||||||
|
#[suggestion(message = "This is a message", code = "This is suggested code")]
|
||||||
|
suggestion: (Applicability, Span),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
#[error = "E0123"]
|
||||||
|
struct SuggestWithWrongTypeApplicabilityOnly {
|
||||||
|
#[suggestion(message = "This is a message", code = "This is suggested code")]
|
||||||
|
//~^ ERROR wrong field type for suggestion
|
||||||
|
suggestion: Applicability,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
#[error = "E0123"]
|
||||||
|
struct SuggestWithSpanOnly{
|
||||||
|
#[suggestion(message = "This is a message", code = "This is suggested code")]
|
||||||
|
suggestion: Span,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
#[error = "E0123"]
|
||||||
|
struct SuggestWithDuplicateSpanAndApplicability {
|
||||||
|
#[suggestion(message = "This is a message", code = "This is suggested code")]
|
||||||
|
//~^ ERROR type of field annotated with `#[suggestion(...)]` contains more than one Span
|
||||||
|
suggestion: (Span, Span, Applicability),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
#[error = "E0123"]
|
||||||
|
struct SuggestWithDuplicateApplicabilityAndSpan {
|
||||||
|
#[suggestion(message = "This is a message", code = "This is suggested code")]
|
||||||
|
//~^ ERROR type of field annotated with `#[suggestion(...)]` contains more than one
|
||||||
|
suggestion: (Applicability, Applicability, Span),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
#[error = "E0123"]
|
||||||
|
struct WrongKindOfAnnotation {
|
||||||
|
#[label("wrong kind of annotation for label")]
|
||||||
|
//~^ ERROR invalid annotation list `#[label(...)]`
|
||||||
|
z: Span,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
#[error = "E0123"]
|
||||||
|
#[message = "Something something else"]
|
||||||
|
struct OptionsInErrors {
|
||||||
|
#[label = "Label message"]
|
||||||
|
label: Option<Span>,
|
||||||
|
#[suggestion(message = "suggestion message")]
|
||||||
|
opt_sugg: Option<(Span, Applicability)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
#[error = "E0456"]
|
||||||
|
struct MoveOutOfBorrowError<'tcx> {
|
||||||
|
name: Ident,
|
||||||
|
ty: Ty<'tcx>,
|
||||||
|
#[message = "cannot move {ty} out of borrow"]
|
||||||
|
#[label = "cannot move out of borrow"]
|
||||||
|
span: Span,
|
||||||
|
#[label = "`{ty}` first borrowed here"]
|
||||||
|
other_span: Span,
|
||||||
|
#[suggestion(message = "consider cloning here", code = "{name}.clone()")]
|
||||||
|
opt_sugg: Option<(Span, Applicability)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SessionDiagnostic)]
|
||||||
|
#[error = "E0123"]
|
||||||
|
struct ErrorWithLifetime<'a> {
|
||||||
|
#[message = "Some message that references {name}"]
|
||||||
|
span: Span,
|
||||||
|
name: &'a str,
|
||||||
|
}
|
135
src/test/ui-fulldeps/session-derive-errors.stderr
Normal file
135
src/test/ui-fulldeps/session-derive-errors.stderr
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
error: `#[derive(SessionDiagnostic)]` can only be used on structs
|
||||||
|
--> $DIR/session-derive-errors.rs:28:1
|
||||||
|
|
|
||||||
|
LL | / #[error = "E0123"]
|
||||||
|
LL | |
|
||||||
|
LL | | enum SessionDiagnosticOnEnum {
|
||||||
|
LL | | Foo,
|
||||||
|
LL | | Bar,
|
||||||
|
LL | | }
|
||||||
|
| |_^
|
||||||
|
|
||||||
|
error: `#[label = ...]` is not a valid SessionDiagnostic struct attribute
|
||||||
|
--> $DIR/session-derive-errors.rs:37:1
|
||||||
|
|
|
||||||
|
LL | #[label = "This is in the wrong place"]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: `#[suggestion = ...]` is not a valid SessionDiagnostic field attribute
|
||||||
|
--> $DIR/session-derive-errors.rs:44:5
|
||||||
|
|
|
||||||
|
LL | #[suggestion = "this is the wrong kind of attribute"]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: `error` specified multiple times
|
||||||
|
--> $DIR/session-derive-errors.rs:52:11
|
||||||
|
|
|
||||||
|
LL | #[error = "E0456"]
|
||||||
|
| ^^^^^^^
|
||||||
|
|
||||||
|
error: `lint` specified when `error` was already specified
|
||||||
|
--> $DIR/session-derive-errors.rs:58:10
|
||||||
|
|
|
||||||
|
LL | #[lint = "some_useful_lint"]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: `code` not specified
|
||||||
|
--> $DIR/session-derive-errors.rs:67:1
|
||||||
|
|
|
||||||
|
LL | struct ErrorCodeNotProvided {}
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= help: use the [code = "..."] attribute to set this diagnostic's error code
|
||||||
|
|
||||||
|
error: the `#[message = "..."]` attribute can only be applied to fields of type Span
|
||||||
|
--> $DIR/session-derive-errors.rs:95:5
|
||||||
|
|
|
||||||
|
LL | #[message = "this message is applied to a String field"]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: `name` doesn't refer to a field on this type
|
||||||
|
--> $DIR/session-derive-errors.rs:102:1
|
||||||
|
|
|
||||||
|
LL | #[message = "This error has a field, and references {name}"]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: invalid format string: expected `'}'` but string was terminated
|
||||||
|
--> $DIR/session-derive-errors.rs:110:1
|
||||||
|
|
|
||||||
|
LL | #[error = "E0123"]
|
||||||
|
| - because of this opening brace
|
||||||
|
LL | #[message = "This is missing a closing brace: {name"]
|
||||||
|
| ^ expected `'}'` in format string
|
||||||
|
|
|
||||||
|
= note: if you intended to print `{`, you can escape it using `{{`
|
||||||
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
|
||||||
|
error: invalid format string: unmatched `}` found
|
||||||
|
--> $DIR/session-derive-errors.rs:119:1
|
||||||
|
|
|
||||||
|
LL | #[message = "This is missing an opening brace: name}"]
|
||||||
|
| ^ unmatched `}` in format string
|
||||||
|
|
|
||||||
|
= note: if you intended to print `}`, you can escape it using `}}`
|
||||||
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
|
||||||
|
error: The `#[label = ...]` attribute can only be applied to fields of type Span
|
||||||
|
--> $DIR/session-derive-errors.rs:138:5
|
||||||
|
|
|
||||||
|
LL | #[label = "See here"]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: `nonsense` is not a valid key for `#[suggestion(...)]`
|
||||||
|
--> $DIR/session-derive-errors.rs:163:18
|
||||||
|
|
|
||||||
|
LL | #[suggestion(nonsense = "This is nonsense")]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: `msg` is not a valid key for `#[suggestion(...)]`
|
||||||
|
--> $DIR/session-derive-errors.rs:171:18
|
||||||
|
|
|
||||||
|
LL | #[suggestion(msg = "This is a suggestion")]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: missing suggestion message
|
||||||
|
--> $DIR/session-derive-errors.rs:179:7
|
||||||
|
|
|
||||||
|
LL | #[suggestion(code = "This is suggested code")]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= help: provide a suggestion message using #[suggestion(message = "...")]
|
||||||
|
|
||||||
|
error: wrong field type for suggestion
|
||||||
|
--> $DIR/session-derive-errors.rs:194:5
|
||||||
|
|
|
||||||
|
LL | / #[suggestion(message = "This is a message", code = "This is suggested code")]
|
||||||
|
LL | |
|
||||||
|
LL | | suggestion: Applicability,
|
||||||
|
| |_____________________________^
|
||||||
|
|
|
||||||
|
= help: #[suggestion(...)] should be applied to fields of type Span or (Span, Applicability)
|
||||||
|
|
||||||
|
error: type of field annotated with `#[suggestion(...)]` contains more than one Span
|
||||||
|
--> $DIR/session-derive-errors.rs:209:5
|
||||||
|
|
|
||||||
|
LL | / #[suggestion(message = "This is a message", code = "This is suggested code")]
|
||||||
|
LL | |
|
||||||
|
LL | | suggestion: (Span, Span, Applicability),
|
||||||
|
| |___________________________________________^
|
||||||
|
|
||||||
|
error: type of field annotated with `#[suggestion(...)]` contains more than one Applicability
|
||||||
|
--> $DIR/session-derive-errors.rs:217:5
|
||||||
|
|
|
||||||
|
LL | / #[suggestion(message = "This is a message", code = "This is suggested code")]
|
||||||
|
LL | |
|
||||||
|
LL | | suggestion: (Applicability, Applicability, Span),
|
||||||
|
| |____________________________________________________^
|
||||||
|
|
||||||
|
error: invalid annotation list `#[label(...)]`
|
||||||
|
--> $DIR/session-derive-errors.rs:225:7
|
||||||
|
|
|
||||||
|
LL | #[label("wrong kind of annotation for label")]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: aborting due to 18 previous errors
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue