diff --git a/Cargo.lock b/Cargo.lock index ba0098a977534..37af3f28f44a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4009,6 +4009,7 @@ dependencies = [ "rustc_hir", "rustc_index", "rustc_infer", + "rustc_macros", "rustc_middle", "rustc_parse_format", "rustc_session", diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs index c15dc024736f3..0d1d017d87458 100644 --- a/compiler/rustc_errors/src/diagnostic.rs +++ b/compiler/rustc_errors/src/diagnostic.rs @@ -1,7 +1,7 @@ use crate::snippet::Style; use crate::{ - CodeSuggestion, DiagnosticMessage, Level, MultiSpan, SubdiagnosticMessage, Substitution, - SubstitutionPart, SuggestionStyle, + CodeSuggestion, DiagnosticMessage, EmissionGuarantee, Level, LintDiagnosticBuilder, MultiSpan, + SubdiagnosticMessage, Substitution, SubstitutionPart, SuggestionStyle, }; use rustc_data_structures::stable_map::FxHashMap; use rustc_error_messages::FluentValue; @@ -168,6 +168,14 @@ pub trait AddSubdiagnostic { fn add_to_diagnostic(self, diag: &mut Diagnostic); } +/// Trait implemented by lint types. This should not be implemented manually. Instead, use +/// `#[derive(LintDiagnostic)]` -- see [rustc_macros::LintDiagnostic]. +#[rustc_diagnostic_item = "DecorateLint"] +pub trait DecorateLint<'a, G: EmissionGuarantee> { + /// Decorate and emit a lint. + fn decorate_lint(self, diag: LintDiagnosticBuilder<'a, G>); +} + #[must_use] #[derive(Clone, Debug, Encodable, Decodable)] pub struct Diagnostic { diff --git a/compiler/rustc_errors/src/diagnostic_builder.rs b/compiler/rustc_errors/src/diagnostic_builder.rs index 1ad33ef25b763..c3341fd68f4a5 100644 --- a/compiler/rustc_errors/src/diagnostic_builder.rs +++ b/compiler/rustc_errors/src/diagnostic_builder.rs @@ -589,3 +589,26 @@ macro_rules! struct_span_err { macro_rules! error_code { ($code:ident) => {{ $crate::DiagnosticId::Error(stringify!($code).to_owned()) }}; } + +/// Wrapper around a `DiagnosticBuilder` for creating lints. +pub struct LintDiagnosticBuilder<'a, G: EmissionGuarantee>(DiagnosticBuilder<'a, G>); + +impl<'a, G: EmissionGuarantee> LintDiagnosticBuilder<'a, G> { + /// Return the inner `DiagnosticBuilder`, first setting the primary message to `msg`. + pub fn build(mut self, msg: impl Into) -> DiagnosticBuilder<'a, G> { + self.0.set_primary_message(msg); + self.0.set_is_lint(); + self.0 + } + + /// Create a `LintDiagnosticBuilder` from some existing `DiagnosticBuilder`. + pub fn new(err: DiagnosticBuilder<'a, G>) -> LintDiagnosticBuilder<'a, G> { + LintDiagnosticBuilder(err) + } +} + +impl<'a> LintDiagnosticBuilder<'a, ErrorGuaranteed> { + pub fn forget_guarantee(self) -> LintDiagnosticBuilder<'a, ()> { + LintDiagnosticBuilder(self.0.forget_guarantee()) + } +} diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index b084e12848348..ffe4ecebb2e36 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -370,10 +370,10 @@ impl fmt::Display for ExplicitBug { impl error::Error for ExplicitBug {} pub use diagnostic::{ - AddSubdiagnostic, Diagnostic, DiagnosticArg, DiagnosticArgValue, DiagnosticId, + AddSubdiagnostic, DecorateLint, Diagnostic, DiagnosticArg, DiagnosticArgValue, DiagnosticId, DiagnosticStyledString, IntoDiagnosticArg, SubDiagnostic, }; -pub use diagnostic_builder::{DiagnosticBuilder, EmissionGuarantee}; +pub use diagnostic_builder::{DiagnosticBuilder, EmissionGuarantee, LintDiagnosticBuilder}; use std::backtrace::Backtrace; /// A handler deals with errors and other compiler output. diff --git a/compiler/rustc_lint/Cargo.toml b/compiler/rustc_lint/Cargo.toml index fab60b6f6096c..7c0f2c440d51d 100644 --- a/compiler/rustc_lint/Cargo.toml +++ b/compiler/rustc_lint/Cargo.toml @@ -22,3 +22,4 @@ rustc_trait_selection = { path = "../rustc_trait_selection" } rustc_parse_format = { path = "../rustc_parse_format" } rustc_infer = { path = "../rustc_infer" } rustc_type_ir = { path = "../rustc_type_ir" } +rustc_macros = { path = "../rustc_macros" } diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index 2fa4d7e072fef..6a089fceb2858 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -32,7 +32,8 @@ use rustc_ast_pretty::pprust::{self, expr_to_string}; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_errors::{ - fluent, Applicability, Diagnostic, DiagnosticMessage, DiagnosticStyledString, MultiSpan, + fluent, Applicability, Diagnostic, DiagnosticMessage, DiagnosticStyledString, + LintDiagnosticBuilder, MultiSpan, }; use rustc_feature::{deprecated_attributes, AttributeGate, BuiltinAttribute, GateIssue, Stability}; use rustc_hir as hir; @@ -40,7 +41,7 @@ use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{DefId, LocalDefId, LocalDefIdSet, CRATE_DEF_ID}; use rustc_hir::{ForeignItemKind, GenericParamKind, HirId, PatKind, PredicateOrigin}; use rustc_index::vec::Idx; -use rustc_middle::lint::{in_external_macro, LintDiagnosticBuilder}; +use rustc_middle::lint::in_external_macro; use rustc_middle::ty::layout::{LayoutError, LayoutOf}; use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::subst::GenericArgKind; diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs index eeb66f2d73871..83328093e9fa8 100644 --- a/compiler/rustc_lint/src/context.rs +++ b/compiler/rustc_lint/src/context.rs @@ -22,12 +22,13 @@ use rustc_ast::util::unicode::TEXT_FLOW_CONTROL_CHARS; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::sync; use rustc_errors::{add_elided_lifetime_in_path_suggestion, struct_span_err}; -use rustc_errors::{Applicability, MultiSpan, SuggestionStyle}; +use rustc_errors::{ + Applicability, DecorateLint, LintDiagnosticBuilder, MultiSpan, SuggestionStyle, +}; use rustc_hir as hir; use rustc_hir::def::Res; use rustc_hir::def_id::{CrateNum, DefId}; use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData}; -use rustc_middle::lint::LintDiagnosticBuilder; use rustc_middle::middle::privacy::AccessLevels; use rustc_middle::middle::stability; use rustc_middle::ty::layout::{LayoutError, LayoutOfHelpers, TyAndLayout}; @@ -871,6 +872,17 @@ pub trait LintContext: Sized { decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a, ()>), ); + /// Emit a lint at `span` from a lint struct (some type that implements `DecorateLint`, + /// typically generated by `#[derive(LintDiagnostic)]`). + fn emit_spanned_lint>( + &self, + lint: &'static Lint, + span: S, + decorator: impl for<'a> DecorateLint<'a, ()>, + ) { + self.lookup(lint, Some(span), |diag| decorator.decorate_lint(diag)); + } + fn struct_span_lint>( &self, lint: &'static Lint, @@ -879,6 +891,13 @@ pub trait LintContext: Sized { ) { self.lookup(lint, Some(span), decorate); } + + /// Emit a lint from a lint struct (some type that implements `DecorateLint`, typically + /// generated by `#[derive(LintDiagnostic)]`). + fn emit_lint(&self, lint: &'static Lint, decorator: impl for<'a> DecorateLint<'a, ()>) { + self.lookup(lint, None as Option, |diag| decorator.decorate_lint(diag)); + } + /// Emit a lint at the appropriate level, with no associated span. fn lint( &self, diff --git a/compiler/rustc_lint/src/internal.rs b/compiler/rustc_lint/src/internal.rs index 5bcf9390c076a..738f475983e9b 100644 --- a/compiler/rustc_lint/src/internal.rs +++ b/compiler/rustc_lint/src/internal.rs @@ -414,7 +414,7 @@ impl LateLintPass<'_> for Diagnostics { let Impl { of_trait: Some(of_trait), .. } = impl_ && let Some(def_id) = of_trait.trait_def_id() && let Some(name) = cx.tcx.get_diagnostic_name(def_id) && - matches!(name, sym::SessionDiagnostic | sym::AddSubdiagnostic) + matches!(name, sym::SessionDiagnostic | sym::AddSubdiagnostic | sym::DecorateLint) { found_impl = true; break; diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs index bf4a726b06188..00e96f20d1aaa 100644 --- a/compiler/rustc_lint/src/levels.rs +++ b/compiler/rustc_lint/src/levels.rs @@ -3,13 +3,13 @@ use crate::late::unerased_lint_store; use rustc_ast as ast; use rustc_ast_pretty::pprust; use rustc_data_structures::fx::FxHashMap; -use rustc_errors::{struct_span_err, Applicability, Diagnostic, MultiSpan}; +use rustc_errors::{struct_span_err, Applicability, Diagnostic, LintDiagnosticBuilder, MultiSpan}; use rustc_hir as hir; use rustc_hir::{intravisit, HirId}; use rustc_middle::hir::nested_filter; use rustc_middle::lint::{ - struct_lint_level, LevelAndSource, LintDiagnosticBuilder, LintExpectation, LintLevelMap, - LintLevelSets, LintLevelSource, LintSet, LintStackIndex, COMMAND_LINE, + struct_lint_level, LevelAndSource, LintExpectation, LintLevelMap, LintLevelSets, + LintLevelSource, LintSet, LintStackIndex, COMMAND_LINE, }; use rustc_middle::ty::query::Providers; use rustc_middle::ty::{RegisteredTools, TyCtxt}; diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs index 0056872ee44c3..fa48128e3b0d1 100644 --- a/compiler/rustc_lint/src/types.rs +++ b/compiler/rustc_lint/src/types.rs @@ -5,6 +5,7 @@ use rustc_data_structures::fx::FxHashSet; use rustc_errors::{fluent, Applicability, DiagnosticMessage}; use rustc_hir as hir; use rustc_hir::{is_range_literal, Expr, ExprKind, Node}; +use rustc_macros::LintDiagnostic; use rustc_middle::ty::layout::{IntegerExt, LayoutOf, SizeSkeleton}; use rustc_middle::ty::subst::SubstsRef; use rustc_middle::ty::{self, AdtKind, DefIdTree, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable}; @@ -1553,13 +1554,20 @@ impl InvalidAtomicOrdering { let Some(fail_ordering) = Self::match_ordering(cx, fail_order_arg) else { return }; if matches!(fail_ordering, sym::Release | sym::AcqRel) { - cx.struct_span_lint(INVALID_ATOMIC_ORDERING, fail_order_arg.span, |diag| { - diag.build(fluent::lint::atomic_ordering_invalid) - .set_arg("method", method) - .span_label(fail_order_arg.span, fluent::lint::label) - .help(fluent::lint::help) - .emit(); - }); + #[derive(LintDiagnostic)] + #[lint(lint::atomic_ordering_invalid)] + #[help] + struct InvalidAtomicOrderingDiag { + method: Symbol, + #[label] + fail_order_arg_span: Span, + } + + cx.emit_spanned_lint( + INVALID_ATOMIC_ORDERING, + fail_order_arg.span, + InvalidAtomicOrderingDiag { method, fail_order_arg_span: fail_order_arg.span }, + ); } let Some(success_ordering) = Self::match_ordering(cx, success_order_arg) else { return }; diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic.rs b/compiler/rustc_macros/src/diagnostics/diagnostic.rs index d0c8652718969..027f377b0acc7 100644 --- a/compiler/rustc_macros/src/diagnostics/diagnostic.rs +++ b/compiler/rustc_macros/src/diagnostics/diagnostic.rs @@ -1,125 +1,54 @@ #![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::{ - report_error_if_not_applied_to_span, report_type_error, type_is_unit, type_matches_path, - Applicability, FieldInfo, FieldInnerTy, HasFieldMap, SetOnce, -}; -use proc_macro2::{Ident, TokenStream}; -use quote::{format_ident, quote}; -use std::collections::HashMap; -use std::str::FromStr; -use syn::{ - parse_quote, spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, NestedMeta, Path, Type, -}; -use synstructure::{BindingInfo, Structure}; +use crate::diagnostics::diagnostic_builder::{DiagnosticDeriveBuilder, DiagnosticDeriveKind}; +use crate::diagnostics::error::{span_err, DiagnosticDeriveError}; +use crate::diagnostics::utils::{build_field_mapping, SetOnce}; +use proc_macro2::TokenStream; +use quote::quote; +use syn::spanned::Spanned; +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, + sess: syn::Ident, + builder: DiagnosticDeriveBuilder, } 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 { + builder: DiagnosticDeriveBuilder { diag, - sess, - fields: fields_map, + fields: build_field_mapping(&structure), kind: None, code: None, slug: None, }, + sess, structure, } } pub(crate) fn into_tokens(self) -> TokenStream { - let SessionDiagnosticDerive { mut structure, mut builder } = self; + let SessionDiagnosticDerive { mut structure, sess, 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)*; - } - }; - - // Keep track of which fields are subdiagnostics or have no attributes. - let mut subdiagnostics_or_empty = std::collections::HashSet::new(); - - // 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 `add_subdiagnostic` - // or `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 - .clone() - .filter(|field_binding| { - let attrs = &field_binding.ast().attrs; - - (!attrs.is_empty() - && attrs.iter().all(|attr| { - "subdiagnostic" - != attr.path.segments.last().unwrap().ident.to_string() - })) - || { - subdiagnostics_or_empty.insert(field_binding.binding.clone()); - false - } - }) - .each(|field_binding| builder.generate_field_attrs_code(field_binding)); - - structure.bind_with(|_| synstructure::BindStyle::Move); - // 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 or a `#[subdiagnostic]` attribute then it must be passed as an - // argument to the diagnostic so that it can be referred to by Fluent messages. - let args = structure - .filter(|field_binding| { - subdiagnostics_or_empty.contains(&field_binding.binding) - }) - .each(|field_binding| builder.generate_field_attrs_code(field_binding)); + let preamble = builder.preamble(&structure); + let (attrs, args) = builder.body(&mut structure); let span = ast.span().unwrap(); - let (diag, sess) = (&builder.diag, &builder.sess); - let init = match (builder.kind, builder.slug) { + let diag = &builder.diag; + let init = match (builder.kind.value(), builder.slug.value()) { (None, _) => { span_err(span, "diagnostic kind not specified") .help("use the `#[error(...)]` attribute to create an error") .emit(); - return SessionDiagnosticDeriveError::ErrorHandled.to_compile_error(); + return DiagnosticDeriveError::ErrorHandled.to_compile_error(); } - (Some((kind, _)), None) => { + (Some(kind), None) => { span_err(span, "diagnostic slug not specified") .help(&format!( "specify the slug as the first argument to the attribute, such as \ @@ -127,14 +56,20 @@ impl<'a> SessionDiagnosticDerive<'a> { kind.descr() )) .emit(); - return SessionDiagnosticDeriveError::ErrorHandled.to_compile_error(); + return DiagnosticDeriveError::ErrorHandled.to_compile_error(); } - (Some((SessionDiagnosticKind::Error, _)), Some((slug, _))) => { + (Some(DiagnosticDeriveKind::Lint), _) => { + span_err(span, "only `#[error(..)]` and `#[warn(..)]` are supported") + .help("use the `#[error(...)]` attribute to create a error") + .emit(); + return DiagnosticDeriveError::ErrorHandled.to_compile_error(); + } + (Some(DiagnosticDeriveKind::Error), Some(slug)) => { quote! { let mut #diag = #sess.struct_err(rustc_errors::fluent::#slug); } } - (Some((SessionDiagnosticKind::Warn, _)), Some((slug, _))) => { + (Some(DiagnosticDeriveKind::Warn), Some(slug)) => { quote! { let mut #diag = #sess.struct_warn(rustc_errors::fluent::#slug); } @@ -153,10 +88,12 @@ impl<'a> SessionDiagnosticDerive<'a> { #diag }; let param_ty = match builder.kind { - Some((SessionDiagnosticKind::Error, _)) => { + Some((DiagnosticDeriveKind::Error, _)) => { quote! { rustc_errors::ErrorGuaranteed } } - Some((SessionDiagnosticKind::Warn, _)) => quote! { () }, + Some((DiagnosticDeriveKind::Lint | DiagnosticDeriveKind::Warn, _)) => { + quote! { () } + } _ => unreachable!(), }; @@ -168,13 +105,12 @@ impl<'a> SessionDiagnosticDerive<'a> { ) .emit(); - let implementation = SessionDiagnosticDeriveError::ErrorHandled.to_compile_error(); + let implementation = DiagnosticDeriveError::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 @@ -191,525 +127,99 @@ impl<'a> SessionDiagnosticDerive<'a> { } } -/// What kind of session diagnostic is being derived - an error or a warning? -#[derive(Copy, Clone)] -enum SessionDiagnosticKind { - /// `#[error(..)]` - Error, - /// `#[warn(..)]` - Warn, +/// The central struct for constructing the `decorate_lint` method from an annotated struct. +pub(crate) struct LintDiagnosticDerive<'a> { + structure: Structure<'a>, + builder: DiagnosticDeriveBuilder, } -impl SessionDiagnosticKind { - /// Returns human-readable string corresponding to the kind. - fn descr(&self) -> &'static str { - match self { - SessionDiagnosticKind::Error => "error", - SessionDiagnosticKind::Warn => "warning", +impl<'a> LintDiagnosticDerive<'a> { + pub(crate) fn new(diag: syn::Ident, structure: Structure<'a>) -> Self { + Self { + builder: DiagnosticDeriveBuilder { + diag, + fields: build_field_mapping(&structure), + kind: None, + code: None, + slug: None, + }, + structure, } } -} - -/// 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<(Path, 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 diag = &self.diag; - 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 is_help_or_note = matches!(name, "help" | "note"); - - let nested = match meta { - // Most attributes are lists, like `#[error(..)]`/`#[warning(..)]` for most cases or - // `#[help(..)]`/`#[note(..)]` when the user is specifying a alternative slug. - Meta::List(MetaList { ref nested, .. }) => nested, - // Subdiagnostics without spans can be applied to the type too, and these are just - // paths: `#[help]` and `#[note]` - Meta::Path(_) if is_help_or_note => { - let fn_name = proc_macro2::Ident::new(name, attr.span()); - return Ok(quote! { #diag.#fn_name(rustc_errors::fluent::_subdiag::#fn_name); }); - } - _ => throw_invalid_attr!(attr, &meta), - }; - - // Check the kind before doing any further processing so that there aren't misleading - // "no kind specified" errors if there are failures later. - match name { - "error" => self.kind.set_once((SessionDiagnosticKind::Error, span)), - "warning" => self.kind.set_once((SessionDiagnosticKind::Warn, span)), - "help" | "note" => (), - _ => throw_invalid_attr!(attr, &meta, |diag| { - diag.help("only `error`, `warning`, `help` and `note` are valid attributes") - }), - } - // First nested element should always be the path, e.g. `#[error(typeck::invalid)]` or - // `#[help(typeck::another_help)]`. - let mut nested_iter = nested.into_iter(); - if let Some(nested_attr) = nested_iter.next() { - // Report an error if there are any other list items after the path. - if is_help_or_note && nested_iter.next().is_some() { - throw_invalid_nested_attr!(attr, &nested_attr, |diag| { - diag.help("`help` and `note` struct attributes can only have one argument") - }); - } - - match nested_attr { - NestedMeta::Meta(Meta::Path(path)) if is_help_or_note => { - let fn_name = proc_macro2::Ident::new(name, attr.span()); - return Ok(quote! { #diag.#fn_name(rustc_errors::fluent::#path); }); - } - NestedMeta::Meta(Meta::Path(path)) => { - self.slug.set_once((path.clone(), span)); - } - NestedMeta::Meta(meta @ Meta::NameValue(_)) - if !is_help_or_note - && meta.path().segments.last().unwrap().ident.to_string() == "code" => - { - // don't error for valid follow-up attributes - } - nested_attr => throw_invalid_nested_attr!(attr, &nested_attr, |diag| { - diag.help("first argument of the attribute should be the diagnostic slug") - }), - }; - } + pub(crate) fn into_tokens(self) -> TokenStream { + let LintDiagnosticDerive { mut structure, mut builder } = self; - // Remaining attributes are optional, only `code = ".."` at the moment. - let mut tokens = Vec::new(); - for nested_attr in nested_iter { - let meta = match nested_attr { - syn::NestedMeta::Meta(meta) => meta, - _ => throw_invalid_nested_attr!(attr, &nested_attr), - }; + let ast = structure.ast(); + let implementation = { + if let syn::Data::Struct(..) = ast.data { + let preamble = builder.preamble(&structure); + let (attrs, args) = builder.body(&mut structure); - let path = meta.path(); - let nested_name = path.segments.last().unwrap().ident.to_string(); - // Struct attributes are only allowed to be applied once, and the diagnostic - // changes will be set in the initialisation code. - if let Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) = &meta { - let span = s.span().unwrap(); - match nested_name.as_str() { - "code" => { - self.code.set_once((s.value(), span)); - let code = &self.code.as_ref().map(|(v, _)| v); - tokens.push(quote! { - #diag.code(rustc_errors::DiagnosticId::Error(#code.to_string())); - }); + let diag = &builder.diag; + let span = ast.span().unwrap(); + let init = match (builder.kind.value(), builder.slug.value()) { + (None, _) => { + span_err(span, "diagnostic kind not specified") + .help("use the `#[error(...)]` attribute to create an error") + .emit(); + return DiagnosticDeriveError::ErrorHandled.to_compile_error(); } - _ => invalid_nested_attr(attr, &nested_attr) - .help("only `code` is a valid nested attributes following the slug") - .emit(), - } - } else { - invalid_nested_attr(attr, &nested_attr).emit() - } - } - - Ok(tokens.drain(..).collect()) - } - - fn generate_field_attrs_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream { - let field = binding_info.ast(); - let field_binding = &binding_info.binding; - - let inner_ty = FieldInnerTy::from_type(&field.ty); - - // When generating `set_arg` or `add_subdiagnostic` 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). - if field.attrs.is_empty() { - let diag = &self.diag; - let ident = field.ident.as_ref().unwrap(); - quote! { - #diag.set_arg( - stringify!(#ident), - #field_binding - ); - } - } else { - field - .attrs - .iter() - .map(move |attr| { - let name = attr.path.segments.last().unwrap().ident.to_string(); - let (binding, needs_destructure) = match (name.as_str(), &inner_ty) { - // `primary_span` can accept a `Vec` so don't destructure that. - ("primary_span", FieldInnerTy::Vec(_)) => { - (quote! { #field_binding.clone() }, false) + (Some(kind), None) => { + span_err(span, "diagnostic slug not specified") + .help(&format!( + "specify the slug as the first argument to the attribute, such as \ + `#[{}(typeck::example_error)]`", + kind.descr() + )) + .emit(); + return DiagnosticDeriveError::ErrorHandled.to_compile_error(); + } + (Some(DiagnosticDeriveKind::Error | DiagnosticDeriveKind::Warn), _) => { + span_err(span, "only `#[lint(..)]` is supported") + .help("use the `#[lint(...)]` attribute to create a lint") + .emit(); + return DiagnosticDeriveError::ErrorHandled.to_compile_error(); + } + (Some(DiagnosticDeriveKind::Lint), Some(slug)) => { + quote! { + let mut #diag = #diag.build(rustc_errors::fluent::#slug); } - // `subdiagnostics` are not derefed because they are bound by value. - ("subdiagnostic", _) => (quote! { #field_binding }, true), - _ => (quote! { *#field_binding }, true), - }; - - let generated_code = self - .generate_inner_field_code( - attr, - FieldInfo { - binding: binding_info, - ty: inner_ty.inner_type().unwrap_or(&field.ty), - span: &field.span(), - }, - binding, - ) - .unwrap_or_else(|v| v.to_compile_error()); - - if needs_destructure { - inner_ty.with(field_binding, generated_code) - } else { - generated_code } - }) - .collect() - } - } - - fn generate_inner_field_code( - &mut self, - attr: &Attribute, - info: FieldInfo<'_>, - binding: TokenStream, - ) -> Result { - let meta = attr.parse_meta()?; - match meta { - Meta::Path(_) => self.generate_inner_field_code_path(attr, info, binding), - Meta::List(MetaList { .. }) => self.generate_inner_field_code_list(attr, info, binding), - _ => throw_invalid_attr!(attr, &meta), - } - } - - fn generate_inner_field_code_path( - &mut self, - attr: &Attribute, - info: FieldInfo<'_>, - binding: TokenStream, - ) -> Result { - assert!(matches!(attr.parse_meta()?, Meta::Path(_))); - let diag = &self.diag; - - let meta = attr.parse_meta()?; - - let ident = &attr.path.segments.last().unwrap().ident; - let name = ident.to_string(); - let name = name.as_str(); - 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(#binding); - }) - } - "label" => { - report_error_if_not_applied_to_span(attr, &info)?; - Ok(self.add_spanned_subdiagnostic(binding, ident, parse_quote! { _subdiag::label })) - } - "note" | "help" => { - let path = match name { - "note" => parse_quote! { _subdiag::note }, - "help" => parse_quote! { _subdiag::help }, - _ => unreachable!(), }; - if type_matches_path(&info.ty, &["rustc_span", "Span"]) { - Ok(self.add_spanned_subdiagnostic(binding, ident, path)) - } else if type_is_unit(&info.ty) { - Ok(self.add_subdiagnostic(ident, path)) - } else { - report_type_error(attr, "`Span` or `()`")?; - } - } - "subdiagnostic" => Ok(quote! { #diag.subdiagnostic(#binding); }), - _ => throw_invalid_attr!(attr, &meta, |diag| { - diag.help( - "only `skip_arg`, `primary_span`, `label`, `note`, `help` and `subdiagnostic` \ - are valid field attributes", - ) - }), - } - } - - fn generate_inner_field_code_list( - &mut self, - attr: &Attribute, - info: FieldInfo<'_>, - binding: TokenStream, - ) -> Result { - let meta = attr.parse_meta()?; - let Meta::List(MetaList { ref path, ref nested, .. }) = meta else { unreachable!() }; - - let ident = &attr.path.segments.last().unwrap().ident; - let name = path.segments.last().unwrap().ident.to_string(); - let name = name.as_ref(); - match name { - "suggestion" | "suggestion_short" | "suggestion_hidden" | "suggestion_verbose" => { - return self.generate_inner_field_code_suggestion(attr, info); - } - "label" | "help" | "note" => (), - _ => throw_invalid_attr!(attr, &meta, |diag| { - diag.help( - "only `label`, `note`, `help` or `suggestion{,_short,_hidden,_verbose}` are \ - valid field attributes", - ) - }), - } - // For `#[label(..)]`, `#[note(..)]` and `#[help(..)]`, the first nested element must be a - // path, e.g. `#[label(typeck::label)]`. - let mut nested_iter = nested.into_iter(); - let msg = match nested_iter.next() { - Some(NestedMeta::Meta(Meta::Path(path))) => path.clone(), - Some(nested_attr) => throw_invalid_nested_attr!(attr, &nested_attr), - None => throw_invalid_attr!(attr, &meta), - }; - - // None of these attributes should have anything following the slug. - if nested_iter.next().is_some() { - throw_invalid_attr!(attr, &meta); - } - - match name { - "label" => { - report_error_if_not_applied_to_span(attr, &info)?; - Ok(self.add_spanned_subdiagnostic(binding, ident, msg)) - } - "note" | "help" if type_matches_path(&info.ty, &["rustc_span", "Span"]) => { - Ok(self.add_spanned_subdiagnostic(binding, ident, msg)) - } - "note" | "help" if type_is_unit(&info.ty) => Ok(self.add_subdiagnostic(ident, msg)), - "note" | "help" => { - report_type_error(attr, "`Span` or `()`")?; - } - _ => unreachable!(), - } - } - - fn generate_inner_field_code_suggestion( - &mut self, - attr: &Attribute, - info: FieldInfo<'_>, - ) -> Result { - let diag = &self.diag; - - let mut meta = attr.parse_meta()?; - let Meta::List(MetaList { ref path, ref mut nested, .. }) = meta else { unreachable!() }; - - let (span_field, mut applicability) = self.span_and_applicability_of_ty(info)?; - - let mut msg = None; - let mut code = None; - - let mut nested_iter = nested.into_iter().peekable(); - if let Some(nested_attr) = nested_iter.peek() { - if let NestedMeta::Meta(Meta::Path(path)) = nested_attr { - msg = Some(path.clone()); - } - }; - // Move the iterator forward if a path was found (don't otherwise so that - // code/applicability can be found or an error emitted). - if msg.is_some() { - let _ = nested_iter.next(); - } - - for nested_attr in nested_iter { - 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 { - "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", - ) - }), + let implementation = quote! { + #init + #preamble + match self { + #attrs } - } - _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| { - if matches!(meta, Meta::Path(_)) { - diag.help("a diagnostic slug must be the first argument to the attribute") - } else { - diag + match self { + #args } - }), - } - } - - let applicability = - applicability.unwrap_or_else(|| quote!(rustc_errors::Applicability::Unspecified)); - - let name = path.segments.last().unwrap().ident.to_string(); - let method = format_ident!("span_{}", name); - - let msg = msg.unwrap_or_else(|| parse_quote! { _subdiag::suggestion }); - let msg = quote! { rustc_errors::fluent::#msg }; - let code = code.unwrap_or_else(|| quote! { String::new() }); - - Ok(quote! { #diag.#method(#span_field, #msg, #code, #applicability); }) - } - - /// Adds a spanned subdiagnostic by generating a `diag.span_$kind` call with the current slug - /// and `fluent_attr_identifier`. - fn add_spanned_subdiagnostic( - &self, - field_binding: TokenStream, - kind: &Ident, - fluent_attr_identifier: Path, - ) -> TokenStream { - let diag = &self.diag; - let fn_name = format_ident!("span_{}", kind); - quote! { - #diag.#fn_name( - #field_binding, - rustc_errors::fluent::#fluent_attr_identifier - ); - } - } + #diag.emit(); + }; - /// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current slug - /// and `fluent_attr_identifier`. - fn add_subdiagnostic(&self, kind: &Ident, fluent_attr_identifier: Path) -> TokenStream { - let diag = &self.diag; - quote! { - #diag.#kind(rustc_errors::fluent::#fluent_attr_identifier); - } - } + implementation + } else { + span_err( + ast.span().unwrap(), + "`#[derive(LintDiagnostic)]` can only be used on structs", + ) + .emit(); - 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)) + DiagnosticDeriveError::ErrorHandled.to_compile_error() } - // 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))); + let diag = &builder.diag; + structure.gen_impl(quote! { + gen impl<'__a> rustc_errors::DecorateLint<'__a, ()> for @Self { + fn decorate_lint(self, #diag: rustc_errors::LintDiagnosticBuilder<'__a, ()>) { + use rustc_errors::IntoDiagnosticArg; + #implementation } - - 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/diagnostic_builder.rs b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs new file mode 100644 index 0000000000000..74ce1ab08c264 --- /dev/null +++ b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs @@ -0,0 +1,590 @@ +#![deny(unused_must_use)] + +use crate::diagnostics::error::{ + invalid_nested_attr, span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err, + DiagnosticDeriveError, +}; +use crate::diagnostics::utils::{ + report_error_if_not_applied_to_span, report_type_error, type_is_unit, type_matches_path, + Applicability, FieldInfo, FieldInnerTy, HasFieldMap, SetOnce, +}; +use proc_macro2::{Ident, TokenStream}; +use quote::{format_ident, quote}; +use std::collections::HashMap; +use std::str::FromStr; +use syn::{ + parse_quote, spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, NestedMeta, Path, Type, +}; +use synstructure::{BindingInfo, Structure}; + +/// What kind of diagnostic is being derived - an error, a warning or a lint? +#[derive(Copy, Clone)] +pub(crate) enum DiagnosticDeriveKind { + /// `#[error(..)]` + Error, + /// `#[warn(..)]` + Warn, + /// `#[lint(..)]` + Lint, +} + +impl DiagnosticDeriveKind { + /// Returns human-readable string corresponding to the kind. + pub fn descr(&self) -> &'static str { + match self { + DiagnosticDeriveKind::Error => "error", + DiagnosticDeriveKind::Warn => "warning", + DiagnosticDeriveKind::Lint => "lint", + } + } +} + +/// Tracks persistent information required for building up individual calls to diagnostic methods +/// for generated diagnostic derives - both `SessionDiagnostic` for errors/warnings and +/// `LintDiagnostic` for lints. +pub(crate) struct DiagnosticDeriveBuilder { + /// The identifier to use for the generated `DiagnosticBuilder` instance. + pub diag: syn::Ident, + + /// Store a map of field name to its corresponding field. This is built on construction of the + /// derive builder. + pub fields: HashMap, + + /// Kind of diagnostic requested via the struct attribute. + pub kind: Option<(DiagnosticDeriveKind, proc_macro::Span)>, + /// Slug is a mandatory part of the struct attribute as corresponds to the Fluent message that + /// has the actual diagnostic message. + pub slug: Option<(Path, proc_macro::Span)>, + /// Error codes are a optional part of the struct attribute - this is only set to detect + /// multiple specifications. + pub code: Option<(String, proc_macro::Span)>, +} + +impl HasFieldMap for DiagnosticDeriveBuilder { + fn get_field_binding(&self, field: &String) -> Option<&TokenStream> { + self.fields.get(field) + } +} + +impl DiagnosticDeriveBuilder { + pub fn preamble<'s>(&mut self, structure: &Structure<'s>) -> TokenStream { + let ast = structure.ast(); + let attrs = &ast.attrs; + let preamble = attrs.iter().map(|attr| { + self.generate_structure_code_for_attr(attr).unwrap_or_else(|v| v.to_compile_error()) + }); + + quote! { + #(#preamble)*; + } + } + + pub fn body<'s>(&mut self, structure: &mut Structure<'s>) -> (TokenStream, TokenStream) { + // Keep track of which fields are subdiagnostics or have no attributes. + let mut subdiagnostics_or_empty = std::collections::HashSet::new(); + + // 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 `add_subdiagnostic` + // or `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 + .clone() + .filter(|field_binding| { + let attrs = &field_binding.ast().attrs; + + (!attrs.is_empty() + && attrs.iter().all(|attr| { + "subdiagnostic" != attr.path.segments.last().unwrap().ident.to_string() + })) + || { + subdiagnostics_or_empty.insert(field_binding.binding.clone()); + false + } + }) + .each(|field_binding| self.generate_field_attrs_code(field_binding)); + + structure.bind_with(|_| synstructure::BindStyle::Move); + // 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 or a `#[subdiagnostic]` attribute then it must be passed as an + // argument to the diagnostic so that it can be referred to by Fluent messages. + let args = structure + .filter(|field_binding| subdiagnostics_or_empty.contains(&field_binding.binding)) + .each(|field_binding| self.generate_field_attrs_code(field_binding)); + + (attrs, args) + } + + /// Establishes state in the `DiagnosticDeriveBuilder` 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_for_attr( + &mut self, + attr: &Attribute, + ) -> Result { + let diag = &self.diag; + 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 is_help_or_note = matches!(name, "help" | "note"); + + let nested = match meta { + // Most attributes are lists, like `#[error(..)]`/`#[warning(..)]` for most cases or + // `#[help(..)]`/`#[note(..)]` when the user is specifying a alternative slug. + Meta::List(MetaList { ref nested, .. }) => nested, + // Subdiagnostics without spans can be applied to the type too, and these are just + // paths: `#[help]` and `#[note]` + Meta::Path(_) if is_help_or_note => { + let fn_name = proc_macro2::Ident::new(name, attr.span()); + return Ok(quote! { #diag.#fn_name(rustc_errors::fluent::_subdiag::#fn_name); }); + } + _ => throw_invalid_attr!(attr, &meta), + }; + + // Check the kind before doing any further processing so that there aren't misleading + // "no kind specified" errors if there are failures later. + match name { + "error" => self.kind.set_once((DiagnosticDeriveKind::Error, span)), + "warning" => self.kind.set_once((DiagnosticDeriveKind::Warn, span)), + "lint" => self.kind.set_once((DiagnosticDeriveKind::Lint, span)), + "help" | "note" => (), + _ => throw_invalid_attr!(attr, &meta, |diag| { + diag.help("only `error`, `warning`, `help` and `note` are valid attributes") + }), + } + + // First nested element should always be the path, e.g. `#[error(typeck::invalid)]` or + // `#[help(typeck::another_help)]`. + let mut nested_iter = nested.into_iter(); + if let Some(nested_attr) = nested_iter.next() { + // Report an error if there are any other list items after the path. + if is_help_or_note && nested_iter.next().is_some() { + throw_invalid_nested_attr!(attr, &nested_attr, |diag| { + diag.help("`help` and `note` struct attributes can only have one argument") + }); + } + + match nested_attr { + NestedMeta::Meta(Meta::Path(path)) if is_help_or_note => { + let fn_name = proc_macro2::Ident::new(name, attr.span()); + return Ok(quote! { #diag.#fn_name(rustc_errors::fluent::#path); }); + } + NestedMeta::Meta(Meta::Path(path)) => { + self.slug.set_once((path.clone(), span)); + } + NestedMeta::Meta(meta @ Meta::NameValue(_)) + if !is_help_or_note + && meta.path().segments.last().unwrap().ident.to_string() == "code" => + { + // don't error for valid follow-up attributes + } + nested_attr => throw_invalid_nested_attr!(attr, &nested_attr, |diag| { + diag.help("first argument of the attribute should be the diagnostic slug") + }), + }; + } + + // Remaining attributes are optional, only `code = ".."` at the moment. + let mut tokens = Vec::new(); + for nested_attr in nested_iter { + 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(); + // Struct attributes are only allowed to be applied once, and the diagnostic + // changes will be set in the initialisation code. + if let Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) = &meta { + let span = s.span().unwrap(); + match nested_name.as_str() { + "code" => { + self.code.set_once((s.value(), span)); + let code = &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 `code` is a valid nested attributes following the slug") + .emit(), + } + } else { + invalid_nested_attr(attr, &nested_attr).emit() + } + } + + Ok(tokens.drain(..).collect()) + } + + fn generate_field_attrs_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream { + let field = binding_info.ast(); + let field_binding = &binding_info.binding; + + let inner_ty = FieldInnerTy::from_type(&field.ty); + + // When generating `set_arg` or `add_subdiagnostic` 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). + if field.attrs.is_empty() { + let diag = &self.diag; + let ident = field.ident.as_ref().unwrap(); + quote! { + #diag.set_arg( + stringify!(#ident), + #field_binding + ); + } + } else { + field + .attrs + .iter() + .map(move |attr| { + let name = attr.path.segments.last().unwrap().ident.to_string(); + let (binding, needs_destructure) = match (name.as_str(), &inner_ty) { + // `primary_span` can accept a `Vec` so don't destructure that. + ("primary_span", FieldInnerTy::Vec(_)) => { + (quote! { #field_binding.clone() }, false) + } + // `subdiagnostics` are not derefed because they are bound by value. + ("subdiagnostic", _) => (quote! { #field_binding }, true), + _ => (quote! { *#field_binding }, true), + }; + + let generated_code = self + .generate_inner_field_code( + attr, + FieldInfo { + binding: binding_info, + ty: inner_ty.inner_type().unwrap_or(&field.ty), + span: &field.span(), + }, + binding, + ) + .unwrap_or_else(|v| v.to_compile_error()); + + if needs_destructure { + inner_ty.with(field_binding, generated_code) + } else { + generated_code + } + }) + .collect() + } + } + + fn generate_inner_field_code( + &mut self, + attr: &Attribute, + info: FieldInfo<'_>, + binding: TokenStream, + ) -> Result { + let meta = attr.parse_meta()?; + match meta { + Meta::Path(_) => self.generate_inner_field_code_path(attr, info, binding), + Meta::List(MetaList { .. }) => self.generate_inner_field_code_list(attr, info, binding), + _ => throw_invalid_attr!(attr, &meta), + } + } + + fn generate_inner_field_code_path( + &mut self, + attr: &Attribute, + info: FieldInfo<'_>, + binding: TokenStream, + ) -> Result { + assert!(matches!(attr.parse_meta()?, Meta::Path(_))); + let diag = &self.diag; + + let meta = attr.parse_meta()?; + + let ident = &attr.path.segments.last().unwrap().ident; + let name = ident.to_string(); + let name = name.as_str(); + 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(#binding); + }) + } + "label" => { + report_error_if_not_applied_to_span(attr, &info)?; + Ok(self.add_spanned_subdiagnostic(binding, ident, parse_quote! { _subdiag::label })) + } + "note" | "help" => { + let path = match name { + "note" => parse_quote! { _subdiag::note }, + "help" => parse_quote! { _subdiag::help }, + _ => unreachable!(), + }; + if type_matches_path(&info.ty, &["rustc_span", "Span"]) { + Ok(self.add_spanned_subdiagnostic(binding, ident, path)) + } else if type_is_unit(&info.ty) { + Ok(self.add_subdiagnostic(ident, path)) + } else { + report_type_error(attr, "`Span` or `()`")? + } + } + "subdiagnostic" => Ok(quote! { #diag.subdiagnostic(#binding); }), + _ => throw_invalid_attr!(attr, &meta, |diag| { + diag.help( + "only `skip_arg`, `primary_span`, `label`, `note`, `help` and `subdiagnostic` \ + are valid field attributes", + ) + }), + } + } + + fn generate_inner_field_code_list( + &mut self, + attr: &Attribute, + info: FieldInfo<'_>, + binding: TokenStream, + ) -> Result { + let meta = attr.parse_meta()?; + let Meta::List(MetaList { ref path, ref nested, .. }) = meta else { unreachable!() }; + + let ident = &attr.path.segments.last().unwrap().ident; + let name = path.segments.last().unwrap().ident.to_string(); + let name = name.as_ref(); + match name { + "suggestion" | "suggestion_short" | "suggestion_hidden" | "suggestion_verbose" => { + return self.generate_inner_field_code_suggestion(attr, info); + } + "label" | "help" | "note" => (), + _ => throw_invalid_attr!(attr, &meta, |diag| { + diag.help( + "only `label`, `note`, `help` or `suggestion{,_short,_hidden,_verbose}` are \ + valid field attributes", + ) + }), + } + + // For `#[label(..)]`, `#[note(..)]` and `#[help(..)]`, the first nested element must be a + // path, e.g. `#[label(typeck::label)]`. + let mut nested_iter = nested.into_iter(); + let msg = match nested_iter.next() { + Some(NestedMeta::Meta(Meta::Path(path))) => path.clone(), + Some(nested_attr) => throw_invalid_nested_attr!(attr, &nested_attr), + None => throw_invalid_attr!(attr, &meta), + }; + + // None of these attributes should have anything following the slug. + if nested_iter.next().is_some() { + throw_invalid_attr!(attr, &meta); + } + + match name { + "label" => { + report_error_if_not_applied_to_span(attr, &info)?; + Ok(self.add_spanned_subdiagnostic(binding, ident, msg)) + } + "note" | "help" if type_matches_path(&info.ty, &["rustc_span", "Span"]) => { + Ok(self.add_spanned_subdiagnostic(binding, ident, msg)) + } + "note" | "help" if type_is_unit(&info.ty) => Ok(self.add_subdiagnostic(ident, msg)), + "note" | "help" => report_type_error(attr, "`Span` or `()`")?, + _ => unreachable!(), + } + } + + fn generate_inner_field_code_suggestion( + &mut self, + attr: &Attribute, + info: FieldInfo<'_>, + ) -> Result { + let diag = &self.diag; + + let mut meta = attr.parse_meta()?; + let Meta::List(MetaList { ref path, ref mut nested, .. }) = meta else { unreachable!() }; + + let (span_field, mut applicability) = self.span_and_applicability_of_ty(info)?; + + let mut msg = None; + let mut code = None; + + let mut nested_iter = nested.into_iter().peekable(); + if let Some(nested_attr) = nested_iter.peek() { + if let NestedMeta::Meta(Meta::Path(path)) = nested_attr { + msg = Some(path.clone()); + } + }; + // Move the iterator forward if a path was found (don't otherwise so that + // code/applicability can be found or an error emitted). + if msg.is_some() { + let _ = nested_iter.next(); + } + + for nested_attr in nested_iter { + 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 { + "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, |diag| { + if matches!(meta, Meta::Path(_)) { + diag.help("a diagnostic slug must be the first argument to the attribute") + } else { + diag + } + }), + } + } + + let applicability = + applicability.unwrap_or_else(|| quote!(rustc_errors::Applicability::Unspecified)); + + let name = path.segments.last().unwrap().ident.to_string(); + let method = format_ident!("span_{}", name); + + let msg = msg.unwrap_or_else(|| parse_quote! { _subdiag::suggestion }); + let msg = quote! { rustc_errors::fluent::#msg }; + let code = code.unwrap_or_else(|| quote! { String::new() }); + + Ok(quote! { #diag.#method(#span_field, #msg, #code, #applicability); }) + } + + /// Adds a spanned subdiagnostic by generating a `diag.span_$kind` call with the current slug + /// and `fluent_attr_identifier`. + fn add_spanned_subdiagnostic( + &self, + field_binding: TokenStream, + kind: &Ident, + fluent_attr_identifier: Path, + ) -> TokenStream { + let diag = &self.diag; + let fn_name = format_ident!("span_{}", kind); + quote! { + #diag.#fn_name( + #field_binding, + rustc_errors::fluent::#fluent_attr_identifier + ); + } + } + + /// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current slug + /// and `fluent_attr_identifier`. + fn add_subdiagnostic(&self, kind: &Ident, fluent_attr_identifier: Path) -> TokenStream { + let diag = &self.diag; + quote! { + #diag.#kind(rustc_errors::fluent::#fluent_attr_identifier); + } + } + + fn span_and_applicability_of_ty( + &self, + info: FieldInfo<'_>, + ) -> Result<(TokenStream, Option), DiagnosticDeriveError> { + 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 index d088402abc6da..0b1ededa77510 100644 --- a/compiler/rustc_macros/src/diagnostics/error.rs +++ b/compiler/rustc_macros/src/diagnostics/error.rs @@ -4,16 +4,16 @@ use quote::quote; use syn::{spanned::Spanned, Attribute, Error as SynError, Meta, NestedMeta}; #[derive(Debug)] -pub(crate) enum SessionDiagnosticDeriveError { +pub(crate) enum DiagnosticDeriveError { SynError(SynError), ErrorHandled, } -impl SessionDiagnosticDeriveError { +impl DiagnosticDeriveError { pub(crate) fn to_compile_error(self) -> TokenStream { match self { - SessionDiagnosticDeriveError::SynError(e) => e.to_compile_error(), - SessionDiagnosticDeriveError::ErrorHandled => { + DiagnosticDeriveError::SynError(e) => e.to_compile_error(), + DiagnosticDeriveError::ErrorHandled => { // Return ! to avoid having to create a blank DiagnosticBuilder to return when an // error has already been emitted to the compiler. quote! { @@ -24,9 +24,9 @@ impl SessionDiagnosticDeriveError { } } -impl From for SessionDiagnosticDeriveError { +impl From for DiagnosticDeriveError { fn from(e: SynError) -> Self { - SessionDiagnosticDeriveError::SynError(e) + DiagnosticDeriveError::SynError(e) } } @@ -34,9 +34,9 @@ impl From for SessionDiagnosticDeriveError { pub(crate) fn _throw_err( diag: Diagnostic, f: impl FnOnce(Diagnostic) -> Diagnostic, -) -> SessionDiagnosticDeriveError { +) -> DiagnosticDeriveError { f(diag).emit(); - SessionDiagnosticDeriveError::ErrorHandled + DiagnosticDeriveError::ErrorHandled } /// Helper function for printing `syn::Path` - doesn't handle arguments in paths and these are @@ -60,7 +60,7 @@ pub(crate) fn span_err(span: impl MultiSpan, msg: &str) -> Diagnostic { /// 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>`: +/// For methods that return a `Result<_, DiagnosticDeriveError>`: macro_rules! throw_span_err { ($span:expr, $msg:expr) => {{ throw_span_err!($span, $msg, |diag| diag) }}; ($span:expr, $msg:expr, $f:expr) => {{ @@ -87,7 +87,7 @@ pub(crate) fn invalid_attr(attr: &Attribute, meta: &Meta) -> Diagnostic { /// 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>`: +/// For methods that return a `Result<_, DiagnosticDeriveError>`: macro_rules! throw_invalid_attr { ($attr:expr, $meta:expr) => {{ throw_invalid_attr!($attr, $meta, |diag| diag) }}; ($attr:expr, $meta:expr, $f:expr) => {{ @@ -129,7 +129,7 @@ pub(crate) fn invalid_nested_attr(attr: &Attribute, nested: &NestedMeta) -> Diag /// 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>`: +/// For methods that return a `Result<_, DiagnosticDeriveError>`: 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) => {{ diff --git a/compiler/rustc_macros/src/diagnostics/mod.rs b/compiler/rustc_macros/src/diagnostics/mod.rs index 2eee4bfb5dd45..3997900266641 100644 --- a/compiler/rustc_macros/src/diagnostics/mod.rs +++ b/compiler/rustc_macros/src/diagnostics/mod.rs @@ -1,10 +1,11 @@ mod diagnostic; +mod diagnostic_builder; mod error; mod fluent; mod subdiagnostic; mod utils; -use diagnostic::SessionDiagnosticDerive; +use diagnostic::{LintDiagnosticDerive, SessionDiagnosticDerive}; pub(crate) use fluent::fluent_messages; use proc_macro2::TokenStream; use quote::format_ident; @@ -56,13 +57,55 @@ use synstructure::Structure; /// ``` /// /// 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(format_ident!("diag"), format_ident!("sess"), s).into_tokens() +} - SessionDiagnosticDerive::new(diag, sess, s).into_tokens() +/// Implements `#[derive(LintDiagnostic)]`, which allows for lints to be specified as a struct, +/// independent from the actual lint emitting code. +/// +/// ```ignore (rust) +/// #[derive(LintDiagnostic)] +/// #[lint(lint::atomic_ordering_invalid_fail_success)] +/// pub struct AtomicOrderingInvalidLint { +/// method: Symbol, +/// success_ordering: Symbol, +/// fail_ordering: Symbol, +/// #[label(lint::fail_label)] +/// fail_order_arg_span: Span, +/// #[label(lint::success_label)] +/// #[suggestion( +/// code = "std::sync::atomic::Ordering::{success_suggestion}", +/// applicability = "maybe-incorrect" +/// )] +/// success_order_arg_span: Span, +/// } +/// ``` +/// +/// ```fluent +/// lint-atomic-ordering-invalid-fail-success = `{$method}`'s success ordering must be at least as strong as its failure ordering +/// .fail-label = `{$fail_ordering}` failure ordering +/// .success-label = `{$success_ordering}` success ordering +/// .suggestion = consider using `{$success_suggestion}` success ordering instead +/// ``` +/// +/// Then, later, to emit the error: +/// +/// ```ignore (rust) +/// cx.struct_span_lint(INVALID_ATOMIC_ORDERING, fail_order_arg_span, AtomicOrderingInvalidLint { +/// method, +/// success_ordering, +/// fail_ordering, +/// fail_order_arg_span, +/// success_order_arg_span, +/// }); +/// ``` +/// +/// See rustc dev guide for more examples on using the `#[derive(LintDiagnostic)]`: +/// +pub fn lint_diagnostic_derive(s: Structure<'_>) -> TokenStream { + LintDiagnosticDerive::new(format_ident!("diag"), s).into_tokens() } /// Implements `#[derive(SessionSubdiagnostic)]`, which allows for labels, notes, helps and diff --git a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs index eab954a9c1bf9..2a5b6beba94bb 100644 --- a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs +++ b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs @@ -1,8 +1,7 @@ #![deny(unused_must_use)] use crate::diagnostics::error::{ - span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err, - SessionDiagnosticDeriveError, + span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err, DiagnosticDeriveError, }; use crate::diagnostics::utils::{ report_error_if_not_applied_to_applicability, report_error_if_not_applied_to_span, @@ -214,7 +213,7 @@ impl<'a> HasFieldMap for SessionSubdiagnosticDeriveBuilder<'a> { } impl<'a> SessionSubdiagnosticDeriveBuilder<'a> { - fn identify_kind(&mut self) -> Result<(), SessionDiagnosticDeriveError> { + fn identify_kind(&mut self) -> Result<(), DiagnosticDeriveError> { for attr in self.variant.ast().attrs { let span = attr.span().unwrap(); @@ -351,7 +350,7 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> { &mut self, binding: &BindingInfo<'_>, is_suggestion: bool, - ) -> Result { + ) -> Result { let ast = binding.ast(); let inner_ty = FieldInnerTy::from_type(&ast.ty); @@ -411,7 +410,7 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> { Ok(inner_ty.with(binding, generated)) } - fn into_tokens(&mut self) -> Result { + fn into_tokens(&mut self) -> Result { self.identify_kind()?; let Some(kind) = self.kind.map(|(kind, _)| kind) else { throw_span_err!( diff --git a/compiler/rustc_macros/src/diagnostics/utils.rs b/compiler/rustc_macros/src/diagnostics/utils.rs index 636bcf1f7b1d9..8977db4606c56 100644 --- a/compiler/rustc_macros/src/diagnostics/utils.rs +++ b/compiler/rustc_macros/src/diagnostics/utils.rs @@ -1,11 +1,11 @@ -use crate::diagnostics::error::{span_err, throw_span_err, SessionDiagnosticDeriveError}; +use crate::diagnostics::error::{span_err, throw_span_err, DiagnosticDeriveError}; use proc_macro::Span; use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens}; -use std::collections::BTreeSet; +use std::collections::{BTreeSet, HashMap}; use std::str::FromStr; use syn::{spanned::Spanned, Attribute, Meta, Type, TypeTuple}; -use synstructure::BindingInfo; +use synstructure::{BindingInfo, Structure}; /// Checks whether the type name of `ty` matches `name`. /// @@ -34,7 +34,7 @@ pub(crate) fn type_is_unit(ty: &Type) -> bool { pub(crate) fn report_type_error( attr: &Attribute, ty_name: &str, -) -> Result { +) -> Result { let name = attr.path.segments.last().unwrap().ident.to_string(); let meta = attr.parse_meta()?; @@ -59,7 +59,7 @@ fn report_error_if_not_applied_to_ty( info: &FieldInfo<'_>, path: &[&str], ty_name: &str, -) -> Result<(), SessionDiagnosticDeriveError> { +) -> Result<(), DiagnosticDeriveError> { if !type_matches_path(&info.ty, path) { report_type_error(attr, ty_name)?; } @@ -71,7 +71,7 @@ fn report_error_if_not_applied_to_ty( pub(crate) fn report_error_if_not_applied_to_applicability( attr: &Attribute, info: &FieldInfo<'_>, -) -> Result<(), SessionDiagnosticDeriveError> { +) -> Result<(), DiagnosticDeriveError> { report_error_if_not_applied_to_ty( attr, info, @@ -84,7 +84,7 @@ pub(crate) fn report_error_if_not_applied_to_applicability( pub(crate) fn report_error_if_not_applied_to_span( attr: &Attribute, info: &FieldInfo<'_>, -) -> Result<(), SessionDiagnosticDeriveError> { +) -> Result<(), DiagnosticDeriveError> { report_error_if_not_applied_to_ty(attr, info, &["rustc_span", "Span"], "`Span`") } @@ -166,10 +166,12 @@ pub(crate) struct FieldInfo<'a> { /// 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); + fn set_once(&mut self, _: (T, Span)); + + fn value(self) -> Option; } -impl SetOnce<(T, Span)> for Option<(T, Span)> { +impl SetOnce for Option<(T, Span)> { fn set_once(&mut self, (value, span): (T, Span)) { match self { None => { @@ -182,6 +184,10 @@ impl SetOnce<(T, Span)> for Option<(T, Span)> { } } } + + fn value(self) -> Option { + self.map(|(v, _)| v) + } } pub(crate) trait HasFieldMap { @@ -325,3 +331,20 @@ impl quote::ToTokens for Applicability { }); } } + +/// Build the mapping of field names to fields. This allows attributes to peek values from +/// other fields. +pub(crate) fn build_field_mapping<'a>(structure: &Structure<'a>) -> HashMap { + let mut fields_map = HashMap::new(); + + 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 }); + } + } + } + + fields_map +} diff --git a/compiler/rustc_macros/src/lib.rs b/compiler/rustc_macros/src/lib.rs index 7c8e3c6d14024..2902e99ebaf76 100644 --- a/compiler/rustc_macros/src/lib.rs +++ b/compiler/rustc_macros/src/lib.rs @@ -127,6 +127,7 @@ decl_derive!( // struct attributes warning, error, + lint, note, help, // field attributes @@ -139,6 +140,24 @@ decl_derive!( suggestion_hidden, suggestion_verbose)] => diagnostics::session_diagnostic_derive ); +decl_derive!( + [LintDiagnostic, attributes( + // struct attributes + warning, + error, + lint, + note, + help, + // field attributes + skip_arg, + primary_span, + label, + subdiagnostic, + suggestion, + suggestion_short, + suggestion_hidden, + suggestion_verbose)] => diagnostics::lint_diagnostic_derive +); decl_derive!( [SessionSubdiagnostic, attributes( // struct/variant attributes diff --git a/compiler/rustc_middle/src/lint.rs b/compiler/rustc_middle/src/lint.rs index 32c0a7e260569..4b156de410d88 100644 --- a/compiler/rustc_middle/src/lint.rs +++ b/compiler/rustc_middle/src/lint.rs @@ -2,10 +2,7 @@ use std::cmp; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; -use rustc_errors::{ - Diagnostic, DiagnosticBuilder, DiagnosticId, DiagnosticMessage, EmissionGuarantee, - ErrorGuaranteed, MultiSpan, -}; +use rustc_errors::{Diagnostic, DiagnosticId, LintDiagnosticBuilder, MultiSpan}; use rustc_hir::HirId; use rustc_index::vec::IndexVec; use rustc_query_system::ich::StableHashingContext; @@ -228,28 +225,6 @@ impl LintExpectation { } } -pub struct LintDiagnosticBuilder<'a, G: EmissionGuarantee>(DiagnosticBuilder<'a, G>); - -impl<'a, G: EmissionGuarantee> LintDiagnosticBuilder<'a, G> { - /// Return the inner `DiagnosticBuilder`, first setting the primary message to `msg`. - pub fn build(mut self, msg: impl Into) -> DiagnosticBuilder<'a, G> { - self.0.set_primary_message(msg); - self.0.set_is_lint(); - self.0 - } - - /// Create a `LintDiagnosticBuilder` from some existing `DiagnosticBuilder`. - pub fn new(err: DiagnosticBuilder<'a, G>) -> LintDiagnosticBuilder<'a, G> { - LintDiagnosticBuilder(err) - } -} - -impl<'a> LintDiagnosticBuilder<'a, ErrorGuaranteed> { - pub fn forget_guarantee(self) -> LintDiagnosticBuilder<'a, ()> { - LintDiagnosticBuilder(self.0.forget_guarantee()) - } -} - pub fn explain_lint_level_source( lint: &'static Lint, level: Level, diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index c3df9a66fe718..3d8b0763122f1 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -4,7 +4,7 @@ use crate::arena::Arena; use crate::dep_graph::{DepGraph, DepKind, DepKindStruct}; use crate::hir::place::Place as HirPlace; use crate::infer::canonical::{Canonical, CanonicalVarInfo, CanonicalVarInfos}; -use crate::lint::{struct_lint_level, LintDiagnosticBuilder, LintLevelSource}; +use crate::lint::{struct_lint_level, LintLevelSource}; use crate::middle::codegen_fn_attrs::CodegenFnAttrs; use crate::middle::resolve_lifetime; use crate::middle::stability; @@ -34,7 +34,7 @@ use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; use rustc_data_structures::steal::Steal; use rustc_data_structures::sync::{self, Lock, Lrc, WorkerLocal}; use rustc_data_structures::vec_map::VecMap; -use rustc_errors::{ErrorGuaranteed, MultiSpan}; +use rustc_errors::{DecorateLint, ErrorGuaranteed, LintDiagnosticBuilder, MultiSpan}; use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, LocalDefId, LOCAL_CRATE}; @@ -2787,6 +2787,18 @@ impl<'tcx> TyCtxt<'tcx> { } } + /// Emit a lint at `span` from a lint struct (some type that implements `DecorateLint`, + /// typically generated by `#[derive(LintDiagnostic)]`). + pub fn emit_spanned_lint( + self, + lint: &'static Lint, + hir_id: HirId, + span: impl Into, + decorator: impl for<'a> DecorateLint<'a, ()>, + ) { + self.struct_span_lint_hir(lint, hir_id, span, |diag| decorator.decorate_lint(diag)) + } + pub fn struct_span_lint_hir( self, lint: &'static Lint, @@ -2798,6 +2810,17 @@ impl<'tcx> TyCtxt<'tcx> { struct_lint_level(self.sess, lint, level, src, Some(span.into()), decorate); } + /// Emit a lint from a lint struct (some type that implements `DecorateLint`, typically + /// generated by `#[derive(LintDiagnostic)]`). + pub fn emit_lint( + self, + lint: &'static Lint, + id: HirId, + decorator: impl for<'a> DecorateLint<'a, ()>, + ) { + self.struct_lint_node(lint, id, |diag| decorator.decorate_lint(diag)) + } + pub fn struct_lint_node( self, lint: &'static Lint, diff --git a/compiler/rustc_mir_transform/src/check_const_item_mutation.rs b/compiler/rustc_mir_transform/src/check_const_item_mutation.rs index 097a6186cd57a..8838b14c53a58 100644 --- a/compiler/rustc_mir_transform/src/check_const_item_mutation.rs +++ b/compiler/rustc_mir_transform/src/check_const_item_mutation.rs @@ -1,5 +1,4 @@ -use rustc_errors::DiagnosticBuilder; -use rustc_middle::lint::LintDiagnosticBuilder; +use rustc_errors::{DiagnosticBuilder, LintDiagnosticBuilder}; use rustc_middle::mir::visit::Visitor; use rustc_middle::mir::*; use rustc_middle::ty::TyCtxt; diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 4e28d2b6001ed..65a2a18e02f8f 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -173,6 +173,7 @@ symbols! { DebugTuple, Decodable, Decoder, + DecorateLint, Default, Deref, DiagnosticMessage, diff --git a/compiler/rustc_trait_selection/src/traits/specialize/mod.rs b/compiler/rustc_trait_selection/src/traits/specialize/mod.rs index a1861529b5964..7efb0360b7f25 100644 --- a/compiler/rustc_trait_selection/src/traits/specialize/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/specialize/mod.rs @@ -16,9 +16,8 @@ use crate::infer::{InferCtxt, InferOk, TyCtxtInferExt}; use crate::traits::select::IntercrateAmbiguityCause; use crate::traits::{self, coherence, FutureCompatOverlapErrorKind, ObligationCause, TraitEngine}; use rustc_data_structures::fx::FxHashSet; -use rustc_errors::{struct_span_err, EmissionGuarantee}; +use rustc_errors::{struct_span_err, EmissionGuarantee, LintDiagnosticBuilder}; use rustc_hir::def_id::{DefId, LocalDefId}; -use rustc_middle::lint::LintDiagnosticBuilder; use rustc_middle::ty::subst::{InternalSubsts, Subst, SubstsRef}; use rustc_middle::ty::{self, ImplSubject, TyCtxt}; use rustc_session::lint::builtin::COHERENCE_LEAK_CHECK; diff --git a/src/librustdoc/passes/check_code_block_syntax.rs b/src/librustdoc/passes/check_code_block_syntax.rs index e3b349af661a1..8bf0971ccb5a5 100644 --- a/src/librustdoc/passes/check_code_block_syntax.rs +++ b/src/librustdoc/passes/check_code_block_syntax.rs @@ -1,7 +1,8 @@ //! Validates syntax inside Rust code blocks (\`\`\`rust). use rustc_data_structures::sync::{Lock, Lrc}; -use rustc_errors::{emitter::Emitter, Applicability, Diagnostic, Handler, LazyFallbackBundle}; -use rustc_middle::lint::LintDiagnosticBuilder; +use rustc_errors::{ + emitter::Emitter, Applicability, Diagnostic, Handler, LazyFallbackBundle, LintDiagnosticBuilder, +}; use rustc_parse::parse_stream_from_source_str; use rustc_session::parse::ParseSess; use rustc_span::hygiene::{AstPass, ExpnData, ExpnKind, LocalExpnId}; diff --git a/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs b/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs index 18283c19cb42c..56e95d70fd53d 100644 --- a/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs +++ b/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs @@ -17,7 +17,7 @@ use rustc_span::symbol::Ident; use rustc_span::Span; extern crate rustc_macros; -use rustc_macros::{SessionDiagnostic, SessionSubdiagnostic}; +use rustc_macros::{SessionDiagnostic, LintDiagnostic, SessionSubdiagnostic}; extern crate rustc_middle; use rustc_middle::ty::Ty; @@ -535,3 +535,20 @@ struct LabelWithTrailingList { //~^ ERROR `#[label(...)]` is not a valid attribute span: Span, } + +#[derive(SessionDiagnostic)] +#[lint(typeck::ambiguous_lifetime_bound)] +//~^ ERROR only `#[error(..)]` and `#[warn(..)]` are supported +struct LintsBad { +} + +#[derive(LintDiagnostic)] +#[lint(typeck::ambiguous_lifetime_bound)] +struct LintsGood { +} + +#[derive(LintDiagnostic)] +#[error(typeck::ambiguous_lifetime_bound)] +//~^ ERROR only `#[lint(..)]` is supported +struct ErrorsBad { +} diff --git a/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr b/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr index 9e2e34e4bec81..98c22af387e85 100644 --- a/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr +++ b/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr @@ -363,6 +363,28 @@ error: `#[label(...)]` is not a valid attribute LL | #[label(typeck::label, foo("..."))] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +error: only `#[error(..)]` and `#[warn(..)]` are supported + --> $DIR/diagnostic-derive.rs:540:1 + | +LL | / #[lint(typeck::ambiguous_lifetime_bound)] +LL | | +LL | | struct LintsBad { +LL | | } + | |_^ + | + = help: use the `#[error(...)]` attribute to create a error + +error: only `#[lint(..)]` is supported + --> $DIR/diagnostic-derive.rs:551:1 + | +LL | / #[error(typeck::ambiguous_lifetime_bound)] +LL | | +LL | | struct ErrorsBad { +LL | | } + | |_^ + | + = help: use the `#[lint(...)]` attribute to create a lint + error: cannot find attribute `nonsense` in this scope --> $DIR/diagnostic-derive.rs:53:3 | @@ -395,7 +417,7 @@ LL | arg: impl IntoDiagnosticArg, | ^^^^^^^^^^^^^^^^^ required by this bound in `DiagnosticBuilder::<'a, G>::set_arg` = note: this error originates in the derive macro `SessionDiagnostic` (in Nightly builds, run with -Z macro-backtrace for more info) -error: aborting due to 46 previous errors +error: aborting due to 48 previous errors Some errors have detailed explanations: E0277, E0425. For more information about an error, try `rustc --explain E0277`.