From 49ec909ca7d649d73115f7e0e894b0ffb0740b66 Mon Sep 17 00:00:00 2001 From: David Wood Date: Tue, 26 Apr 2022 11:59:45 +0100 Subject: [PATCH] macros: subdiagnostic derive Add a new derive, `#[derive(SessionSubdiagnostic)]`, which enables deriving structs for labels, notes, helps and suggestions. Signed-off-by: David Wood --- .../locales/en-US/typeck.ftl | 8 + compiler/rustc_errors/src/diagnostic.rs | 14 + .../rustc_errors/src/diagnostic_builder.rs | 5 + compiler/rustc_errors/src/lib.rs | 4 +- compiler/rustc_macros/src/lib.rs | 18 +- .../rustc_macros/src/session_diagnostic.rs | 886 +++++++++++++++--- .../src/check/fn_ctxt/suggestions.rs | 23 +- compiler/rustc_typeck/src/errors.rs | 40 +- .../subdiagnostic-derive.rs | 501 ++++++++++ .../subdiagnostic-derive.stderr | 387 ++++++++ 10 files changed, 1735 insertions(+), 151 deletions(-) create mode 100644 src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs create mode 100644 src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.stderr diff --git a/compiler/rustc_error_messages/locales/en-US/typeck.ftl b/compiler/rustc_error_messages/locales/en-US/typeck.ftl index 721201d9312..6a3235fc772 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 39cb71848eb..83e6a751394 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 { @@ -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 efc7693459f..96b730c2baa 100644 --- a/compiler/rustc_errors/src/diagnostic_builder.rs +++ b/compiler/rustc_errors/src/diagnostic_builder.rs @@ -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 a64133bb7f4..df41fc00714 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_macros/src/lib.rs b/compiler/rustc_macros/src/lib.rs index b53ef816135..8026288202e 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"] @@ -77,3 +78,18 @@ decl_derive!( suggestion_hidden, suggestion_verbose)] => session_diagnostic::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)] => session_diagnostic::session_subdiagnostic_derive +); diff --git a/compiler/rustc_macros/src/session_diagnostic.rs b/compiler/rustc_macros/src/session_diagnostic.rs index 9466d0f34bc..27c94f3e306 100644 --- a/compiler/rustc_macros/src/session_diagnostic.rs +++ b/compiler/rustc_macros/src/session_diagnostic.rs @@ -1,9 +1,12 @@ #![deny(unused_must_use)] -use proc_macro::Diagnostic; +use proc_macro::{Diagnostic, Level, MultiSpan}; +use proc_macro2::TokenStream; use quote::{format_ident, quote}; -use syn::spanned::Spanned; - use std::collections::{BTreeSet, HashMap}; +use std::fmt; +use std::str::FromStr; +use syn::{spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, Type, Visibility}; +use synstructure::{BindingInfo, Structure, VariantInfo}; /// Implements `#[derive(SessionDiagnostic)]`, which allows for errors to be specified as a struct, /// independent from the actual diagnostics emitting code. @@ -51,7 +54,7 @@ use std::collections::{BTreeSet, HashMap}; /// /// See rustc dev guide for more examples on using the `#[derive(SessionDiagnostic)]`: /// -pub fn session_diagnostic_derive(s: synstructure::Structure<'_>) -> proc_macro2::TokenStream { +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"); @@ -59,12 +62,62 @@ pub fn session_diagnostic_derive(s: synstructure::Structure<'_>) -> proc_macro2: 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() +} + /// 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 { +fn type_matches_path(ty: &Type, name: &[&str]) -> bool { + if let Type::Path(ty) = ty { ty.path .segments .iter() @@ -79,8 +132,8 @@ fn type_matches_path(ty: &syn::Type, name: &[&str]) -> bool { /// The central struct for constructing the `as_error` method from an annotated struct. struct SessionDiagnosticDerive<'a> { - structure: synstructure::Structure<'a>, - builder: SessionDiagnosticDeriveBuilder<'a>, + structure: Structure<'a>, + builder: SessionDiagnosticDeriveBuilder, } impl std::convert::From for SessionDiagnosticDeriveError { @@ -96,7 +149,7 @@ enum SessionDiagnosticDeriveError { } impl SessionDiagnosticDeriveError { - fn to_compile_error(self) -> proc_macro2::TokenStream { + fn to_compile_error(self) -> TokenStream { match self { SessionDiagnosticDeriveError::SynError(e) => e.to_compile_error(), SessionDiagnosticDeriveError::ErrorHandled => { @@ -110,8 +163,8 @@ impl SessionDiagnosticDeriveError { } } -fn span_err(span: impl proc_macro::MultiSpan, msg: &str) -> proc_macro::Diagnostic { - Diagnostic::spanned(span, proc_macro::Level::Error, msg) +fn span_err(span: impl MultiSpan, msg: &str) -> Diagnostic { + Diagnostic::spanned(span, Level::Error, msg) } /// For methods that return a `Result<_, SessionDiagnosticDeriveError>`: @@ -128,9 +181,9 @@ macro_rules! throw_span_err { /// 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, + span: impl MultiSpan, msg: &str, - f: impl FnOnce(proc_macro::Diagnostic) -> proc_macro::Diagnostic, + f: impl FnOnce(Diagnostic) -> Diagnostic, ) -> SessionDiagnosticDeriveError { let diag = span_err(span, msg); f(diag).emit(); @@ -138,7 +191,7 @@ fn _throw_span_err( } impl<'a> SessionDiagnosticDerive<'a> { - fn new(diag: syn::Ident, sess: syn::Ident, structure: synstructure::Structure<'a>) -> Self { + 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(); @@ -149,7 +202,7 @@ impl<'a> SessionDiagnosticDerive<'a> { 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); + fields_map.insert(ident.to_string(), quote! { &self.#ident }); } } } @@ -167,7 +220,7 @@ impl<'a> SessionDiagnosticDerive<'a> { } } - fn into_tokens(self) -> proc_macro2::TokenStream { + fn into_tokens(self) -> TokenStream { let SessionDiagnosticDerive { mut structure, mut builder } = self; let ast = structure.ast(); @@ -321,9 +374,9 @@ impl<'a> SessionDiagnosticDerive<'a> { /// 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, + vis: &'a Visibility, + binding: &'a BindingInfo<'a>, + ty: &'a Type, span: &'a proc_macro2::Span, } @@ -350,7 +403,7 @@ impl SessionDiagnosticKind { /// 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> { +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. @@ -358,7 +411,7 @@ struct SessionDiagnosticDeriveBuilder<'a> { /// Store a map of field name to its corresponding field. This is built on construction of the /// derive builder. - fields: HashMap, + fields: HashMap, /// Kind of diagnostic requested via the struct attribute. kind: Option<(SessionDiagnosticKind, proc_macro::Span)>, @@ -370,23 +423,21 @@ struct SessionDiagnosticDeriveBuilder<'a> { code: Option, } -impl<'a> SessionDiagnosticDeriveBuilder<'a> { +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: &syn::Attribute, - ) -> Result { + 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, syn::Meta::Path(_) | syn::Meta::NameValue(_)) - { + 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(), @@ -396,16 +447,16 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> { "`#[{}{}]` must come after `#[error(..)]` or `#[warn(..)]`", name, match meta { - syn::Meta::Path(_) => "", - syn::Meta::NameValue(_) => " = ...", + Meta::Path(_) => "", + Meta::NameValue(_) => " = ...", _ => unreachable!(), } ) ), }; let id = match meta { - syn::Meta::Path(..) => quote! { #name }, - syn::Meta::NameValue(syn::MetaNameValue { lit: syn::Lit::Str(s), .. }) => { + Meta::Path(..) => quote! { #name }, + Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => { quote! { #s } } _ => unreachable!(), @@ -418,12 +469,12 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> { } let nested = match meta { - syn::Meta::List(syn::MetaList { nested, .. }) => nested, - syn::Meta::Path(..) => throw_span_err!( + Meta::List(MetaList { nested, .. }) => nested, + Meta::Path(..) => throw_span_err!( span, &format!("`#[{}]` is not a valid `SessionDiagnostic` struct attribute", name) ), - syn::Meta::NameValue(..) => throw_span_err!( + Meta::NameValue(..) => throw_span_err!( span, &format!("`#[{} = ...]` is not a valid `SessionDiagnostic` struct attribute", name) ), @@ -458,7 +509,7 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> { 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), .. }) => { + Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => { match nested_name.as_str() { "slug" => { self.set_slug_once(s.value(), s.span().unwrap()); @@ -478,7 +529,7 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> { } } } - syn::Meta::NameValue(..) => { + Meta::NameValue(..) => { span_err( span, &format!( @@ -489,7 +540,7 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> { .help("value must be a string") .emit(); } - syn::Meta::Path(..) => { + Meta::Path(..) => { span_err( span, &format!( @@ -499,7 +550,7 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> { ) .emit(); } - syn::Meta::List(..) => { + Meta::List(..) => { span_err( span, &format!( @@ -541,7 +592,7 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> { } } - fn set_code_once(&mut self, code: String, span: proc_macro::Span) -> proc_macro2::TokenStream { + fn set_code_once(&mut self, code: String, span: proc_macro::Span) -> TokenStream { match self.code { None => { self.code = Some(span); @@ -574,7 +625,7 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> { &mut self, attr: &syn::Attribute, info: FieldInfo<'_>, - ) -> Result { + ) -> Result { let field_binding = &info.binding.binding; let option_ty = option_inner_ty(&info.ty); let generated_code = self.generate_non_option_field_code( @@ -600,9 +651,9 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> { fn generate_non_option_field_code( &mut self, - attr: &syn::Attribute, + attr: &Attribute, info: FieldInfo<'_>, - ) -> Result { + ) -> Result { let diag = &self.diag; let span = attr.span().unwrap(); let field_binding = &info.binding.binding; @@ -612,20 +663,20 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> { let meta = attr.parse_meta()?; match meta { - syn::Meta::Path(_) => match name { + 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)?; + 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)?; + report_error_if_not_applied_to_span(attr, &info)?; Ok(self.add_subdiagnostic(field_binding, name, name)) } other => throw_span_err!( @@ -633,9 +684,9 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> { &format!("`#[{}]` is not a valid `SessionDiagnostic` field attribute", other) ), }, - syn::Meta::NameValue(syn::MetaNameValue { lit: syn::Lit::Str(s), .. }) => match name { + Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => match name { "label" | "note" | "help" => { - self.report_error_if_not_applied_to_span(attr, info)?; + report_error_if_not_applied_to_span(attr, &info)?; Ok(self.add_subdiagnostic(field_binding, name, &s.value())) } other => throw_span_err!( @@ -646,12 +697,12 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> { ) ), }, - syn::Meta::NameValue(_) => throw_span_err!( + 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, .. }) => { + Meta::List(MetaList { path, nested, .. }) => { let name = path.segments.last().unwrap().ident.to_string(); let name = name.as_ref(); @@ -689,25 +740,25 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> { 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()); + Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => { + match nested_name { + "message" => { + msg = Some(s.value()); + } + "code" => { + let formatted_str = self.build_format(&s.value(), s.span()); + code = Some(formatted_str); + } + other => throw_span_err!( + span, + &format!( + "`#[{}({} = ...)]` is not a valid `SessionDiagnostic` field attribute", + name, other + ) + ), } - "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!( + } + Meta::NameValue(..) => throw_span_err!( span, &format!( "`#[{}({} = ...)]` is not a valid `SessionDiagnostic` struct attribute", @@ -715,14 +766,14 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> { ), |diag| diag.help("value must be a string") ), - syn::Meta::Path(..) => throw_span_err!( + Meta::Path(..) => throw_span_err!( span, &format!( "`#[{}({})]` is not a valid `SessionDiagnostic` struct attribute", name, nested_name ) ), - syn::Meta::List(..) => throw_span_err!( + Meta::List(..) => throw_span_err!( span, &format!( "`#[{}({}(...))]` is not a valid `SessionDiagnostic` struct attribute", @@ -748,34 +799,6 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> { } } - /// 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( @@ -783,7 +806,7 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> { field_binding: &proc_macro2::Ident, kind: &str, fluent_attr_identifier: &str, - ) -> proc_macro2::TokenStream { + ) -> TokenStream { let diag = &self.diag; let slug = @@ -800,16 +823,15 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> { fn span_and_applicability_of_ty( &self, info: FieldInfo<'_>, - ) -> Result<(proc_macro2::TokenStream, proc_macro2::TokenStream), SessionDiagnosticDeriveError> - { + ) -> Result<(TokenStream, 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"]) => { + ty @ Type::Path(..) if type_matches_path(ty, &["rustc_span", "Span"]) => { let binding = &info.binding.binding; Ok((quote!(*#binding), quote!(rustc_errors::Applicability::Unspecified))) } // If `ty` is `(Span, Applicability)` then return tokens accessing those. - syn::Type::Tuple(tup) => { + Type::Tuple(tup) => { let mut span_idx = None; let mut applicability_idx = None; @@ -855,43 +877,53 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> { }), } } +} + +trait HasFieldMap { + 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) - /// struct Point { - /// #[error = "Expected a point greater than ({x}, {y})"] - /// x: i32, - /// y: i32, + /// /// 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 `{x}` refers `self.x` and `{y}` refers to `self.y`, - /// then generate this call to `format!`: + /// 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!("Expected a point greater than ({x}, {y})", x = self.x, y = self.y) + /// 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) -> proc_macro2::TokenStream { + 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. + // 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. + // 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 ] '}' . + // 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); @@ -916,25 +948,25 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> { } } } + // 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 { + let value = match self.get_field_binding(&field) { + Some(value) => value.clone(), // 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}" + None => { + Diagnostic::spanned( + span.unwrap(), + Level::Error, + format!("`{}` doesn't refer to a field on this type", field), + ) + .emit(); + quote! { + "{#field}" + } } }; quote! { @@ -947,10 +979,67 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> { } } +impl HasFieldMap for SessionDiagnosticDeriveBuilder { + fn get_field_binding(&self, field: &String) -> Option<&TokenStream> { + self.fields.get(field) + } +} + +/// 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`. +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`. +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`. -fn option_inner_ty(ty: &syn::Type) -> Option<&syn::Type> { +fn option_inner_ty(ty: &Type) -> Option<&Type> { if type_matches_path(ty, &["std", "option", "Option"]) { - if let syn::Type::Path(ty_path) = ty { + 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 { @@ -964,3 +1053,538 @@ fn option_inner_ty(ty: &syn::Type) -> Option<&syn::Type> { } None } + +trait SetOnce { + fn set_once(&mut self, value: T); +} + +impl SetOnce<(T, proc_macro::Span)> for Option<(T, proc_macro::Span)> { + fn set_once(&mut self, (value, span): (T, proc_macro::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(); + } + } + } +} + +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 } + } + }); + } +} + +#[derive(Clone, Copy)] +enum SubdiagnosticSuggestionKind { + /// `#[suggestion]` + Normal, + /// `#[suggestion_short]` + Short, + /// `#[suggestion_hidden]` + Hidden, + /// `#[suggestion_verbose]` + Verbose, +} + +#[derive(Clone, Copy)] +enum SubdiagnosticKind { + /// `#[label]` or `#[label(...)]` + Label, + /// `#[note]` or `#[note(...)]` + Note, + /// `#[help]` or `#[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 + } +} + +struct SessionSubdiagnosticDerive<'a> { + structure: Structure<'a>, + diag: syn::Ident, +} + +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<(proc_macro2::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<(proc_macro2::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::Path(_) => throw_span_err!( + span, + &format!("`#[{}]` is not a valid `SessionSubdiagnostic` attribute", name) + ), + Meta::NameValue(_) => throw_span_err!( + span, + &format!("`#[{} = ...]` is not a valid `SessionSubdiagnostic` attribute", name) + ), + Meta::List(MetaList { nested, .. }) => { + 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 `SessionSubdiagnostic` 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(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)); + } + other => throw_span_err!( + span, + &format!( + "`#[{}({} = ...)]` is not a valid `SessionSubdiagnostic` attribute", + name, other + ) + ), + } + } + Meta::NameValue(..) => throw_span_err!( + span, + &format!( + "`#[{}({} = ...)]` is not a valid `SessionSubdiagnostic` attribute", + name, nested_name + ), + |diag| diag.help("value must be a string") + ), + Meta::Path(..) => throw_span_err!( + span, + &format!( + "`#[{}({})]` is not a valid `SessionSubdiagnostic` attribute", + name, nested_name + ) + ), + Meta::List(..) => throw_span_err!( + span, + &format!( + "`#[{}({}(...))]` is not a valid `SessionSubdiagnostic` attribute", + name, nested_name + ) + ), + } + } + + let Ok(kind) = SubdiagnosticKind::from_str(name) else { + throw_span_err!( + span, + &format!( + "`#[{}(...)]` is not a valid `SessionSubdiagnostic` attribute", + name + ) + ); + }; + kind + } + }; + + 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! {}); + } + other => span_err( + span, + &format!( + "`#[{}]` is not a valid `SessionSubdiagnostic` field attribute", + other + ), + ) + .emit(), + }, + Meta::NameValue(_) => span_err( + span, + &format!( + "`#[{} = ...]` is not a valid `SessionSubdiagnostic` field attribute", + name + ), + ) + .emit(), + Meta::List(_) => span_err( + span, + &format!( + "`#[{}(...)]` is not a valid `SessionSubdiagnostic` field attribute", + name + ), + ) + .emit(), + } + } + + 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 + }) + } +} + +impl<'a> SessionSubdiagnosticDerive<'a> { + fn new(structure: Structure<'a>) -> Self { + let diag = format_ident!("diag"); + Self { structure, diag } + } + + 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 + } +} diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs b/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs index 8db9da7fcb2..a92c288cac9 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 fc08171d2f4..3d2f93537e4 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-diagnostic/subdiagnostic-derive.rs b/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs new file mode 100644 index 00000000000..a587bd2cbb2 --- /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 `SessionSubdiagnostic` attribute +struct D { + #[primary_span] + span: Span, + var: String, +} + +#[derive(SessionSubdiagnostic)] +#[foo] +//~^ ERROR `#[foo]` is not a valid `SessionSubdiagnostic` 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 `SessionSubdiagnostic` attribute +struct F { + #[primary_span] + span: Span, + var: String, +} + +#[derive(SessionSubdiagnostic)] +#[label(bug = "...")] +//~^ ERROR `#[label(bug = ...)]` is not a valid `SessionSubdiagnostic` attribute +struct G { + #[primary_span] + span: Span, + var: String, +} + +#[derive(SessionSubdiagnostic)] +#[label("...")] +//~^ ERROR `#[label("...")]` is not a valid `SessionSubdiagnostic` attribute +struct H { + #[primary_span] + span: Span, + var: String, +} + +#[derive(SessionSubdiagnostic)] +#[label(slug = 4)] +//~^ ERROR `#[label(slug = ...)]` is not a valid `SessionSubdiagnostic` attribute +struct J { + #[primary_span] + span: Span, + var: String, +} + +#[derive(SessionSubdiagnostic)] +#[label(slug("..."))] +//~^ ERROR `#[label(slug(...))]` is not a valid `SessionSubdiagnostic` attribute +struct K { + #[primary_span] + span: Span, + var: String, +} + +#[derive(SessionSubdiagnostic)] +#[label(slug)] +//~^ ERROR `#[label(slug)]` is not a valid `SessionSubdiagnostic` 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 `SessionSubdiagnostic` 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 `SessionSubdiagnostic` 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 `SessionSubdiagnostic` 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 `SessionSubdiagnostic` 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 `SessionSubdiagnostic` field 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 `SessionSubdiagnostic` field 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 `SessionSubdiagnostic` field 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 00000000000..bc96fcf771b --- /dev/null +++ b/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.stderr @@ -0,0 +1,387 @@ +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 `SessionSubdiagnostic` attribute + --> $DIR/subdiagnostic-derive.rs:54:1 + | +LL | #[label] + | ^^^^^^^^ + +error: `#[foo]` is not a valid `SessionSubdiagnostic` attribute + --> $DIR/subdiagnostic-derive.rs:63:1 + | +LL | #[foo] + | ^^^^^^ + +error: `#[label = ...]` is not a valid `SessionSubdiagnostic` attribute + --> $DIR/subdiagnostic-derive.rs:73:1 + | +LL | #[label = "..."] + | ^^^^^^^^^^^^^^^^ + +error: `#[label(bug = ...)]` is not a valid `SessionSubdiagnostic` attribute + --> $DIR/subdiagnostic-derive.rs:82:9 + | +LL | #[label(bug = "...")] + | ^^^^^^^^^^^ + +error: `#[label("...")]` is not a valid `SessionSubdiagnostic` attribute + --> $DIR/subdiagnostic-derive.rs:91:1 + | +LL | #[label("...")] + | ^^^^^^^^^^^^^^^ + +error: `#[label(slug = ...)]` is not a valid `SessionSubdiagnostic` attribute + --> $DIR/subdiagnostic-derive.rs:100:9 + | +LL | #[label(slug = 4)] + | ^^^^^^^^ + | + = help: value must be a string + +error: `#[label(slug(...))]` is not a valid `SessionSubdiagnostic` attribute + --> $DIR/subdiagnostic-derive.rs:109:9 + | +LL | #[label(slug("..."))] + | ^^^^^^^^^^^ + +error: `#[label(slug)]` is not a valid `SessionSubdiagnostic` 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 `SessionSubdiagnostic` attribute + --> $DIR/subdiagnostic-derive.rs:159:5 + | +LL | #[bar] + | ^^^^^^ + +error: `#[bar = ...]` is not a valid `SessionSubdiagnostic` attribute + --> $DIR/subdiagnostic-derive.rs:171:5 + | +LL | #[bar = "..."] + | ^^^^^^^^^^^^^^ + +error: `#[bar = ...]` is not a valid `SessionSubdiagnostic` attribute + --> $DIR/subdiagnostic-derive.rs:183:5 + | +LL | #[bar = 4] + | ^^^^^^^^^^ + +error: `#[bar("...")]` is not a valid `SessionSubdiagnostic` attribute + --> $DIR/subdiagnostic-derive.rs:195:5 + | +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 `SessionSubdiagnostic` field attribute + --> $DIR/subdiagnostic-derive.rs:256:5 + | +LL | #[bar] + | ^^^^^^ + +error: `#[bar = ...]` is not a valid `SessionSubdiagnostic` field attribute + --> $DIR/subdiagnostic-derive.rs:267:5 + | +LL | #[bar = "..."] + | ^^^^^^^^^^^^^^ + +error: `#[bar(...)]` is not a valid `SessionSubdiagnostic` field 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 +