diff --git a/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs b/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs index 8b12db071b6ac..fe5e3c5a81b61 100644 --- a/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs @@ -237,7 +237,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { err.span_suggestion_verbose( span, "consider changing this to be mutable", - " mut ".into(), + " mut ", Applicability::MaybeIncorrect, ); } diff --git a/compiler/rustc_builtin_macros/src/deriving/default.rs b/compiler/rustc_builtin_macros/src/deriving/default.rs index 2c5260616c7da..b49331e28753d 100644 --- a/compiler/rustc_builtin_macros/src/deriving/default.rs +++ b/compiler/rustc_builtin_macros/src/deriving/default.rs @@ -235,7 +235,7 @@ fn validate_default_attribute( .span_suggestion_hidden( attr.span, "try using `#[default]`", - "#[default]".into(), + "#[default]", Applicability::MaybeIncorrect, ) .emit(); diff --git a/compiler/rustc_error_messages/locales/en-US/typeck.ftl b/compiler/rustc_error_messages/locales/en-US/typeck.ftl index 721201d931241..6a3235fc7728c 100644 --- a/compiler/rustc_error_messages/locales/en-US/typeck.ftl +++ b/compiler/rustc_error_messages/locales/en-US/typeck.ftl @@ -82,3 +82,11 @@ typeck-value-of-associated-struct-already-specified = typeck-address-of-temporary-taken = cannot take address of a temporary .label = temporary value + +typeck-add-return-type-add = try adding a return type + +typeck-add-return-type-missing-here = a return type might be missing here + +typeck-expected-default-return-type = expected `()` because of default return type + +typeck-expected-return-type = expected `{$expected}` because of return type diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs index cd17726c78588..83e6a751394f5 100644 --- a/compiler/rustc_errors/src/diagnostic.rs +++ b/compiler/rustc_errors/src/diagnostic.rs @@ -78,6 +78,13 @@ impl<'source> Into> for DiagnosticArgValue<'source> { } } +/// Trait implemented by error types. This should not be implemented manually. Instead, use +/// `#[derive(SessionSubdiagnostic)]` -- see [rustc_macros::SessionSubdiagnostic]. +pub trait AddSubdiagnostic { + /// Add a subdiagnostic to an existing diagnostic. + fn add_to_diagnostic(self, diag: &mut Diagnostic); +} + #[must_use] #[derive(Clone, Debug, Encodable, Decodable)] pub struct Diagnostic { @@ -605,7 +612,7 @@ impl Diagnostic { &mut self, sp: Span, msg: impl Into, - suggestion: String, + suggestion: impl ToString, applicability: Applicability, ) -> &mut Self { self.span_suggestion_with_style( @@ -623,13 +630,13 @@ impl Diagnostic { &mut self, sp: Span, msg: impl Into, - suggestion: String, + suggestion: impl ToString, applicability: Applicability, style: SuggestionStyle, ) -> &mut Self { self.push_suggestion(CodeSuggestion { substitutions: vec![Substitution { - parts: vec![SubstitutionPart { snippet: suggestion, span: sp }], + parts: vec![SubstitutionPart { snippet: suggestion.to_string(), span: sp }], }], msg: msg.into(), style, @@ -643,7 +650,7 @@ impl Diagnostic { &mut self, sp: Span, msg: impl Into, - suggestion: String, + suggestion: impl ToString, applicability: Applicability, ) -> &mut Self { self.span_suggestion_with_style( @@ -711,7 +718,7 @@ impl Diagnostic { &mut self, sp: Span, msg: impl Into, - suggestion: String, + suggestion: impl ToString, applicability: Applicability, ) -> &mut Self { self.span_suggestion_with_style( @@ -734,7 +741,7 @@ impl Diagnostic { &mut self, sp: Span, msg: impl Into, - suggestion: String, + suggestion: impl ToString, applicability: Applicability, ) -> &mut Self { self.span_suggestion_with_style( @@ -755,7 +762,7 @@ impl Diagnostic { &mut self, sp: Span, msg: impl Into, - suggestion: String, + suggestion: impl ToString, applicability: Applicability, ) -> &mut Self { self.span_suggestion_with_style( @@ -768,6 +775,13 @@ impl Diagnostic { self } + /// Add a subdiagnostic from a type that implements `SessionSubdiagnostic` - see + /// [rustc_macros::SessionSubdiagnostic]. + pub fn subdiagnostic(&mut self, subdiagnostic: impl AddSubdiagnostic) -> &mut Self { + subdiagnostic.add_to_diagnostic(self); + self + } + pub fn set_span>(&mut self, sp: S) -> &mut Self { self.span = sp.into(); if let Some(span) = self.span.primary_span() { diff --git a/compiler/rustc_errors/src/diagnostic_builder.rs b/compiler/rustc_errors/src/diagnostic_builder.rs index d218040847704..96b730c2baaff 100644 --- a/compiler/rustc_errors/src/diagnostic_builder.rs +++ b/compiler/rustc_errors/src/diagnostic_builder.rs @@ -477,7 +477,7 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> { &mut self, sp: Span, msg: impl Into, - suggestion: String, + suggestion: impl ToString, applicability: Applicability, ) -> &mut Self); forward!(pub fn span_suggestions( @@ -497,28 +497,28 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> { &mut self, sp: Span, msg: impl Into, - suggestion: String, + suggestion: impl ToString, applicability: Applicability, ) -> &mut Self); forward!(pub fn span_suggestion_verbose( &mut self, sp: Span, msg: impl Into, - suggestion: String, + suggestion: impl ToString, applicability: Applicability, ) -> &mut Self); forward!(pub fn span_suggestion_hidden( &mut self, sp: Span, msg: impl Into, - suggestion: String, + suggestion: impl ToString, applicability: Applicability, ) -> &mut Self); forward!(pub fn tool_only_span_suggestion( &mut self, sp: Span, msg: impl Into, - suggestion: String, + suggestion: impl ToString, applicability: Applicability, ) -> &mut Self); @@ -530,6 +530,11 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> { name: impl Into>, arg: DiagnosticArgValue<'static>, ) -> &mut Self); + + forward!(pub fn subdiagnostic( + &mut self, + subdiagnostic: impl crate::AddSubdiagnostic + ) -> &mut Self); } impl Debug for DiagnosticBuilder<'_, G> { diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index a64133bb7f4f9..df41fc00714b6 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -370,8 +370,8 @@ impl fmt::Display for ExplicitBug { impl error::Error for ExplicitBug {} pub use diagnostic::{ - Diagnostic, DiagnosticArg, DiagnosticArgValue, DiagnosticId, DiagnosticStyledString, - IntoDiagnosticArg, SubDiagnostic, + AddSubdiagnostic, Diagnostic, DiagnosticArg, DiagnosticArgValue, DiagnosticId, + DiagnosticStyledString, IntoDiagnosticArg, SubDiagnostic, }; pub use diagnostic_builder::{DiagnosticBuilder, EmissionGuarantee}; use std::backtrace::Backtrace; diff --git a/compiler/rustc_expand/src/config.rs b/compiler/rustc_expand/src/config.rs index a9c7ba5d072d2..c91125105d746 100644 --- a/compiler/rustc_expand/src/config.rs +++ b/compiler/rustc_expand/src/config.rs @@ -511,7 +511,7 @@ pub fn parse_cfg<'a>(meta_item: &'a MetaItem, sess: &Session) -> Option<&'a Meta err.span_suggestion( span, "expected syntax is", - suggestion.into(), + suggestion, Applicability::HasPlaceholders, ); } diff --git a/compiler/rustc_infer/src/infer/error_reporting/mod.rs b/compiler/rustc_infer/src/infer/error_reporting/mod.rs index f9273cc50b70a..14555ad92559d 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/mod.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/mod.rs @@ -2198,7 +2198,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> { err.span_suggestion( span.with_hi(before_close).shrink_to_hi(), msg, - ",".into(), + ",", Applicability::MachineApplicable, ); } else { diff --git a/compiler/rustc_lint/src/array_into_iter.rs b/compiler/rustc_lint/src/array_into_iter.rs index a14d602036131..dff2e31c6070c 100644 --- a/compiler/rustc_lint/src/array_into_iter.rs +++ b/compiler/rustc_lint/src/array_into_iter.rs @@ -129,7 +129,7 @@ impl<'tcx> LateLintPass<'tcx> for ArrayIntoIter { diag.span_suggestion( call.ident.span, "use `.iter()` instead of `.into_iter()` to avoid ambiguity", - "iter".into(), + "iter", Applicability::MachineApplicable, ); if self.for_expr_span == expr.span { diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs index 0ffa65b79b584..d7cd5ec04f323 100644 --- a/compiler/rustc_lint/src/context.rs +++ b/compiler/rustc_lint/src/context.rs @@ -738,7 +738,7 @@ pub trait LintContext: Sized { db.span_suggestion_verbose( span.shrink_to_hi(), "insert whitespace here to avoid this being parsed as a prefix in Rust 2021", - " ".into(), + " ", Applicability::MachineApplicable, ); } diff --git a/compiler/rustc_lint/src/non_fmt_panic.rs b/compiler/rustc_lint/src/non_fmt_panic.rs index f21f25c35847e..71769fceec1f2 100644 --- a/compiler/rustc_lint/src/non_fmt_panic.rs +++ b/compiler/rustc_lint/src/non_fmt_panic.rs @@ -176,7 +176,7 @@ fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tc l.span_suggestion_verbose( arg_span.shrink_to_lo(), "add a \"{}\" format string to Display the message", - "\"{}\", ".into(), + "\"{}\", ", fmt_applicability, ); } else if suggest_debug { @@ -186,7 +186,7 @@ fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tc "add a \"{{:?}}\" format string to use the Debug implementation of `{}`", ty, ), - "\"{:?}\", ".into(), + "\"{:?}\", ", fmt_applicability, ); } @@ -266,13 +266,13 @@ fn check_panic_str<'tcx>( l.span_suggestion( arg.span.shrink_to_hi(), &format!("add the missing argument{}", pluralize!(n_arguments)), - ", ...".into(), + ", ...", Applicability::HasPlaceholders, ); l.span_suggestion( arg.span.shrink_to_lo(), "or add a \"{}\" format string to use the message literally", - "\"{}\", ".into(), + "\"{}\", ", Applicability::MachineApplicable, ); } @@ -297,7 +297,7 @@ fn check_panic_str<'tcx>( l.span_suggestion( arg.span.shrink_to_lo(), "add a \"{}\" format string to use the message literally", - "\"{}\", ".into(), + "\"{}\", ", Applicability::MachineApplicable, ); } diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic.rs b/compiler/rustc_macros/src/diagnostics/diagnostic.rs new file mode 100644 index 0000000000000..f49166433faad --- /dev/null +++ b/compiler/rustc_macros/src/diagnostics/diagnostic.rs @@ -0,0 +1,586 @@ +#![deny(unused_must_use)] + +use crate::diagnostics::error::{ + invalid_nested_attr, span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err, + SessionDiagnosticDeriveError, +}; +use crate::diagnostics::utils::{ + option_inner_ty, report_error_if_not_applied_to_span, type_matches_path, Applicability, + FieldInfo, HasFieldMap, SetOnce, +}; +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +use std::collections::HashMap; +use std::str::FromStr; +use syn::{spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, Type}; +use synstructure::Structure; + +/// The central struct for constructing the `into_diagnostic` 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, + + /// 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. + code: Option<(String, proc_macro::Span)>, +} + +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 { + 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 { + Meta::List(MetaList { ref nested, .. }) => nested, + _ => throw_invalid_attr!(attr, &meta), + }; + + let kind = match name { + "error" => SessionDiagnosticKind::Error, + "warning" => SessionDiagnosticKind::Warn, + _ => throw_invalid_attr!(attr, &meta, |diag| { + diag.help("only `error` and `warning` are valid attributes") + }), + }; + self.kind.set_once((kind, span)); + + let mut tokens = Vec::new(); + for nested_attr in nested { + let meta = match nested_attr { + syn::NestedMeta::Meta(meta) => meta, + _ => throw_invalid_nested_attr!(attr, &nested_attr), + }; + + 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), .. }) => { + let span = s.span().unwrap(); + match nested_name.as_str() { + "slug" => { + self.slug.set_once((s.value(), span)); + } + "code" => { + 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())); + }); + } + _ => invalid_nested_attr(attr, &nested_attr) + .help("only `slug` and `code` are valid nested attributes") + .emit(), + } + } + _ => invalid_nested_attr(attr, &nested_attr).emit(), + } + } + + Ok(tokens.drain(..).collect()) + } + + fn generate_field_attr_code( + &mut self, + attr: &syn::Attribute, + info: FieldInfo<'_>, + ) -> Result { + 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 { + 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)) + } + "subdiagnostic" => Ok(quote! { #diag.subdiagnostic(*#field_binding); }), + _ => throw_invalid_attr!(attr, &meta, |diag| { + diag + .help("only `skip_arg`, `primary_span`, `label`, `note`, `help` and `subdiagnostic` are valid field attributes") + }), + }, + Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(ref s), .. }) => match name { + "label" | "note" | "help" => { + report_error_if_not_applied_to_span(attr, &info)?; + Ok(self.add_subdiagnostic(field_binding, name, &s.value())) + } + _ => throw_invalid_attr!(attr, &meta, |diag| { + diag.help("only `label`, `note` and `help` are valid field attributes") + }), + }, + Meta::List(MetaList { ref path, ref nested, .. }) => { + let name = path.segments.last().unwrap().ident.to_string(); + let name = name.as_ref(); + + match name { + "suggestion" | "suggestion_short" | "suggestion_hidden" + | "suggestion_verbose" => (), + _ => throw_invalid_attr!(attr, &meta, |diag| { + diag + .help("only `suggestion{,_short,_hidden,_verbose}` are valid field attributes") + }), + }; + + let (span_field, mut applicability) = self.span_and_applicability_of_ty(info)?; + + let mut msg = None; + let mut code = None; + + 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), + }; + + 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), .. }) => { + let span = meta.span().unwrap(); + match nested_name { + "message" => { + msg = Some(s.value()); + } + "code" => { + let formatted_str = self.build_format(&s.value(), s.span()); + code = Some(formatted_str); + } + "applicability" => { + applicability = match applicability { + Some(v) => { + span_err( + span, + "applicability cannot be set in both the field and attribute" + ).emit(); + Some(v) + } + None => match Applicability::from_str(&s.value()) { + Ok(v) => Some(quote! { #v }), + Err(()) => { + span_err(span, "invalid applicability").emit(); + None + } + }, + } + } + _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| { + diag.help( + "only `message`, `code` and `applicability` are valid field attributes", + ) + }), + } + } + _ => throw_invalid_nested_attr!(attr, &nested_attr), + } + } + + let applicability = applicability + .unwrap_or_else(|| quote!(rustc_errors::Applicability::Unspecified)); + + 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_field, #msg, #code, #applicability); }) + } + _ => throw_invalid_attr!(attr, &meta), + } + } + + /// 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, Option), 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), None)) + } + // 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, Some(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)`") + }), + } + } +} diff --git a/compiler/rustc_macros/src/diagnostics/error.rs b/compiler/rustc_macros/src/diagnostics/error.rs new file mode 100644 index 0000000000000..fd1dc2f307397 --- /dev/null +++ b/compiler/rustc_macros/src/diagnostics/error.rs @@ -0,0 +1,132 @@ +use proc_macro::{Diagnostic, Level, MultiSpan}; +use proc_macro2::TokenStream; +use quote::quote; +use syn::{spanned::Spanned, Attribute, Error as SynError, Meta, NestedMeta}; + +#[derive(Debug)] +pub(crate) enum SessionDiagnosticDeriveError { + SynError(SynError), + ErrorHandled, +} + +impl SessionDiagnosticDeriveError { + pub(crate) fn to_compile_error(self) -> 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!(); } + } + } + } + } +} + +impl From for SessionDiagnosticDeriveError { + fn from(e: SynError) -> Self { + SessionDiagnosticDeriveError::SynError(e) + } +} + +/// Helper function for use with `throw_*` macros - constraints `$f` to an `impl FnOnce`. +pub(crate) fn _throw_err( + diag: Diagnostic, + f: impl FnOnce(Diagnostic) -> Diagnostic, +) -> SessionDiagnosticDeriveError { + f(diag).emit(); + SessionDiagnosticDeriveError::ErrorHandled +} + +/// Returns an error diagnostic on span `span` with msg `msg`. +pub(crate) fn span_err(span: impl MultiSpan, msg: &str) -> Diagnostic { + Diagnostic::spanned(span, Level::Error, msg) +} + +/// Emit a diagnostic on span `$span` with msg `$msg` (optionally performing additional decoration +/// using the `FnOnce` passed in `diag`) and return `Err(ErrorHandled)`. +/// +/// For methods that return a `Result<_, SessionDiagnosticDeriveError>`: +macro_rules! throw_span_err { + ($span:expr, $msg:expr) => {{ throw_span_err!($span, $msg, |diag| diag) }}; + ($span:expr, $msg:expr, $f:expr) => {{ + let diag = span_err($span, $msg); + return Err(crate::diagnostics::error::_throw_err(diag, $f)); + }}; +} + +pub(crate) use throw_span_err; + +/// Returns an error diagnostic for an invalid attribute. +pub(crate) fn invalid_attr(attr: &Attribute, meta: &Meta) -> Diagnostic { + let span = attr.span().unwrap(); + let name = attr.path.segments.last().unwrap().ident.to_string(); + let name = name.as_str(); + + match meta { + Meta::Path(_) => span_err(span, &format!("`#[{}]` is not a valid attribute", name)), + Meta::NameValue(_) => { + span_err(span, &format!("`#[{} = ...]` is not a valid attribute", name)) + } + Meta::List(_) => span_err(span, &format!("`#[{}(...)]` is not a valid attribute", name)), + } +} + +/// Emit a error diagnostic for an invalid attribute (optionally performing additional decoration +/// using the `FnOnce` passed in `diag`) and return `Err(ErrorHandled)`. +/// +/// For methods that return a `Result<_, SessionDiagnosticDeriveError>`: +macro_rules! throw_invalid_attr { + ($attr:expr, $meta:expr) => {{ throw_invalid_attr!($attr, $meta, |diag| diag) }}; + ($attr:expr, $meta:expr, $f:expr) => {{ + let diag = crate::diagnostics::error::invalid_attr($attr, $meta); + return Err(crate::diagnostics::error::_throw_err(diag, $f)); + }}; +} + +pub(crate) use throw_invalid_attr; + +/// Returns an error diagnostic for an invalid nested attribute. +pub(crate) fn invalid_nested_attr(attr: &Attribute, nested: &NestedMeta) -> Diagnostic { + let name = attr.path.segments.last().unwrap().ident.to_string(); + let name = name.as_str(); + + let span = nested.span().unwrap(); + let meta = match nested { + syn::NestedMeta::Meta(meta) => meta, + syn::NestedMeta::Lit(_) => { + return span_err(span, &format!("`#[{}(\"...\")]` is not a valid attribute", name)); + } + }; + + let span = meta.span().unwrap(); + let nested_name = meta.path().segments.last().unwrap().ident.to_string(); + let nested_name = nested_name.as_str(); + match meta { + Meta::NameValue(..) => span_err( + span, + &format!("`#[{}({} = ...)]` is not a valid attribute", name, nested_name), + ), + Meta::Path(..) => { + span_err(span, &format!("`#[{}({})]` is not a valid attribute", name, nested_name)) + } + Meta::List(..) => { + span_err(span, &format!("`#[{}({}(...))]` is not a valid attribute", name, nested_name)) + } + } +} + +/// Emit a error diagnostic for an invalid nested attribute (optionally performing additional +/// decoration using the `FnOnce` passed in `diag`) and return `Err(ErrorHandled)`. +/// +/// For methods that return a `Result<_, SessionDiagnosticDeriveError>`: +macro_rules! throw_invalid_nested_attr { + ($attr:expr, $nested_attr:expr) => {{ throw_invalid_nested_attr!($attr, $nested_attr, |diag| diag) }}; + ($attr:expr, $nested_attr:expr, $f:expr) => {{ + let diag = crate::diagnostics::error::invalid_nested_attr($attr, $nested_attr); + return Err(crate::diagnostics::error::_throw_err(diag, $f)); + }}; +} + +pub(crate) use throw_invalid_nested_attr; diff --git a/compiler/rustc_macros/src/diagnostics/mod.rs b/compiler/rustc_macros/src/diagnostics/mod.rs new file mode 100644 index 0000000000000..73ad415d6c355 --- /dev/null +++ b/compiler/rustc_macros/src/diagnostics/mod.rs @@ -0,0 +1,114 @@ +mod diagnostic; +mod error; +mod subdiagnostic; +mod utils; + +use diagnostic::SessionDiagnosticDerive; +use proc_macro2::TokenStream; +use quote::format_ident; +use subdiagnostic::SessionSubdiagnosticDerive; +use synstructure::Structure; + +/// 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)] +/// #[error(code = "E0505", slug = "borrowck-move-out-of-borrow")] +/// pub struct MoveOutOfBorrowError<'tcx> { +/// pub name: Ident, +/// pub ty: Ty<'tcx>, +/// #[primary_span] +/// #[label] +/// pub span: Span, +/// #[label = "first-borrow-label"] +/// pub first_borrow_span: Span, +/// #[suggestion(code = "{name}.clone()")] +/// pub clone_sugg: Option<(Span, Applicability)> +/// } +/// ``` +/// +/// ```fluent +/// move-out-of-borrow = cannot move out of {$name} because it is borrowed +/// .label = cannot move out of borrow +/// .first-borrow-label = `{$ty}` first borrowed here +/// .suggestion = consider cloning here +/// ``` +/// +/// Then, later, to emit the error: +/// +/// ```ignore (pseudo-rust) +/// sess.emit_err(MoveOutOfBorrowError { +/// expected, +/// actual, +/// span, +/// first_borrow_span, +/// clone_sugg: Some(suggestion, Applicability::MachineApplicable), +/// }); +/// ``` +/// +/// See rustc dev guide for more examples on using the `#[derive(SessionDiagnostic)]`: +/// +pub fn session_diagnostic_derive(s: Structure<'_>) -> 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() +} + +/// Implements `#[derive(SessionSubdiagnostic)]`, which allows for labels, notes, helps and +/// suggestions to be specified as a structs or enums, independent from the actual diagnostics +/// emitting code or diagnostic derives. +/// +/// ```ignore (pseudo-rust) +/// #[derive(SessionSubdiagnostic)] +/// pub enum ExpectedIdentifierLabel<'tcx> { +/// #[label(slug = "parser-expected-identifier")] +/// WithoutFound { +/// #[primary_span] +/// span: Span, +/// } +/// #[label(slug = "parser-expected-identifier-found")] +/// WithFound { +/// #[primary_span] +/// span: Span, +/// found: String, +/// } +/// } +/// +/// #[derive(SessionSubdiagnostic)] +/// #[suggestion_verbose(slug = "parser-raw-identifier")] +/// pub struct RawIdentifierSuggestion<'tcx> { +/// #[primary_span] +/// span: Span, +/// #[applicability] +/// applicability: Applicability, +/// ident: Ident, +/// } +/// ``` +/// +/// ```fluent +/// parser-expected-identifier = expected identifier +/// +/// parser-expected-identifier-found = expected identifier, found {$found} +/// +/// parser-raw-identifier = escape `{$ident}` to use it as an identifier +/// ``` +/// +/// Then, later, to add the subdiagnostic: +/// +/// ```ignore (pseudo-rust) +/// diag.subdiagnostic(ExpectedIdentifierLabel::WithoutFound { span }); +/// +/// diag.subdiagnostic(RawIdentifierSuggestion { span, applicability, ident }); +/// ``` +pub fn session_subdiagnostic_derive(s: Structure<'_>) -> TokenStream { + SessionSubdiagnosticDerive::new(s).into_tokens() +} diff --git a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs new file mode 100644 index 0000000000000..961b42f424fd1 --- /dev/null +++ b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs @@ -0,0 +1,437 @@ +#![deny(unused_must_use)] + +use crate::diagnostics::error::{ + span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err, + SessionDiagnosticDeriveError, +}; +use crate::diagnostics::utils::{ + option_inner_ty, report_error_if_not_applied_to_applicability, + report_error_if_not_applied_to_span, Applicability, FieldInfo, HasFieldMap, SetOnce, +}; +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +use std::collections::HashMap; +use std::fmt; +use std::str::FromStr; +use syn::{spanned::Spanned, Meta, MetaList, MetaNameValue}; +use synstructure::{BindingInfo, Structure, VariantInfo}; + +/// Which kind of suggestion is being created? +#[derive(Clone, Copy)] +enum SubdiagnosticSuggestionKind { + /// `#[suggestion]` + Normal, + /// `#[suggestion_short]` + Short, + /// `#[suggestion_hidden]` + Hidden, + /// `#[suggestion_verbose]` + Verbose, +} + +/// Which kind of subdiagnostic is being created from a variant? +#[derive(Clone, Copy)] +enum SubdiagnosticKind { + /// `#[label(...)]` + Label, + /// `#[note(...)]` + Note, + /// `#[help(...)]` + Help, + /// `#[suggestion{,_short,_hidden,_verbose}]` + Suggestion(SubdiagnosticSuggestionKind), +} + +impl FromStr for SubdiagnosticKind { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "label" => Ok(SubdiagnosticKind::Label), + "note" => Ok(SubdiagnosticKind::Note), + "help" => Ok(SubdiagnosticKind::Help), + "suggestion" => Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Normal)), + "suggestion_short" => { + Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Short)) + } + "suggestion_hidden" => { + Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Hidden)) + } + "suggestion_verbose" => { + Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Verbose)) + } + _ => Err(()), + } + } +} + +impl quote::IdentFragment for SubdiagnosticKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + SubdiagnosticKind::Label => write!(f, "label"), + SubdiagnosticKind::Note => write!(f, "note"), + SubdiagnosticKind::Help => write!(f, "help"), + SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Normal) => { + write!(f, "suggestion") + } + SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Short) => { + write!(f, "suggestion_short") + } + SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Hidden) => { + write!(f, "suggestion_hidden") + } + SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Verbose) => { + write!(f, "suggestion_verbose") + } + } + } + + fn span(&self) -> Option { + None + } +} + +/// The central struct for constructing the `add_to_diagnostic` method from an annotated struct. +pub(crate) struct SessionSubdiagnosticDerive<'a> { + structure: Structure<'a>, + diag: syn::Ident, +} + +impl<'a> SessionSubdiagnosticDerive<'a> { + pub(crate) fn new(structure: Structure<'a>) -> Self { + let diag = format_ident!("diag"); + Self { structure, diag } + } + + pub(crate) fn into_tokens(self) -> TokenStream { + let SessionSubdiagnosticDerive { mut structure, diag } = self; + let implementation = { + let ast = structure.ast(); + let span = ast.span().unwrap(); + match ast.data { + syn::Data::Struct(..) | syn::Data::Enum(..) => (), + syn::Data::Union(..) => { + span_err( + span, + "`#[derive(SessionSubdiagnostic)]` can only be used on structs and enums", + ); + } + } + + if matches!(ast.data, syn::Data::Enum(..)) { + for attr in &ast.attrs { + span_err( + attr.span().unwrap(), + "unsupported type attribute for subdiagnostic enum", + ) + .emit(); + } + } + + structure.bind_with(|_| synstructure::BindStyle::Move); + let variants_ = structure.each_variant(|variant| { + // Build the mapping of field names to fields. This allows attributes to peek + // values from other fields. + let mut fields_map = HashMap::new(); + for binding in variant.bindings() { + let field = binding.ast(); + if let Some(ident) = &field.ident { + fields_map.insert(ident.to_string(), quote! { #binding }); + } + } + + let mut builder = SessionSubdiagnosticDeriveBuilder { + diag: &diag, + variant, + span, + fields: fields_map, + kind: None, + slug: None, + code: None, + span_field: None, + applicability: None, + }; + builder.into_tokens().unwrap_or_else(|v| v.to_compile_error()) + }); + + quote! { + match self { + #variants_ + } + } + }; + + let ret = structure.gen_impl(quote! { + gen impl rustc_errors::AddSubdiagnostic for @Self { + fn add_to_diagnostic(self, #diag: &mut rustc_errors::Diagnostic) { + use rustc_errors::{Applicability, IntoDiagnosticArg}; + #implementation + } + } + }); + ret + } +} + +/// Tracks persistent information required for building up the call to add to the diagnostic +/// for the final generated method. This is a separate struct to `SessionSubdiagnosticDerive` +/// only to be able to destructure and split `self.builder` and the `self.structure` up to avoid a +/// double mut borrow later on. +struct SessionSubdiagnosticDeriveBuilder<'a> { + /// The identifier to use for the generated `DiagnosticBuilder` instance. + diag: &'a syn::Ident, + + /// Info for the current variant (or the type if not an enum). + variant: &'a VariantInfo<'a>, + /// Span for the entire type. + span: proc_macro::Span, + + /// Store a map of field name to its corresponding field. This is built on construction of the + /// derive builder. + fields: HashMap, + + /// Subdiagnostic kind of the type/variant. + kind: Option<(SubdiagnosticKind, proc_macro::Span)>, + + /// Slug of the subdiagnostic - corresponds to the Fluent identifier for the message - from the + /// `#[kind(slug = "...")]` attribute on the type or variant. + slug: Option<(String, proc_macro::Span)>, + /// If a suggestion, the code to suggest as a replacement - from the `#[kind(code = "...")]` + /// attribute on the type or variant. + code: Option<(TokenStream, proc_macro::Span)>, + + /// Identifier for the binding to the `#[primary_span]` field. + span_field: Option<(proc_macro2::Ident, proc_macro::Span)>, + /// If a suggestion, the identifier for the binding to the `#[applicability]` field or a + /// `rustc_errors::Applicability::*` variant directly. + applicability: Option<(TokenStream, proc_macro::Span)>, +} + +impl<'a> HasFieldMap for SessionSubdiagnosticDeriveBuilder<'a> { + fn get_field_binding(&self, field: &String) -> Option<&TokenStream> { + self.fields.get(field) + } +} + +impl<'a> SessionSubdiagnosticDeriveBuilder<'a> { + fn identify_kind(&mut self) -> Result<(), SessionDiagnosticDeriveError> { + for attr in self.variant.ast().attrs { + 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()?; + let kind = match meta { + Meta::List(MetaList { ref nested, .. }) => { + for nested_attr in nested { + let meta = match nested_attr { + syn::NestedMeta::Meta(ref meta) => meta, + _ => throw_invalid_nested_attr!(attr, &nested_attr), + }; + + let span = meta.span().unwrap(); + 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 { + "code" => { + let formatted_str = self.build_format(&s.value(), s.span()); + self.code.set_once((formatted_str, span)); + } + "slug" => self.slug.set_once((s.value(), span)), + "applicability" => { + let value = match Applicability::from_str(&s.value()) { + Ok(v) => v, + Err(()) => { + span_err(span, "invalid applicability").emit(); + Applicability::Unspecified + } + }; + self.applicability.set_once((quote! { #value }, span)); + } + _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| { + diag.help("only `code`, `slug` and `applicability` are valid nested attributes") + }), + } + } + _ => throw_invalid_nested_attr!(attr, &nested_attr), + } + } + + let Ok(kind) = SubdiagnosticKind::from_str(name) else { + throw_invalid_attr!(attr, &meta) + }; + + kind + } + _ => throw_invalid_attr!(attr, &meta), + }; + + if matches!( + kind, + SubdiagnosticKind::Label | SubdiagnosticKind::Help | SubdiagnosticKind::Note + ) && self.code.is_some() + { + throw_span_err!( + span, + &format!("`code` is not a valid nested attribute of a `{}` attribute", name) + ); + } + + if self.slug.is_none() { + throw_span_err!( + span, + &format!("`slug` must be set in a `#[{}(...)]` attribute", name) + ); + } + + self.kind.set_once((kind, span)); + } + + Ok(()) + } + + fn generate_field_code( + &mut self, + binding: &BindingInfo<'_>, + is_suggestion: bool, + ) -> Result { + let ast = binding.ast(); + + let option_ty = option_inner_ty(&ast.ty); + let info = FieldInfo { + vis: &ast.vis, + binding: binding, + ty: option_ty.unwrap_or(&ast.ty), + span: &ast.span(), + }; + + for attr in &ast.attrs { + let name = attr.path.segments.last().unwrap().ident.to_string(); + let name = name.as_str(); + let span = attr.span().unwrap(); + + let meta = attr.parse_meta()?; + match meta { + Meta::Path(_) => match name { + "primary_span" => { + report_error_if_not_applied_to_span(attr, &info)?; + self.span_field.set_once((binding.binding.clone(), span)); + return Ok(quote! {}); + } + "applicability" if is_suggestion => { + report_error_if_not_applied_to_applicability(attr, &info)?; + let binding = binding.binding.clone(); + self.applicability.set_once((quote! { #binding }, span)); + return Ok(quote! {}); + } + "applicability" => { + span_err(span, "`#[applicability]` is only valid on suggestions").emit(); + return Ok(quote! {}); + } + "skip_arg" => { + return Ok(quote! {}); + } + _ => throw_invalid_attr!(attr, &meta, |diag| { + diag.help("only `primary_span`, `applicability` and `skip_arg` are valid field attributes") + }), + }, + _ => throw_invalid_attr!(attr, &meta), + } + } + + let ident = ast.ident.as_ref().unwrap(); + + let diag = &self.diag; + let generated = quote! { + #diag.set_arg( + stringify!(#ident), + #binding.into_diagnostic_arg() + ); + }; + + if option_ty.is_none() { + Ok(quote! { #generated }) + } else { + Ok(quote! { + if let Some(#binding) = #binding { + #generated + } + }) + } + } + + fn into_tokens(&mut self) -> Result { + self.identify_kind()?; + let Some(kind) = self.kind.map(|(kind, _)| kind) else { + throw_span_err!( + self.variant.ast().ident.span().unwrap(), + "subdiagnostic kind not specified" + ); + }; + + let is_suggestion = matches!(kind, SubdiagnosticKind::Suggestion(_)); + + let mut args = TokenStream::new(); + for binding in self.variant.bindings() { + let arg = self + .generate_field_code(binding, is_suggestion) + .unwrap_or_else(|v| v.to_compile_error()); + args.extend(arg); + } + + // Missing slug errors will already have been reported. + let slug = self.slug.as_ref().map(|(slug, _)| &**slug).unwrap_or("missing-slug"); + let code = match self.code.as_ref() { + Some((code, _)) => Some(quote! { #code }), + None if is_suggestion => { + span_err(self.span, "suggestion without `code = \"...\"`").emit(); + Some(quote! { /* macro error */ "..." }) + } + None => None, + }; + + let span_field = self.span_field.as_ref().map(|(span, _)| span); + let applicability = match self.applicability.clone() { + Some((applicability, _)) => Some(applicability), + None if is_suggestion => { + span_err(self.span, "suggestion without `applicability`").emit(); + Some(quote! { rustc_errors::Applicability::Unspecified }) + } + None => None, + }; + + let diag = &self.diag; + let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind); + let message = quote! { rustc_errors::DiagnosticMessage::fluent(#slug) }; + let call = if matches!(kind, SubdiagnosticKind::Suggestion(..)) { + if let Some(span) = span_field { + quote! { #diag.#name(#span, #message, #code, #applicability); } + } else { + span_err(self.span, "suggestion without `#[primary_span]` field").emit(); + quote! { unreachable!(); } + } + } else if matches!(kind, SubdiagnosticKind::Label) { + if let Some(span) = span_field { + quote! { #diag.#name(#span, #message); } + } else { + span_err(self.span, "label without `#[primary_span]` field").emit(); + quote! { unreachable!(); } + } + } else { + if let Some(span) = span_field { + quote! { #diag.#name(#span, #message); } + } else { + quote! { #diag.#name(#message); } + } + }; + + Ok(quote! { + #call + #args + }) + } +} diff --git a/compiler/rustc_macros/src/diagnostics/utils.rs b/compiler/rustc_macros/src/diagnostics/utils.rs new file mode 100644 index 0000000000000..1f36af0a20bcd --- /dev/null +++ b/compiler/rustc_macros/src/diagnostics/utils.rs @@ -0,0 +1,267 @@ +use crate::diagnostics::error::{span_err, throw_span_err, SessionDiagnosticDeriveError}; +use proc_macro::Span; +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +use std::collections::BTreeSet; +use std::str::FromStr; +use syn::{spanned::Spanned, Attribute, Meta, Type, Visibility}; +use synstructure::BindingInfo; + +/// 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. +pub(crate) fn type_matches_path(ty: &Type, name: &[&str]) -> bool { + if let 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 + } +} + +/// Reports an error if the field's type is not `Applicability`. +fn report_error_if_not_applied_to_ty( + attr: &Attribute, + info: &FieldInfo<'_>, + path: &[&str], + ty_name: &str, +) -> Result<(), SessionDiagnosticDeriveError> { + if !type_matches_path(&info.ty, path) { + let name = attr.path.segments.last().unwrap().ident.to_string(); + let name = name.as_str(); + let meta = attr.parse_meta()?; + + throw_span_err!( + attr.span().unwrap(), + &format!( + "the `#[{}{}]` attribute can only be applied to fields of type `{}`", + name, + match meta { + Meta::Path(_) => "", + Meta::NameValue(_) => " = ...", + Meta::List(_) => "(...)", + }, + ty_name + ) + ); + } + + Ok(()) +} + +/// Reports an error if the field's type is not `Applicability`. +pub(crate) fn report_error_if_not_applied_to_applicability( + attr: &Attribute, + info: &FieldInfo<'_>, +) -> Result<(), SessionDiagnosticDeriveError> { + report_error_if_not_applied_to_ty( + attr, + info, + &["rustc_errors", "Applicability"], + "Applicability", + ) +} + +/// Reports an error if the field's type is not `Span`. +pub(crate) fn report_error_if_not_applied_to_span( + attr: &Attribute, + info: &FieldInfo<'_>, +) -> Result<(), SessionDiagnosticDeriveError> { + report_error_if_not_applied_to_ty(attr, info, &["rustc_span", "Span"], "Span") +} + +/// If `ty` is an Option, returns `Some(inner type)`, otherwise returns `None`. +pub(crate) fn option_inner_ty(ty: &Type) -> Option<&Type> { + if type_matches_path(ty, &["std", "option", "Option"]) { + if let 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 +} + +/// Field information passed to the builder. Deliberately omits attrs to discourage the +/// `generate_*` methods from walking the attributes themselves. +pub(crate) struct FieldInfo<'a> { + pub(crate) vis: &'a Visibility, + pub(crate) binding: &'a BindingInfo<'a>, + pub(crate) ty: &'a Type, + pub(crate) span: &'a proc_macro2::Span, +} + +/// Small helper trait for abstracting over `Option` fields that contain a value and a `Span` +/// for error reporting if they are set more than once. +pub(crate) trait SetOnce { + fn set_once(&mut self, value: T); +} + +impl SetOnce<(T, Span)> for Option<(T, Span)> { + fn set_once(&mut self, (value, span): (T, Span)) { + match self { + None => { + *self = Some((value, span)); + } + Some((_, prev_span)) => { + span_err(span, "specified multiple times") + .span_note(*prev_span, "previously specified here") + .emit(); + } + } + } +} + +pub(crate) trait HasFieldMap { + /// Returns the binding for the field with the given name, if it exists on the type. + fn get_field_binding(&self, field: &String) -> Option<&TokenStream>; + + /// In the strings in the attributes supplied to this macro, we want callers to be able to + /// reference fields in the format string. For example: + /// + /// ```ignore (not-usage-example) + /// /// Suggest `==` when users wrote `===`. + /// #[suggestion(slug = "parser-not-javascript-eq", code = "{lhs} == {rhs}")] + /// struct NotJavaScriptEq { + /// #[primary_span] + /// span: Span, + /// lhs: Ident, + /// rhs: Ident, + /// } + /// ``` + /// + /// We want to automatically pick up that `{lhs}` refers `self.lhs` and `{rhs}` refers to + /// `self.rhs`, then generate this call to `format!`: + /// + /// ```ignore (not-usage-example) + /// format!("{lhs} == {rhs}", lhs = self.lhs, rhs = self.rhs) + /// ``` + /// + /// This function builds the entire call to `format!`. + fn build_format(&self, input: &str, span: proc_macro2::Span) -> TokenStream { + // This set is used later to generate the final format string. To keep builds reproducible, + // the iteration order needs to be deterministic, hence why we use a `BTreeSet` here + // instead of a `HashSet`. + let mut referenced_fields: BTreeSet = BTreeSet::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') != '{' { + let mut eat_argument = || -> Option { + 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 = match self.get_field_binding(&field) { + Some(value) => value.clone(), + // This field doesn't exist. Emit a diagnostic. + None => { + span_err( + span.unwrap(), + &format!("`{}` doesn't refer to a field on this type", field), + ) + .emit(); + quote! { + "{#field}" + } + } + }; + quote! { + #field_ident = #value + } + }); + quote! { + format!(#input #(,#args)*) + } + } +} + +/// `Applicability` of a suggestion - mirrors `rustc_errors::Applicability` - and used to represent +/// the user's selection of applicability if specified in an attribute. +pub(crate) enum Applicability { + MachineApplicable, + MaybeIncorrect, + HasPlaceholders, + Unspecified, +} + +impl FromStr for Applicability { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "machine-applicable" => Ok(Applicability::MachineApplicable), + "maybe-incorrect" => Ok(Applicability::MaybeIncorrect), + "has-placeholders" => Ok(Applicability::HasPlaceholders), + "unspecified" => Ok(Applicability::Unspecified), + _ => Err(()), + } + } +} + +impl quote::ToTokens for Applicability { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.extend(match self { + Applicability::MachineApplicable => { + quote! { rustc_errors::Applicability::MachineApplicable } + } + Applicability::MaybeIncorrect => { + quote! { rustc_errors::Applicability::MaybeIncorrect } + } + Applicability::HasPlaceholders => { + quote! { rustc_errors::Applicability::HasPlaceholders } + } + Applicability::Unspecified => { + quote! { rustc_errors::Applicability::Unspecified } + } + }); + } +} diff --git a/compiler/rustc_macros/src/lib.rs b/compiler/rustc_macros/src/lib.rs index b53ef8161359a..b01e01414e878 100644 --- a/compiler/rustc_macros/src/lib.rs +++ b/compiler/rustc_macros/src/lib.rs @@ -1,5 +1,6 @@ -#![feature(proc_macro_diagnostic)] #![feature(allow_internal_unstable)] +#![feature(let_else)] +#![feature(proc_macro_diagnostic)] #![allow(rustc::default_hash_types)] #![recursion_limit = "128"] @@ -7,12 +8,12 @@ use synstructure::decl_derive; use proc_macro::TokenStream; +mod diagnostics; mod hash_stable; mod lift; mod newtype; mod query; mod serialize; -mod session_diagnostic; mod symbols; mod type_foldable; @@ -72,8 +73,24 @@ decl_derive!( skip_arg, primary_span, label, + subdiagnostic, suggestion, suggestion_short, suggestion_hidden, - suggestion_verbose)] => session_diagnostic::session_diagnostic_derive + suggestion_verbose)] => diagnostics::session_diagnostic_derive +); +decl_derive!( + [SessionSubdiagnostic, attributes( + // struct/variant attributes + label, + help, + note, + suggestion, + suggestion_short, + suggestion_hidden, + suggestion_verbose, + // field attributes + skip_arg, + primary_span, + applicability)] => diagnostics::session_subdiagnostic_derive ); diff --git a/compiler/rustc_macros/src/session_diagnostic.rs b/compiler/rustc_macros/src/session_diagnostic.rs deleted file mode 100644 index 9466d0f34bc07..0000000000000 --- a/compiler/rustc_macros/src/session_diagnostic.rs +++ /dev/null @@ -1,966 +0,0 @@ -#![deny(unused_must_use)] -use proc_macro::Diagnostic; -use quote::{format_ident, quote}; -use syn::spanned::Spanned; - -use std::collections::{BTreeSet, HashMap}; - -/// 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)] -/// #[error(code = "E0505", slug = "borrowck-move-out-of-borrow")] -/// pub struct MoveOutOfBorrowError<'tcx> { -/// pub name: Ident, -/// pub ty: Ty<'tcx>, -/// #[primary_span] -/// #[label] -/// pub span: Span, -/// #[label = "first-borrow-label"] -/// pub first_borrow_span: Span, -/// #[suggestion(code = "{name}.clone()")] -/// pub clone_sugg: Option<(Span, Applicability)> -/// } -/// ``` -/// -/// ```fluent -/// move-out-of-borrow = cannot move out of {$name} because it is borrowed -/// .label = cannot move out of borrow -/// .first-borrow-label = `{$ty}` first borrowed here -/// .suggestion = consider cloning here -/// ``` -/// -/// Then, later, to emit the error: -/// -/// ```ignore (pseudo-rust) -/// sess.emit_err(MoveOutOfBorrowError { -/// expected, -/// actual, -/// span, -/// first_borrow_span, -/// clone_sugg: Some(suggestion, Applicability::MachineApplicable), -/// }); -/// ``` -/// -/// See rustc dev guide for more examples on using the `#[derive(SessionDiagnostic)]`: -/// -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 for SessionDiagnosticDeriveError { - fn from(e: syn::Error) -> Self { - SessionDiagnosticDeriveError::SynError(e) - } -} - -#[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` (optionally performing additional decoration -/// using the `FnOnce` passed in `diag`) and 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, - 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, 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 - } - } - }) - } -} - -/// 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, -} - -/// 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<'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, - - /// 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. - code: Option, -} - -impl<'a> SessionDiagnosticDeriveBuilder<'a> { - /// 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: &syn::Attribute, - ) -> Result { - 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, syn::Meta::Path(_) | syn::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 { - syn::Meta::Path(_) => "", - syn::Meta::NameValue(_) => " = ...", - _ => unreachable!(), - } - ) - ), - }; - let id = match meta { - syn::Meta::Path(..) => quote! { #name }, - syn::Meta::NameValue(syn::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 { - 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 { - "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)?; - - let mut tokens = Vec::new(); - 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" => { - tokens.push(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(); - } - } - } - syn::Meta::NameValue(..) => { - span_err( - span, - &format!( - "`#[{}({} = ...)]` 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(); - } - } - } - - Ok(tokens.drain(..).collect()) - } - - #[must_use] - fn set_kind_once( - &mut self, - kind: SessionDiagnosticKind, - span: proc_macro::Span, - ) -> Result<(), SessionDiagnosticDeriveError> { - 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 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")); - } - } - } - - fn set_code_once(&mut self, code: String, span: proc_macro::Span) -> proc_macro2::TokenStream { - match self.code { - None => { - self.code = Some(span); - } - Some(prev_span) => { - span_err(span, "`code` specified multiple times") - .span_note(prev_span, "previously specified here") - .emit(); - } - } - - let diag = &self.diag; - quote! { #diag.code(rustc_errors::DiagnosticId::Error(#code.to_string())); } - } - - 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(); - } - } - } - - fn generate_field_attr_code( - &mut self, - attr: &syn::Attribute, - info: FieldInfo<'_>, - ) -> Result { - 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: &syn::Attribute, - info: FieldInfo<'_>, - ) -> Result { - let diag = &self.diag; - let span = attr.span().unwrap(); - 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 { - syn::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" => { - self.report_error_if_not_applied_to_span(attr, info)?; - Ok(quote! { - #diag.set_span(*#field_binding); - }) - } - "label" | "note" | "help" => { - self.report_error_if_not_applied_to_span(attr, info)?; - Ok(self.add_subdiagnostic(field_binding, name, name)) - } - other => throw_span_err!( - span, - &format!("`#[{}]` is not a valid `SessionDiagnostic` field attribute", other) - ), - }, - syn::Meta::NameValue(syn::MetaNameValue { lit: syn::Lit::Str(s), .. }) => match name { - "label" | "note" | "help" => { - self.report_error_if_not_applied_to_span(attr, info)?; - Ok(self.add_subdiagnostic(field_binding, name, &s.value())) - } - other => throw_span_err!( - span, - &format!( - "`#[{} = ...]` is not a valid `SessionDiagnostic` field attribute", - other - ) - ), - }, - syn::Meta::NameValue(_) => throw_span_err!( - span, - &format!("`#[{} = ...]` is not a valid `SessionDiagnostic` field attribute", name), - |diag| diag.help("value must be a string") - ), - syn::Meta::List(syn::MetaList { path, nested, .. }) => { - let name = path.segments.last().unwrap().ident.to_string(); - let name = name.as_ref(); - - match name { - "suggestion" | "suggestion_short" | "suggestion_hidden" - | "suggestion_verbose" => (), - other => throw_span_err!( - span, - &format!( - "`#[{}(...)]` is not a valid `SessionDiagnostic` field attribute", - other - ) - ), - }; - - let (span_, applicability) = self.span_and_applicability_of_ty(info)?; - - let mut msg = None; - let mut code = None; - - for attr in nested { - let meta = match attr { - syn::NestedMeta::Meta(meta) => meta, - syn::NestedMeta::Lit(_) => throw_span_err!( - span, - &format!( - "`#[{}(\"...\")]` is not a valid `SessionDiagnostic` field attribute", - name - ) - ), - }; - - let span = meta.span().unwrap(); - let nested_name = meta.path().segments.last().unwrap().ident.to_string(); - let nested_name = nested_name.as_str(); - - match meta { - syn::Meta::NameValue(syn::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); - } - other => throw_span_err!( - span, - &format!( - "`#[{}({} = ...)]` is not a valid `SessionDiagnostic` field attribute", - name, other - ) - ), - }, - syn::Meta::NameValue(..) => throw_span_err!( - span, - &format!( - "`#[{}({} = ...)]` is not a valid `SessionDiagnostic` struct attribute", - name, nested_name - ), - |diag| diag.help("value must be a string") - ), - syn::Meta::Path(..) => throw_span_err!( - span, - &format!( - "`#[{}({})]` is not a valid `SessionDiagnostic` struct attribute", - name, nested_name - ) - ), - syn::Meta::List(..) => throw_span_err!( - span, - &format!( - "`#[{}({}(...))]` is not a valid `SessionDiagnostic` struct attribute", - name, nested_name - ) - ), - } - } - - 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); }) - } - } - } - - /// Reports an error if the field's type is not `Span`. - fn report_error_if_not_applied_to_span( - &self, - attr: &syn::Attribute, - info: FieldInfo<'_>, - ) -> Result<(), SessionDiagnosticDeriveError> { - if !type_matches_path(&info.ty, &["rustc_span", "Span"]) { - let name = attr.path.segments.last().unwrap().ident.to_string(); - let name = name.as_str(); - let meta = attr.parse_meta()?; - - throw_span_err!( - attr.span().unwrap(), - &format!( - "the `#[{}{}]` attribute can only be applied to fields of type `Span`", - name, - match meta { - syn::Meta::Path(_) => "", - syn::Meta::NameValue(_) => " = ...", - syn::Meta::List(_) => "(...)", - } - ) - ); - } - - Ok(()) - } - - /// 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, - ) -> proc_macro2::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<(proc_macro2::TokenStream, proc_macro2::TokenStream), SessionDiagnosticDeriveError> - { - match &info.ty { - // If `ty` is `Span` w/out applicability, then use `Applicability::Unspecified`. - 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))) - } - // If `ty` is `(Span, Applicability)` then return tokens accessing those. - 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.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)`") - }), - } - } - - /// In the strings in the attributes supplied to this macro, we want callers to be able to - /// reference fields in the format string. 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: &str, span: proc_macro2::Span) -> proc_macro2::TokenStream { - // This set is used later to generate the final format string. To keep builds reproducible, - // the iteration order needs to be deterministic, hence why we use a BTreeSet here instead - // of a HashSet. - let mut referenced_fields: BTreeSet = BTreeSet::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') != '{' { - let mut eat_argument = || -> Option { - 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)`, otherwise 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 -} diff --git a/compiler/rustc_parse/src/lexer/mod.rs b/compiler/rustc_parse/src/lexer/mod.rs index e585c61bf2ee1..ee54dd44f7194 100644 --- a/compiler/rustc_parse/src/lexer/mod.rs +++ b/compiler/rustc_parse/src/lexer/mod.rs @@ -612,14 +612,14 @@ impl<'a> StringReader<'a> { err.span_suggestion_verbose( prefix_span, "use `br` for a raw byte string", - "br".to_string(), + "br", Applicability::MaybeIncorrect, ); } else if expn_data.is_root() { err.span_suggestion_verbose( prefix_span.shrink_to_hi(), "consider inserting whitespace here", - " ".into(), + " ", Applicability::MaybeIncorrect, ); } diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index bdbc25af7fa1e..13e9a5e660fe6 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -1771,7 +1771,7 @@ impl<'a> Parser<'a> { .span_suggestion( token.span, "must have an integer part", - pprust::token_to_string(token).into(), + pprust::token_to_string(token), Applicability::MachineApplicable, ) .emit(); @@ -2324,7 +2324,7 @@ impl<'a> Parser<'a> { .span_suggestion_short( span, msg, - sugg.into(), + sugg, // Has been misleading, at least in the past (closed Issue #48492). Applicability::MaybeIncorrect, ) @@ -2828,7 +2828,7 @@ impl<'a> Parser<'a> { e.span_suggestion( self.prev_token.span.shrink_to_hi(), "try adding a comma", - ",".into(), + ",", Applicability::MachineApplicable, ); } diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 746861933d2f0..10f1daf112951 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -332,7 +332,7 @@ impl<'a> Parser<'a> { err.span_suggestion_short( sp, &msg, - " struct ".into(), + " struct ", Applicability::MaybeIncorrect, // speculative ); Err(err) @@ -532,13 +532,13 @@ impl<'a> Parser<'a> { .span_suggestion( span, "add a trait here", - " Trait ".into(), + " Trait ", Applicability::HasPlaceholders, ) .span_suggestion( span.to(self.token.span), "for an inherent impl, drop this `for`", - "".into(), + "", Applicability::MaybeIncorrect, ) .emit(); @@ -1459,7 +1459,7 @@ impl<'a> Parser<'a> { err.span_suggestion( sp, "missing comma here", - ",".into(), + ",", Applicability::MachineApplicable, ); } @@ -1498,7 +1498,7 @@ impl<'a> Parser<'a> { err.span_suggestion( sp, "try adding a comma", - ",".into(), + ",", Applicability::MachineApplicable, ); err.emit(); diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index 96cca68257e7f..cd61584a87662 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -802,7 +802,7 @@ impl<'a> Parser<'a> { .span_suggestion_verbose( self.prev_token.span.shrink_to_hi().until(self.token.span), &msg, - " @ ".to_string(), + " @ ", Applicability::MaybeIncorrect, ) .emit(); @@ -818,7 +818,7 @@ impl<'a> Parser<'a> { .span_suggestion_short( sp, &format!("missing `{}`", token_str), - token_str.into(), + token_str, Applicability::MaybeIncorrect, ) .emit(); diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs index c7b929bf367dc..8019c5fb67cb9 100644 --- a/compiler/rustc_parse/src/parser/pat.rs +++ b/compiler/rustc_parse/src/parser/pat.rs @@ -606,7 +606,7 @@ impl<'a> Parser<'a> { .span_suggestion( mutref_span, "try switching the order", - "ref mut".into(), + "ref mut", Applicability::MachineApplicable, ) .emit(); diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index 291b6645d9aa6..650a0dc82ce23 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -851,7 +851,7 @@ impl<'a, 'b> BuildReducedGraphVisitor<'a, 'b> { .span_suggestion( item.span, "rename the `self` crate to be able to import it", - "extern crate self as name;".into(), + "extern crate self as name;", Applicability::HasPlaceholders, ) .emit(); diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs b/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs index 8db9da7fcb23a..a92c288cac93d 100644 --- a/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs +++ b/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs @@ -1,5 +1,6 @@ use super::FnCtxt; use crate::astconv::AstConv; +use crate::errors::{AddReturnTypeSuggestion, ExpectedReturnTypeLabel}; use rustc_ast::util::parser::ExprPrecedence; use rustc_errors::{Applicability, Diagnostic, MultiSpan}; @@ -527,28 +528,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // haven't set a return type at all (and aren't `fn main()` or an impl). match (&fn_decl.output, found.is_suggestable(self.tcx), can_suggest, expected.is_unit()) { (&hir::FnRetTy::DefaultReturn(span), true, true, true) => { - err.span_suggestion( - span, - "try adding a return type", - format!("-> {} ", found), - Applicability::MachineApplicable, - ); + err.subdiagnostic(AddReturnTypeSuggestion::Add { span, found }); true } (&hir::FnRetTy::DefaultReturn(span), false, true, true) => { // FIXME: if `found` could be `impl Iterator` or `impl Fn*`, we should suggest // that. - err.span_suggestion( - span, - "a return type might be missing here", - "-> _ ".to_string(), - Applicability::HasPlaceholders, - ); + err.subdiagnostic(AddReturnTypeSuggestion::MissingHere { span }); true } (&hir::FnRetTy::DefaultReturn(span), _, false, true) => { // `fn main()` must return `()`, do not suggest changing return type - err.span_label(span, "expected `()` because of default return type"); + err.subdiagnostic(ExpectedReturnTypeLabel::Unit { span }); true } // expectation was caused by something else, not the default return @@ -557,16 +548,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Only point to return type if the expected type is the return type, as if they // are not, the expectation must have been caused by something else. debug!("suggest_missing_return_type: return type {:?} node {:?}", ty, ty.kind); - let sp = ty.span; + let span = ty.span; let ty = >::ast_ty_to_ty(self, ty); debug!("suggest_missing_return_type: return type {:?}", ty); debug!("suggest_missing_return_type: expected type {:?}", ty); let bound_vars = self.tcx.late_bound_vars(fn_id); let ty = Binder::bind_with_vars(ty, bound_vars); - let ty = self.normalize_associated_types_in(sp, ty); + let ty = self.normalize_associated_types_in(span, ty); let ty = self.tcx.erase_late_bound_regions(ty); if self.can_coerce(expected, ty) { - err.span_label(sp, format!("expected `{}` because of return type", expected)); + err.subdiagnostic(ExpectedReturnTypeLabel::Other { span, expected }); self.try_suggest_return_impl_trait(err, expected, ty, fn_id); return true; } diff --git a/compiler/rustc_typeck/src/errors.rs b/compiler/rustc_typeck/src/errors.rs index fc08171d2f4c2..3d2f93537e4e8 100644 --- a/compiler/rustc_typeck/src/errors.rs +++ b/compiler/rustc_typeck/src/errors.rs @@ -1,6 +1,6 @@ //! Errors emitted by typeck. use rustc_errors::Applicability; -use rustc_macros::SessionDiagnostic; +use rustc_macros::{SessionDiagnostic, SessionSubdiagnostic}; use rustc_middle::ty::Ty; use rustc_span::{symbol::Ident, Span, Symbol}; @@ -190,3 +190,41 @@ pub struct AddressOfTemporaryTaken { #[label] pub span: Span, } + +#[derive(SessionSubdiagnostic)] +pub enum AddReturnTypeSuggestion<'tcx> { + #[suggestion( + slug = "typeck-add-return-type-add", + code = "-> {found} ", + applicability = "machine-applicable" + )] + Add { + #[primary_span] + span: Span, + found: Ty<'tcx>, + }, + #[suggestion( + slug = "typeck-add-return-type-missing-here", + code = "-> _ ", + applicability = "has-placeholders" + )] + MissingHere { + #[primary_span] + span: Span, + }, +} + +#[derive(SessionSubdiagnostic)] +pub enum ExpectedReturnTypeLabel<'tcx> { + #[label(slug = "typeck-expected-default-return-type")] + Unit { + #[primary_span] + span: Span, + }, + #[label(slug = "typeck-expected-return-type")] + Other { + #[primary_span] + span: Span, + expected: Ty<'tcx>, + }, +} diff --git a/src/test/ui-fulldeps/session-derive-errors.rs b/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs similarity index 82% rename from src/test/ui-fulldeps/session-derive-errors.rs rename to src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs index adec548b39053..efbf78ac87d73 100644 --- a/src/test/ui-fulldeps/session-derive-errors.rs +++ b/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs @@ -15,7 +15,7 @@ use rustc_span::symbol::Ident; use rustc_span::Span; extern crate rustc_macros; -use rustc_macros::SessionDiagnostic; +use rustc_macros::{SessionDiagnostic, SessionSubdiagnostic}; extern crate rustc_middle; use rustc_middle::ty::Ty; @@ -44,67 +44,74 @@ enum SessionDiagnosticOnEnum { #[derive(SessionDiagnostic)] #[error(code = "E0123", slug = "foo")] #[error = "E0123"] -//~^ ERROR `#[error = ...]` is not a valid `SessionDiagnostic` struct attribute +//~^ ERROR `#[error = ...]` is not a valid attribute struct WrongStructAttrStyle {} #[derive(SessionDiagnostic)] #[nonsense(code = "E0123", slug = "foo")] -//~^ ERROR `#[nonsense(...)]` is not a valid `SessionDiagnostic` struct attribute +//~^ ERROR `#[nonsense(...)]` is not a valid attribute //~^^ ERROR diagnostic kind not specified //~^^^ ERROR cannot find attribute `nonsense` in this scope struct InvalidStructAttr {} #[derive(SessionDiagnostic)] #[error("E0123")] -//~^ ERROR `#[error("...")]` is not a valid `SessionDiagnostic` struct attribute +//~^ ERROR `#[error("...")]` is not a valid attribute //~^^ ERROR `slug` not specified struct InvalidLitNestedAttr {} #[derive(SessionDiagnostic)] #[error(nonsense, code = "E0123", slug = "foo")] -//~^ ERROR `#[error(nonsense)]` is not a valid `SessionDiagnostic` struct attribute +//~^ ERROR `#[error(nonsense)]` is not a valid attribute struct InvalidNestedStructAttr {} #[derive(SessionDiagnostic)] #[error(nonsense("foo"), code = "E0123", slug = "foo")] -//~^ ERROR `#[error(nonsense(...))]` is not a valid `SessionDiagnostic` struct attribute +//~^ ERROR `#[error(nonsense(...))]` is not a valid attribute struct InvalidNestedStructAttr1 {} #[derive(SessionDiagnostic)] #[error(nonsense = "...", code = "E0123", slug = "foo")] -//~^ ERROR `#[error(nonsense = ...)]` is not a valid `SessionDiagnostic` struct attribute +//~^ ERROR `#[error(nonsense = ...)]` is not a valid attribute struct InvalidNestedStructAttr2 {} #[derive(SessionDiagnostic)] #[error(nonsense = 4, code = "E0123", slug = "foo")] -//~^ ERROR `#[error(nonsense = ...)]` is not a valid `SessionDiagnostic` struct attribute +//~^ ERROR `#[error(nonsense = ...)]` is not a valid attribute struct InvalidNestedStructAttr3 {} #[derive(SessionDiagnostic)] #[error(code = "E0123", slug = "foo")] struct WrongPlaceField { #[suggestion = "bar"] - //~^ ERROR `#[suggestion = ...]` is not a valid `SessionDiagnostic` field attribute + //~^ ERROR `#[suggestion = ...]` is not a valid attribute sp: Span, } #[derive(SessionDiagnostic)] #[error(code = "E0123", slug = "foo")] -#[error(code = "E0456", slug = "bar")] //~ ERROR `error` specified multiple times +#[error(code = "E0456", slug = "bar")] +//~^ ERROR specified multiple times +//~^^ ERROR specified multiple times +//~^^^ ERROR specified multiple times struct ErrorSpecifiedTwice {} #[derive(SessionDiagnostic)] #[error(code = "E0123", slug = "foo")] #[warning(code = "E0293", slug = "bar")] -//~^ ERROR `warning` specified when `error` was already specified +//~^ ERROR specified multiple times +//~^^ ERROR specified multiple times +//~^^^ ERROR specified multiple times struct WarnSpecifiedAfterError {} #[derive(SessionDiagnostic)] -#[error(code = "E0456", code = "E0457", slug = "bar")] //~ ERROR `code` specified multiple times +#[error(code = "E0456", code = "E0457", slug = "bar")] +//~^ ERROR specified multiple times struct CodeSpecifiedTwice {} #[derive(SessionDiagnostic)] -#[error(code = "E0456", slug = "foo", slug = "bar")] //~ ERROR `slug` specified multiple times +#[error(code = "E0456", slug = "foo", slug = "bar")] +//~^ ERROR specified multiple times struct SlugSpecifiedTwice {} #[derive(SessionDiagnostic)] @@ -130,7 +137,7 @@ struct MessageWrongType { #[error(code = "E0123", slug = "foo")] struct InvalidPathFieldAttr { #[nonsense] - //~^ ERROR `#[nonsense]` is not a valid `SessionDiagnostic` field attribute + //~^ ERROR `#[nonsense]` is not a valid attribute //~^^ ERROR cannot find attribute `nonsense` in this scope foo: String, } @@ -215,7 +222,7 @@ struct SuggestWithoutCode { #[error(code = "E0123", slug = "foo")] struct SuggestWithBadKey { #[suggestion(nonsense = "bar")] - //~^ ERROR `#[suggestion(nonsense = ...)]` is not a valid `SessionDiagnostic` field attribute + //~^ ERROR `#[suggestion(nonsense = ...)]` is not a valid attribute suggestion: (Span, Applicability), } @@ -223,7 +230,7 @@ struct SuggestWithBadKey { #[error(code = "E0123", slug = "foo")] struct SuggestWithShorthandMsg { #[suggestion(msg = "bar")] - //~^ ERROR `#[suggestion(msg = ...)]` is not a valid `SessionDiagnostic` field attribute + //~^ ERROR `#[suggestion(msg = ...)]` is not a valid attribute suggestion: (Span, Applicability), } @@ -276,7 +283,7 @@ struct SuggestWithDuplicateApplicabilityAndSpan { #[error(code = "E0123", slug = "foo")] struct WrongKindOfAnnotation { #[label("bar")] - //~^ ERROR `#[label(...)]` is not a valid `SessionDiagnostic` field attribute + //~^ ERROR `#[label(...)]` is not a valid attribute z: Span, } @@ -426,3 +433,44 @@ struct ErrorWithNoteWrongOrder { struct ErrorWithNoteCustomWrongOrder { val: String, } + +#[derive(SessionDiagnostic)] +#[error(code = "E0123", slug = "foo")] +struct ApplicabilityInBoth { + #[suggestion(message = "bar", code = "...", applicability = "maybe-incorrect")] + //~^ ERROR applicability cannot be set in both the field and attribute + suggestion: (Span, Applicability), +} + +#[derive(SessionDiagnostic)] +#[error(code = "E0123", slug = "foo")] +struct InvalidApplicability { + #[suggestion(message = "bar", code = "...", applicability = "batman")] + //~^ ERROR invalid applicability + suggestion: Span, +} + +#[derive(SessionDiagnostic)] +#[error(code = "E0123", slug = "foo")] +struct ValidApplicability { + #[suggestion(message = "bar", code = "...", applicability = "maybe-incorrect")] + suggestion: Span, +} + +#[derive(SessionDiagnostic)] +#[error(code = "E0123", slug = "foo")] +struct NoApplicability { + #[suggestion(message = "bar", code = "...")] + suggestion: Span, +} + +#[derive(SessionSubdiagnostic)] +#[note(slug = "note")] +struct Note; + +#[derive(SessionDiagnostic)] +#[error(slug = "subdiagnostic")] +struct Subdiagnostic { + #[subdiagnostic] + note: Note, +} diff --git a/src/test/ui-fulldeps/session-derive-errors.stderr b/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr similarity index 58% rename from src/test/ui-fulldeps/session-derive-errors.stderr rename to src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr index a528ae1607fba..b1738b60bc0d3 100644 --- a/src/test/ui-fulldeps/session-derive-errors.stderr +++ b/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr @@ -1,5 +1,5 @@ error: `#[derive(SessionDiagnostic)]` can only be used on structs - --> $DIR/session-derive-errors.rs:37:1 + --> $DIR/diagnostic-derive.rs:37:1 | LL | / #[error(code = "E0123", slug = "foo")] LL | | @@ -9,20 +9,22 @@ LL | | Bar, LL | | } | |_^ -error: `#[error = ...]` is not a valid `SessionDiagnostic` struct attribute - --> $DIR/session-derive-errors.rs:46:1 +error: `#[error = ...]` is not a valid attribute + --> $DIR/diagnostic-derive.rs:46:1 | LL | #[error = "E0123"] | ^^^^^^^^^^^^^^^^^^ -error: `#[nonsense(...)]` is not a valid `SessionDiagnostic` struct attribute - --> $DIR/session-derive-errors.rs:51:1 +error: `#[nonsense(...)]` is not a valid attribute + --> $DIR/diagnostic-derive.rs:51:1 | LL | #[nonsense(code = "E0123", slug = "foo")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: only `error` and `warning` are valid attributes error: diagnostic kind not specified - --> $DIR/session-derive-errors.rs:51:1 + --> $DIR/diagnostic-derive.rs:51:1 | LL | / #[nonsense(code = "E0123", slug = "foo")] LL | | @@ -33,14 +35,14 @@ LL | | struct InvalidStructAttr {} | = help: use the `#[error(...)]` attribute to create an error -error: `#[error("...")]` is not a valid `SessionDiagnostic` struct attribute - --> $DIR/session-derive-errors.rs:58:9 +error: `#[error("...")]` is not a valid attribute + --> $DIR/diagnostic-derive.rs:58:9 | LL | #[error("E0123")] | ^^^^^^^ error: `slug` not specified - --> $DIR/session-derive-errors.rs:58:1 + --> $DIR/diagnostic-derive.rs:58:1 | LL | / #[error("E0123")] LL | | @@ -50,88 +52,138 @@ LL | | struct InvalidLitNestedAttr {} | = help: use the `#[error(slug = "...")]` attribute to set this diagnostic's slug -error: `#[error(nonsense)]` is not a valid `SessionDiagnostic` struct attribute - --> $DIR/session-derive-errors.rs:64:9 +error: `#[error(nonsense)]` is not a valid attribute + --> $DIR/diagnostic-derive.rs:64:9 | LL | #[error(nonsense, code = "E0123", slug = "foo")] | ^^^^^^^^ -error: `#[error(nonsense(...))]` is not a valid `SessionDiagnostic` struct attribute - --> $DIR/session-derive-errors.rs:69:9 +error: `#[error(nonsense(...))]` is not a valid attribute + --> $DIR/diagnostic-derive.rs:69:9 | LL | #[error(nonsense("foo"), code = "E0123", slug = "foo")] | ^^^^^^^^^^^^^^^ -error: `#[error(nonsense = ...)]` is not a valid `SessionDiagnostic` struct attribute - --> $DIR/session-derive-errors.rs:74:9 +error: `#[error(nonsense = ...)]` is not a valid attribute + --> $DIR/diagnostic-derive.rs:74:9 | LL | #[error(nonsense = "...", code = "E0123", slug = "foo")] | ^^^^^^^^^^^^^^^^ + | + = help: only `slug` and `code` are valid nested attributes -error: `#[error(nonsense = ...)]` is not a valid `SessionDiagnostic` struct attribute - --> $DIR/session-derive-errors.rs:79:9 +error: `#[error(nonsense = ...)]` is not a valid attribute + --> $DIR/diagnostic-derive.rs:79:9 | LL | #[error(nonsense = 4, code = "E0123", slug = "foo")] | ^^^^^^^^^^^^ - | - = help: value must be a string -error: `#[suggestion = ...]` is not a valid `SessionDiagnostic` field attribute - --> $DIR/session-derive-errors.rs:86:5 +error: `#[suggestion = ...]` is not a valid attribute + --> $DIR/diagnostic-derive.rs:86:5 | LL | #[suggestion = "bar"] | ^^^^^^^^^^^^^^^^^^^^^ + | + = help: only `label`, `note` and `help` are valid field attributes -error: `error` specified multiple times - --> $DIR/session-derive-errors.rs:93:1 +error: specified multiple times + --> $DIR/diagnostic-derive.rs:93:1 | LL | #[error(code = "E0456", slug = "bar")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: previously specified here - --> $DIR/session-derive-errors.rs:92:1 + --> $DIR/diagnostic-derive.rs:92:1 | LL | #[error(code = "E0123", slug = "foo")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: `warning` specified when `error` was already specified - --> $DIR/session-derive-errors.rs:98:1 +error: specified multiple times + --> $DIR/diagnostic-derive.rs:93:16 + | +LL | #[error(code = "E0456", slug = "bar")] + | ^^^^^^^ + | +note: previously specified here + --> $DIR/diagnostic-derive.rs:92:16 + | +LL | #[error(code = "E0123", slug = "foo")] + | ^^^^^^^ + +error: specified multiple times + --> $DIR/diagnostic-derive.rs:93:32 + | +LL | #[error(code = "E0456", slug = "bar")] + | ^^^^^ + | +note: previously specified here + --> $DIR/diagnostic-derive.rs:92:32 + | +LL | #[error(code = "E0123", slug = "foo")] + | ^^^^^ + +error: specified multiple times + --> $DIR/diagnostic-derive.rs:101:1 | LL | #[warning(code = "E0293", slug = "bar")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: previously specified here - --> $DIR/session-derive-errors.rs:97:1 + --> $DIR/diagnostic-derive.rs:100:1 | LL | #[error(code = "E0123", slug = "foo")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: `code` specified multiple times - --> $DIR/session-derive-errors.rs:103:32 +error: specified multiple times + --> $DIR/diagnostic-derive.rs:101:18 + | +LL | #[warning(code = "E0293", slug = "bar")] + | ^^^^^^^ + | +note: previously specified here + --> $DIR/diagnostic-derive.rs:100:16 + | +LL | #[error(code = "E0123", slug = "foo")] + | ^^^^^^^ + +error: specified multiple times + --> $DIR/diagnostic-derive.rs:101:34 + | +LL | #[warning(code = "E0293", slug = "bar")] + | ^^^^^ + | +note: previously specified here + --> $DIR/diagnostic-derive.rs:100:32 + | +LL | #[error(code = "E0123", slug = "foo")] + | ^^^^^ + +error: specified multiple times + --> $DIR/diagnostic-derive.rs:108:32 | LL | #[error(code = "E0456", code = "E0457", slug = "bar")] | ^^^^^^^ | note: previously specified here - --> $DIR/session-derive-errors.rs:103:16 + --> $DIR/diagnostic-derive.rs:108:16 | LL | #[error(code = "E0456", code = "E0457", slug = "bar")] | ^^^^^^^ -error: `slug` specified multiple times - --> $DIR/session-derive-errors.rs:107:46 +error: specified multiple times + --> $DIR/diagnostic-derive.rs:113:46 | LL | #[error(code = "E0456", slug = "foo", slug = "bar")] | ^^^^^ | note: previously specified here - --> $DIR/session-derive-errors.rs:107:32 + --> $DIR/diagnostic-derive.rs:113:32 | LL | #[error(code = "E0456", slug = "foo", slug = "bar")] | ^^^^^ error: diagnostic kind not specified - --> $DIR/session-derive-errors.rs:111:1 + --> $DIR/diagnostic-derive.rs:118:1 | LL | struct KindNotProvided {} | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -139,7 +191,7 @@ LL | struct KindNotProvided {} = help: use the `#[error(...)]` attribute to create an error error: `slug` not specified - --> $DIR/session-derive-errors.rs:114:1 + --> $DIR/diagnostic-derive.rs:121:1 | LL | / #[error(code = "E0456")] LL | | struct SlugNotProvided {} @@ -148,31 +200,33 @@ LL | | struct SlugNotProvided {} = help: use the `#[error(slug = "...")]` attribute to set this diagnostic's slug error: the `#[primary_span]` attribute can only be applied to fields of type `Span` - --> $DIR/session-derive-errors.rs:124:5 + --> $DIR/diagnostic-derive.rs:131:5 | LL | #[primary_span] | ^^^^^^^^^^^^^^^ -error: `#[nonsense]` is not a valid `SessionDiagnostic` field attribute - --> $DIR/session-derive-errors.rs:132:5 +error: `#[nonsense]` is not a valid attribute + --> $DIR/diagnostic-derive.rs:139:5 | LL | #[nonsense] | ^^^^^^^^^^^ + | + = help: only `skip_arg`, `primary_span`, `label`, `note`, `help` and `subdiagnostic` are valid field attributes error: the `#[label = ...]` attribute can only be applied to fields of type `Span` - --> $DIR/session-derive-errors.rs:149:5 + --> $DIR/diagnostic-derive.rs:156:5 | LL | #[label = "bar"] | ^^^^^^^^^^^^^^^^ error: `name` doesn't refer to a field on this type - --> $DIR/session-derive-errors.rs:157:42 + --> $DIR/diagnostic-derive.rs:164:42 | LL | #[suggestion(message = "bar", code = "{name}")] | ^^^^^^^^ error: invalid format string: expected `'}'` but string was terminated - --> $DIR/session-derive-errors.rs:162:16 + --> $DIR/diagnostic-derive.rs:169:16 | LL | #[derive(SessionDiagnostic)] | - ^ expected `'}'` in format string @@ -183,7 +237,7 @@ LL | #[derive(SessionDiagnostic)] = note: this error originates in the derive macro `SessionDiagnostic` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid format string: unmatched `}` found - --> $DIR/session-derive-errors.rs:172:15 + --> $DIR/diagnostic-derive.rs:179:15 | LL | #[derive(SessionDiagnostic)] | ^ unmatched `}` in format string @@ -192,25 +246,29 @@ LL | #[derive(SessionDiagnostic)] = note: this error originates in the derive macro `SessionDiagnostic` (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:192:5 + --> $DIR/diagnostic-derive.rs:199:5 | LL | #[label = "bar"] | ^^^^^^^^^^^^^^^^ -error: `#[suggestion(nonsense = ...)]` is not a valid `SessionDiagnostic` field attribute - --> $DIR/session-derive-errors.rs:217:18 +error: `#[suggestion(nonsense = ...)]` is not a valid attribute + --> $DIR/diagnostic-derive.rs:224:18 | LL | #[suggestion(nonsense = "bar")] | ^^^^^^^^^^^^^^^^ + | + = help: only `message`, `code` and `applicability` are valid field attributes -error: `#[suggestion(msg = ...)]` is not a valid `SessionDiagnostic` field attribute - --> $DIR/session-derive-errors.rs:225:18 +error: `#[suggestion(msg = ...)]` is not a valid attribute + --> $DIR/diagnostic-derive.rs:232:18 | LL | #[suggestion(msg = "bar")] | ^^^^^^^^^^^ + | + = help: only `message`, `code` and `applicability` are valid field attributes error: wrong field type for suggestion - --> $DIR/session-derive-errors.rs:247:5 + --> $DIR/diagnostic-derive.rs:254:5 | LL | / #[suggestion(message = "bar", code = "This is suggested code")] LL | | @@ -220,7 +278,7 @@ 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:262:5 + --> $DIR/diagnostic-derive.rs:269:5 | LL | / #[suggestion(message = "bar", code = "This is suggested code")] LL | | @@ -228,57 +286,71 @@ LL | | suggestion: (Span, Span, Applicability), | |___________________________________________^ error: type of field annotated with `#[suggestion(...)]` contains more than one Applicability - --> $DIR/session-derive-errors.rs:270:5 + --> $DIR/diagnostic-derive.rs:277:5 | LL | / #[suggestion(message = "bar", code = "This is suggested code")] LL | | LL | | suggestion: (Applicability, Applicability, Span), | |____________________________________________________^ -error: `#[label(...)]` is not a valid `SessionDiagnostic` field attribute - --> $DIR/session-derive-errors.rs:278:5 +error: `#[label(...)]` is not a valid attribute + --> $DIR/diagnostic-derive.rs:285:5 | LL | #[label("bar")] | ^^^^^^^^^^^^^^^ + | + = help: only `suggestion{,_short,_hidden,_verbose}` are valid field attributes error: `#[help]` must come after `#[error(..)]` or `#[warn(..)]` - --> $DIR/session-derive-errors.rs:399:1 + --> $DIR/diagnostic-derive.rs:406:1 | LL | #[help] | ^^^^^^^ error: `#[help = ...]` must come after `#[error(..)]` or `#[warn(..)]` - --> $DIR/session-derive-errors.rs:407:1 + --> $DIR/diagnostic-derive.rs:414:1 | LL | #[help = "bar"] | ^^^^^^^^^^^^^^^ error: `#[note]` must come after `#[error(..)]` or `#[warn(..)]` - --> $DIR/session-derive-errors.rs:415:1 + --> $DIR/diagnostic-derive.rs:422:1 | LL | #[note] | ^^^^^^^ error: `#[note = ...]` must come after `#[error(..)]` or `#[warn(..)]` - --> $DIR/session-derive-errors.rs:423:1 + --> $DIR/diagnostic-derive.rs:430:1 | LL | #[note = "bar"] | ^^^^^^^^^^^^^^^ +error: applicability cannot be set in both the field and attribute + --> $DIR/diagnostic-derive.rs:440:49 + | +LL | #[suggestion(message = "bar", code = "...", applicability = "maybe-incorrect")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: invalid applicability + --> $DIR/diagnostic-derive.rs:448:49 + | +LL | #[suggestion(message = "bar", code = "...", applicability = "batman")] + | ^^^^^^^^^^^^^^^^^^^^^^^^ + error: cannot find attribute `nonsense` in this scope - --> $DIR/session-derive-errors.rs:51:3 + --> $DIR/diagnostic-derive.rs:51:3 | LL | #[nonsense(code = "E0123", slug = "foo")] | ^^^^^^^^ error: cannot find attribute `nonsense` in this scope - --> $DIR/session-derive-errors.rs:132:7 + --> $DIR/diagnostic-derive.rs:139:7 | LL | #[nonsense] | ^^^^^^^^ error[E0599]: no method named `into_diagnostic_arg` found for struct `Hello` in the current scope - --> $DIR/session-derive-errors.rs:322:10 + --> $DIR/diagnostic-derive.rs:329:10 | LL | struct Hello {} | ------------ method `into_diagnostic_arg` not found for this @@ -288,6 +360,6 @@ LL | #[derive(SessionDiagnostic)] | = note: this error originates in the derive macro `SessionDiagnostic` (in Nightly builds, run with -Z macro-backtrace for more info) -error: aborting due to 37 previous errors +error: aborting due to 43 previous errors For more information about this error, try `rustc --explain E0599`. diff --git a/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs b/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs new file mode 100644 index 0000000000000..bb406c35c0eef --- /dev/null +++ b/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs @@ -0,0 +1,501 @@ +// check-fail +// Tests error conditions for specifying subdiagnostics using #[derive(SessionSubdiagnostic)] + +// The proc_macro2 crate handles spans differently when on beta/stable release rather than nightly, +// changing the output of this test. Since SessionSubdiagnostic is strictly internal to the compiler +// the test is just ignored on stable and beta: +// ignore-beta +// ignore-stable + +#![feature(rustc_private)] +#![crate_type = "lib"] + +extern crate rustc_errors; +extern crate rustc_session; +extern crate rustc_span; +extern crate rustc_macros; + +use rustc_errors::Applicability; +use rustc_span::Span; +use rustc_macros::SessionSubdiagnostic; + +#[derive(SessionSubdiagnostic)] +#[label(slug = "label-a")] +struct A { + #[primary_span] + span: Span, + var: String, +} + +#[derive(SessionSubdiagnostic)] +enum B { + #[label(slug = "label-b-a")] + A { + #[primary_span] + span: Span, + var: String, + }, + #[label(slug = "label-b-b")] + B { + #[primary_span] + span: Span, + var: String, + } +} + +#[derive(SessionSubdiagnostic)] +#[label(slug = "label-c")] +//~^ ERROR label without `#[primary_span]` field +struct C { + var: String, +} + +#[derive(SessionSubdiagnostic)] +#[label] +//~^ ERROR `#[label]` is not a valid attribute +struct D { + #[primary_span] + span: Span, + var: String, +} + +#[derive(SessionSubdiagnostic)] +#[foo] +//~^ ERROR `#[foo]` is not a valid attribute +//~^^ ERROR cannot find attribute `foo` in this scope +struct E { + #[primary_span] + span: Span, + var: String, +} + +#[derive(SessionSubdiagnostic)] +#[label = "..."] +//~^ ERROR `#[label = ...]` is not a valid attribute +struct F { + #[primary_span] + span: Span, + var: String, +} + +#[derive(SessionSubdiagnostic)] +#[label(bug = "...")] +//~^ ERROR `#[label(bug = ...)]` is not a valid attribute +struct G { + #[primary_span] + span: Span, + var: String, +} + +#[derive(SessionSubdiagnostic)] +#[label("...")] +//~^ ERROR `#[label("...")]` is not a valid attribute +struct H { + #[primary_span] + span: Span, + var: String, +} + +#[derive(SessionSubdiagnostic)] +#[label(slug = 4)] +//~^ ERROR `#[label(slug = ...)]` is not a valid attribute +struct J { + #[primary_span] + span: Span, + var: String, +} + +#[derive(SessionSubdiagnostic)] +#[label(slug("..."))] +//~^ ERROR `#[label(slug(...))]` is not a valid attribute +struct K { + #[primary_span] + span: Span, + var: String, +} + +#[derive(SessionSubdiagnostic)] +#[label(slug)] +//~^ ERROR `#[label(slug)]` is not a valid attribute +struct L { + #[primary_span] + span: Span, + var: String, +} + +#[derive(SessionSubdiagnostic)] +#[label()] +//~^ ERROR `slug` must be set in a `#[label(...)]` attribute +struct M { + #[primary_span] + span: Span, + var: String, +} + +#[derive(SessionSubdiagnostic)] +#[label(code = "...")] +//~^ ERROR `code` is not a valid nested attribute of a `label` attribute +struct N { + #[primary_span] + span: Span, + var: String, +} + +#[derive(SessionSubdiagnostic)] +#[foo] +//~^ ERROR cannot find attribute `foo` in this scope +//~^^ ERROR unsupported type attribute for subdiagnostic enum +enum O { + #[label(slug = "...")] + A { + #[primary_span] + span: Span, + var: String, + } +} + +#[derive(SessionSubdiagnostic)] +enum P { + #[bar] +//~^ ERROR `#[bar]` is not a valid attribute +//~^^ ERROR cannot find attribute `bar` in this scope + A { + #[primary_span] + span: Span, + var: String, + } +} + +#[derive(SessionSubdiagnostic)] +enum Q { + #[bar = "..."] +//~^ ERROR `#[bar = ...]` is not a valid attribute +//~^^ ERROR cannot find attribute `bar` in this scope + A { + #[primary_span] + span: Span, + var: String, + } +} + +#[derive(SessionSubdiagnostic)] +enum R { + #[bar = 4] +//~^ ERROR `#[bar = ...]` is not a valid attribute +//~^^ ERROR cannot find attribute `bar` in this scope + A { + #[primary_span] + span: Span, + var: String, + } +} + +#[derive(SessionSubdiagnostic)] +enum S { + #[bar("...")] +//~^ ERROR `#[bar("...")]` is not a valid attribute +//~^^ ERROR cannot find attribute `bar` in this scope + A { + #[primary_span] + span: Span, + var: String, + } +} + +#[derive(SessionSubdiagnostic)] +enum T { + #[label(code = "...")] +//~^ ERROR `code` is not a valid nested attribute of a `label` + A { + #[primary_span] + span: Span, + var: String, + } +} + +#[derive(SessionSubdiagnostic)] +enum U { + #[label(slug = "label-u")] + A { + #[primary_span] + span: Span, + var: String, + }, + B { +//~^ ERROR subdiagnostic kind not specified + #[primary_span] + span: Span, + var: String, + } +} + +#[derive(SessionSubdiagnostic)] +#[label(slug = "...")] +//~^ ERROR label without `#[primary_span]` field +struct V { + #[primary_span] + //~^ ERROR the `#[primary_span]` attribute can only be applied to fields of type `Span` + span: String, +} + +#[derive(SessionSubdiagnostic)] +#[label(slug = "...")] +struct W { + #[primary_span] + span: Span, + #[applicability] + //~^ ERROR `#[applicability]` is only valid on suggestions + applicability: Applicability, +} + +#[derive(SessionSubdiagnostic)] +#[label(slug = "...")] +struct X { + #[primary_span] + span: Span, + #[bar] + //~^ ERROR `#[bar]` is not a valid attribute + //~^^ ERROR cannot find attribute `bar` in this scope + bar: String, +} + +#[derive(SessionSubdiagnostic)] +#[label(slug = "...")] +struct Y { + #[primary_span] + span: Span, + #[bar = "..."] + //~^ ERROR `#[bar = ...]` is not a valid attribute + //~^^ ERROR cannot find attribute `bar` in this scope + bar: String, +} + +#[derive(SessionSubdiagnostic)] +#[label(slug = "...")] +struct Z { + #[primary_span] + span: Span, + #[bar("...")] + //~^ ERROR `#[bar(...)]` is not a valid attribute + //~^^ ERROR cannot find attribute `bar` in this scope + bar: String, +} + +#[derive(SessionSubdiagnostic)] +#[label(slug = "label-aa")] +struct AA { + #[primary_span] + span: Span, + #[skip_arg] + z: Z +} + +#[derive(SessionSubdiagnostic)] +union AB { +//~^ ERROR unexpected unsupported untagged union + span: u32, + b: u64 +} + +#[derive(SessionSubdiagnostic)] +#[label(slug = "label-ac-1")] +//~^ NOTE previously specified here +//~^^ NOTE previously specified here +#[label(slug = "label-ac-2")] +//~^ ERROR specified multiple times +//~^^ ERROR specified multiple times +struct AC { + #[primary_span] + span: Span, +} + +#[derive(SessionSubdiagnostic)] +#[label(slug = "label-ad-1", slug = "label-ad-2")] +//~^ ERROR specified multiple times +//~^^ NOTE previously specified here +struct AD { + #[primary_span] + span: Span, +} + +#[derive(SessionSubdiagnostic)] +#[label(slug = "label-ad-1")] +struct AE { + #[primary_span] +//~^ NOTE previously specified here + span_a: Span, + #[primary_span] +//~^ ERROR specified multiple times + span_b: Span, +} + +#[derive(SessionSubdiagnostic)] +struct AF { +//~^ ERROR subdiagnostic kind not specified + #[primary_span] + span: Span, +} + +#[derive(SessionSubdiagnostic)] +#[suggestion(slug = "suggestion-af", code = "...")] +struct AG { + #[primary_span] + span: Span, + #[applicability] + applicability: Applicability, + var: String, +} + +#[derive(SessionSubdiagnostic)] +enum AH { + #[suggestion(slug = "suggestion-ag-a", code = "...")] + A { + #[primary_span] + span: Span, + #[applicability] + applicability: Applicability, + var: String, + }, + #[suggestion(slug = "suggestion-ag-b", code = "...")] + B { + #[primary_span] + span: Span, + #[applicability] + applicability: Applicability, + var: String, + } +} + +#[derive(SessionSubdiagnostic)] +#[suggestion(slug = "...", code = "...", code = "...")] +//~^ ERROR specified multiple times +//~^^ NOTE previously specified here +struct AI { + #[primary_span] + span: Span, + #[applicability] + applicability: Applicability, +} + +#[derive(SessionSubdiagnostic)] +#[suggestion(slug = "...", code = "...")] +struct AJ { + #[primary_span] + span: Span, + #[applicability] +//~^ NOTE previously specified here + applicability_a: Applicability, + #[applicability] +//~^ ERROR specified multiple times + applicability_b: Applicability, +} + +#[derive(SessionSubdiagnostic)] +#[suggestion(slug = "...", code = "...")] +//~^ ERROR suggestion without `applicability` +struct AK { + #[primary_span] + span: Span, + #[applicability] +//~^ ERROR the `#[applicability]` attribute can only be applied to fields of type `Applicability` + applicability: Span, +} + +#[derive(SessionSubdiagnostic)] +#[suggestion(slug = "...", code = "...")] +//~^ ERROR suggestion without `applicability` +struct AL { + #[primary_span] + span: Span, +} + +#[derive(SessionSubdiagnostic)] +#[suggestion(slug = "...")] +//~^ ERROR suggestion without `code = "..."` +struct AM { + #[primary_span] + span: Span, + #[applicability] + applicability: Applicability, +} + +#[derive(SessionSubdiagnostic)] +#[suggestion(slug = "...", code ="...", applicability = "foo")] +//~^ ERROR invalid applicability +struct AN { + #[primary_span] + span: Span, +} + +#[derive(SessionSubdiagnostic)] +#[help(slug = "label-am")] +struct AO { + var: String +} + +#[derive(SessionSubdiagnostic)] +#[note(slug = "label-an")] +struct AP; + +#[derive(SessionSubdiagnostic)] +#[suggestion(slug = "...", code = "...")] +//~^ ERROR suggestion without `applicability` +//~^^ ERROR suggestion without `#[primary_span]` field +struct AQ { + var: String, +} + +#[derive(SessionSubdiagnostic)] +#[suggestion(slug = "...", code ="...", applicability = "machine-applicable")] +struct AR { + #[primary_span] + span: Span, +} + +#[derive(SessionSubdiagnostic)] +#[label] +//~^ ERROR unsupported type attribute for subdiagnostic enum +enum AS { + #[label(slug = "...")] + A { + #[primary_span] + span: Span, + var: String, + } +} + +#[derive(SessionSubdiagnostic)] +#[suggestion(slug = "...", code ="{var}", applicability = "machine-applicable")] +struct AT { + #[primary_span] + span: Span, + var: String, +} + +#[derive(SessionSubdiagnostic)] +#[suggestion(slug = "...", code ="{var}", applicability = "machine-applicable")] +//~^ ERROR `var` doesn't refer to a field on this type +struct AU { + #[primary_span] + span: Span, +} + +#[derive(SessionSubdiagnostic)] +enum AV { + #[suggestion(slug = "...", code ="{var}", applicability = "machine-applicable")] + A { + #[primary_span] + span: Span, + var: String, + } +} + +#[derive(SessionSubdiagnostic)] +enum AW { + #[suggestion(slug = "...", code ="{var}", applicability = "machine-applicable")] +//~^ ERROR `var` doesn't refer to a field on this type + A { + #[primary_span] + span: Span, + } +} diff --git a/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.stderr b/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.stderr new file mode 100644 index 0000000000000..4984cc4b3186c --- /dev/null +++ b/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.stderr @@ -0,0 +1,389 @@ +error: label without `#[primary_span]` field + --> $DIR/subdiagnostic-derive.rs:47:1 + | +LL | / #[label(slug = "label-c")] +LL | | +LL | | struct C { +LL | | var: String, +LL | | } + | |_^ + +error: `#[label]` is not a valid attribute + --> $DIR/subdiagnostic-derive.rs:54:1 + | +LL | #[label] + | ^^^^^^^^ + +error: `#[foo]` is not a valid attribute + --> $DIR/subdiagnostic-derive.rs:63:1 + | +LL | #[foo] + | ^^^^^^ + +error: `#[label = ...]` is not a valid attribute + --> $DIR/subdiagnostic-derive.rs:73:1 + | +LL | #[label = "..."] + | ^^^^^^^^^^^^^^^^ + +error: `#[label(bug = ...)]` is not a valid attribute + --> $DIR/subdiagnostic-derive.rs:82:9 + | +LL | #[label(bug = "...")] + | ^^^^^^^^^^^ + | + = help: only `code`, `slug` and `applicability` are valid nested attributes + +error: `#[label("...")]` is not a valid attribute + --> $DIR/subdiagnostic-derive.rs:91:9 + | +LL | #[label("...")] + | ^^^^^ + +error: `#[label(slug = ...)]` is not a valid attribute + --> $DIR/subdiagnostic-derive.rs:100:9 + | +LL | #[label(slug = 4)] + | ^^^^^^^^ + +error: `#[label(slug(...))]` is not a valid attribute + --> $DIR/subdiagnostic-derive.rs:109:9 + | +LL | #[label(slug("..."))] + | ^^^^^^^^^^^ + +error: `#[label(slug)]` is not a valid attribute + --> $DIR/subdiagnostic-derive.rs:118:9 + | +LL | #[label(slug)] + | ^^^^ + +error: `slug` must be set in a `#[label(...)]` attribute + --> $DIR/subdiagnostic-derive.rs:127:1 + | +LL | #[label()] + | ^^^^^^^^^^ + +error: `code` is not a valid nested attribute of a `label` attribute + --> $DIR/subdiagnostic-derive.rs:136:1 + | +LL | #[label(code = "...")] + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: unsupported type attribute for subdiagnostic enum + --> $DIR/subdiagnostic-derive.rs:145:1 + | +LL | #[foo] + | ^^^^^^ + +error: `#[bar]` is not a valid attribute + --> $DIR/subdiagnostic-derive.rs:159:5 + | +LL | #[bar] + | ^^^^^^ + +error: `#[bar = ...]` is not a valid attribute + --> $DIR/subdiagnostic-derive.rs:171:5 + | +LL | #[bar = "..."] + | ^^^^^^^^^^^^^^ + +error: `#[bar = ...]` is not a valid attribute + --> $DIR/subdiagnostic-derive.rs:183:5 + | +LL | #[bar = 4] + | ^^^^^^^^^^ + +error: `#[bar("...")]` is not a valid attribute + --> $DIR/subdiagnostic-derive.rs:195:11 + | +LL | #[bar("...")] + | ^^^^^ + +error: `code` is not a valid nested attribute of a `label` attribute + --> $DIR/subdiagnostic-derive.rs:207:5 + | +LL | #[label(code = "...")] + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: subdiagnostic kind not specified + --> $DIR/subdiagnostic-derive.rs:224:5 + | +LL | B { + | ^ + +error: the `#[primary_span]` attribute can only be applied to fields of type `Span` + --> $DIR/subdiagnostic-derive.rs:236:5 + | +LL | #[primary_span] + | ^^^^^^^^^^^^^^^ + +error: label without `#[primary_span]` field + --> $DIR/subdiagnostic-derive.rs:233:1 + | +LL | / #[label(slug = "...")] +LL | | +LL | | struct V { +LL | | #[primary_span] +LL | | +LL | | span: String, +LL | | } + | |_^ + +error: `#[applicability]` is only valid on suggestions + --> $DIR/subdiagnostic-derive.rs:246:5 + | +LL | #[applicability] + | ^^^^^^^^^^^^^^^^ + +error: `#[bar]` is not a valid attribute + --> $DIR/subdiagnostic-derive.rs:256:5 + | +LL | #[bar] + | ^^^^^^ + | + = help: only `primary_span`, `applicability` and `skip_arg` are valid field attributes + +error: `#[bar = ...]` is not a valid attribute + --> $DIR/subdiagnostic-derive.rs:267:5 + | +LL | #[bar = "..."] + | ^^^^^^^^^^^^^^ + +error: `#[bar(...)]` is not a valid attribute + --> $DIR/subdiagnostic-derive.rs:278:5 + | +LL | #[bar("...")] + | ^^^^^^^^^^^^^ + +error: unexpected unsupported untagged union + --> $DIR/subdiagnostic-derive.rs:294:1 + | +LL | / union AB { +LL | | +LL | | span: u32, +LL | | b: u64 +LL | | } + | |_^ + +error: specified multiple times + --> $DIR/subdiagnostic-derive.rs:304:9 + | +LL | #[label(slug = "label-ac-2")] + | ^^^^^^^^^^^^^^^^^^^ + | +note: previously specified here + --> $DIR/subdiagnostic-derive.rs:301:9 + | +LL | #[label(slug = "label-ac-1")] + | ^^^^^^^^^^^^^^^^^^^ + +error: specified multiple times + --> $DIR/subdiagnostic-derive.rs:304:1 + | +LL | #[label(slug = "label-ac-2")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: previously specified here + --> $DIR/subdiagnostic-derive.rs:301:1 + | +LL | #[label(slug = "label-ac-1")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: specified multiple times + --> $DIR/subdiagnostic-derive.rs:313:30 + | +LL | #[label(slug = "label-ad-1", slug = "label-ad-2")] + | ^^^^^^^^^^^^^^^^^^^ + | +note: previously specified here + --> $DIR/subdiagnostic-derive.rs:313:9 + | +LL | #[label(slug = "label-ad-1", slug = "label-ad-2")] + | ^^^^^^^^^^^^^^^^^^^ + +error: specified multiple times + --> $DIR/subdiagnostic-derive.rs:327:5 + | +LL | #[primary_span] + | ^^^^^^^^^^^^^^^ + | +note: previously specified here + --> $DIR/subdiagnostic-derive.rs:324:5 + | +LL | #[primary_span] + | ^^^^^^^^^^^^^^^ + +error: subdiagnostic kind not specified + --> $DIR/subdiagnostic-derive.rs:333:8 + | +LL | struct AF { + | ^^ + +error: specified multiple times + --> $DIR/subdiagnostic-derive.rs:370:42 + | +LL | #[suggestion(slug = "...", code = "...", code = "...")] + | ^^^^^^^^^^^^ + | +note: previously specified here + --> $DIR/subdiagnostic-derive.rs:370:28 + | +LL | #[suggestion(slug = "...", code = "...", code = "...")] + | ^^^^^^^^^^^^ + +error: specified multiple times + --> $DIR/subdiagnostic-derive.rs:388:5 + | +LL | #[applicability] + | ^^^^^^^^^^^^^^^^ + | +note: previously specified here + --> $DIR/subdiagnostic-derive.rs:385:5 + | +LL | #[applicability] + | ^^^^^^^^^^^^^^^^ + +error: the `#[applicability]` attribute can only be applied to fields of type `Applicability` + --> $DIR/subdiagnostic-derive.rs:399:5 + | +LL | #[applicability] + | ^^^^^^^^^^^^^^^^ + +error: suggestion without `applicability` + --> $DIR/subdiagnostic-derive.rs:394:1 + | +LL | / #[suggestion(slug = "...", code = "...")] +LL | | +LL | | struct AK { +LL | | #[primary_span] +... | +LL | | applicability: Span, +LL | | } + | |_^ + +error: suggestion without `applicability` + --> $DIR/subdiagnostic-derive.rs:405:1 + | +LL | / #[suggestion(slug = "...", code = "...")] +LL | | +LL | | struct AL { +LL | | #[primary_span] +LL | | span: Span, +LL | | } + | |_^ + +error: suggestion without `code = "..."` + --> $DIR/subdiagnostic-derive.rs:413:1 + | +LL | / #[suggestion(slug = "...")] +LL | | +LL | | struct AM { +LL | | #[primary_span] +... | +LL | | applicability: Applicability, +LL | | } + | |_^ + +error: invalid applicability + --> $DIR/subdiagnostic-derive.rs:423:41 + | +LL | #[suggestion(slug = "...", code ="...", applicability = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^ + +error: suggestion without `applicability` + --> $DIR/subdiagnostic-derive.rs:441:1 + | +LL | / #[suggestion(slug = "...", code = "...")] +LL | | +LL | | +LL | | struct AQ { +LL | | var: String, +LL | | } + | |_^ + +error: suggestion without `#[primary_span]` field + --> $DIR/subdiagnostic-derive.rs:441:1 + | +LL | / #[suggestion(slug = "...", code = "...")] +LL | | +LL | | +LL | | struct AQ { +LL | | var: String, +LL | | } + | |_^ + +error: unsupported type attribute for subdiagnostic enum + --> $DIR/subdiagnostic-derive.rs:456:1 + | +LL | #[label] + | ^^^^^^^^ + +error: `var` doesn't refer to a field on this type + --> $DIR/subdiagnostic-derive.rs:476:34 + | +LL | #[suggestion(slug = "...", code ="{var}", applicability = "machine-applicable")] + | ^^^^^^^ + +error: `var` doesn't refer to a field on this type + --> $DIR/subdiagnostic-derive.rs:495:38 + | +LL | #[suggestion(slug = "...", code ="{var}", applicability = "machine-applicable")] + | ^^^^^^^ + +error: cannot find attribute `foo` in this scope + --> $DIR/subdiagnostic-derive.rs:63:3 + | +LL | #[foo] + | ^^^ + +error: cannot find attribute `foo` in this scope + --> $DIR/subdiagnostic-derive.rs:145:3 + | +LL | #[foo] + | ^^^ + +error: cannot find attribute `bar` in this scope + --> $DIR/subdiagnostic-derive.rs:159:7 + | +LL | #[bar] + | ^^^ + +error: cannot find attribute `bar` in this scope + --> $DIR/subdiagnostic-derive.rs:171:7 + | +LL | #[bar = "..."] + | ^^^ + +error: cannot find attribute `bar` in this scope + --> $DIR/subdiagnostic-derive.rs:183:7 + | +LL | #[bar = 4] + | ^^^ + +error: cannot find attribute `bar` in this scope + --> $DIR/subdiagnostic-derive.rs:195:7 + | +LL | #[bar("...")] + | ^^^ + +error: cannot find attribute `bar` in this scope + --> $DIR/subdiagnostic-derive.rs:256:7 + | +LL | #[bar] + | ^^^ + +error: cannot find attribute `bar` in this scope + --> $DIR/subdiagnostic-derive.rs:267:7 + | +LL | #[bar = "..."] + | ^^^ + +error: cannot find attribute `bar` in this scope + --> $DIR/subdiagnostic-derive.rs:278:7 + | +LL | #[bar("...")] + | ^^^ + +error: aborting due to 51 previous errors + diff --git a/src/tools/clippy/clippy_lints/src/functions/must_use.rs b/src/tools/clippy/clippy_lints/src/functions/must_use.rs index 0709580c8adfd..5462d913fb441 100644 --- a/src/tools/clippy/clippy_lints/src/functions/must_use.rs +++ b/src/tools/clippy/clippy_lints/src/functions/must_use.rs @@ -108,7 +108,7 @@ fn check_needless_must_use( diag.span_suggestion( attr.span, "remove the attribute", - "".into(), + "", Applicability::MachineApplicable, ); }, diff --git a/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs b/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs index 9c734221ebcea..4034079a90c0d 100644 --- a/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs +++ b/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs @@ -241,7 +241,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { |x| Cow::from(format!("change `{}` to", x)), ) .as_ref(), - suggestion.into(), + suggestion, Applicability::Unspecified, ); } @@ -271,7 +271,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { |x| Cow::from(format!("change `{}` to", x)) ) .as_ref(), - suggestion.into(), + suggestion, Applicability::Unspecified, ); }