From e3d63203a34cfb5d8b309894005648868053b1b7 Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Fri, 29 Apr 2022 19:43:15 +0200 Subject: [PATCH 01/13] Only compute DefKind through the query. --- compiler/rustc_middle/src/hir/map/mod.rs | 3 ++- compiler/rustc_privacy/src/lib.rs | 2 +- compiler/rustc_query_impl/src/plumbing.rs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_middle/src/hir/map/mod.rs b/compiler/rustc_middle/src/hir/map/mod.rs index c3768d5b2d691..85e3b7424ada9 100644 --- a/compiler/rustc_middle/src/hir/map/mod.rs +++ b/compiler/rustc_middle/src/hir/map/mod.rs @@ -225,7 +225,8 @@ impl<'hir> Map<'hir> { self.tcx.definitions_untracked().iter_local_def_id() } - pub fn opt_def_kind(self, local_def_id: LocalDefId) -> Option { + /// Do not call this function directly. The query should be called. + pub(super) fn opt_def_kind(self, local_def_id: LocalDefId) -> Option { let hir_id = self.local_def_id_to_hir_id(local_def_id); let def_kind = match self.find(hir_id)? { Node::Item(item) => match item.kind { diff --git a/compiler/rustc_privacy/src/lib.rs b/compiler/rustc_privacy/src/lib.rs index f5e323e2bc40e..5ac8ac87fbbff 100644 --- a/compiler/rustc_privacy/src/lib.rs +++ b/compiler/rustc_privacy/src/lib.rs @@ -467,7 +467,7 @@ impl<'tcx> EmbargoVisitor<'tcx> { } let macro_module_def_id = self.tcx.local_parent(local_def_id); - if self.tcx.hir().opt_def_kind(macro_module_def_id) != Some(DefKind::Mod) { + if self.tcx.opt_def_kind(macro_module_def_id) != Some(DefKind::Mod) { // The macro's parent doesn't correspond to a `mod`, return early (#63164, #65252). return; } diff --git a/compiler/rustc_query_impl/src/plumbing.rs b/compiler/rustc_query_impl/src/plumbing.rs index 66f4508f6b497..7f38f32e25f02 100644 --- a/compiler/rustc_query_impl/src/plumbing.rs +++ b/compiler/rustc_query_impl/src/plumbing.rs @@ -291,7 +291,7 @@ macro_rules! define_queries { // accidentally triggering an infinite query loop. let def_kind = key.key_as_def_id() .and_then(|def_id| def_id.as_local()) - .and_then(|def_id| tcx.hir().opt_def_kind(def_id)); + .map(|def_id| tcx.def_kind(def_id)); let hash = || { let mut hcx = tcx.create_stable_hashing_context(); let mut hasher = StableHasher::new(); From 84ec77769f4bf0bbd1b913747e9a1a7226a6116f Mon Sep 17 00:00:00 2001 From: David Wood Date: Wed, 29 Jun 2022 15:42:12 +0100 Subject: [PATCH 02/13] macros: fix documentation link for diag derive Signed-off-by: David Wood --- compiler/rustc_macros/src/diagnostics/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_macros/src/diagnostics/mod.rs b/compiler/rustc_macros/src/diagnostics/mod.rs index 2eee4bfb5dd45..d60de7150c529 100644 --- a/compiler/rustc_macros/src/diagnostics/mod.rs +++ b/compiler/rustc_macros/src/diagnostics/mod.rs @@ -56,7 +56,7 @@ 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"); From 2874f09534b39b98b23a649e8300e8bdd2fb1823 Mon Sep 17 00:00:00 2001 From: David Wood Date: Wed, 29 Jun 2022 15:50:26 +0100 Subject: [PATCH 03/13] lint: `LintDiagnosticBuilder` into `rustc_errors` Signed-off-by: David Wood --- .../rustc_errors/src/diagnostic_builder.rs | 23 ++++++++++++++++ compiler/rustc_errors/src/lib.rs | 2 +- compiler/rustc_lint/src/builtin.rs | 5 ++-- compiler/rustc_lint/src/context.rs | 3 +-- compiler/rustc_lint/src/levels.rs | 6 ++--- compiler/rustc_middle/src/lint.rs | 27 +------------------ compiler/rustc_middle/src/ty/context.rs | 4 +-- .../src/check_const_item_mutation.rs | 3 +-- .../src/traits/specialize/mod.rs | 3 +-- .../passes/check_code_block_syntax.rs | 5 ++-- 10 files changed, 39 insertions(+), 42 deletions(-) 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..a64dd42ec775e 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -373,7 +373,7 @@ pub use diagnostic::{ AddSubdiagnostic, 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/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..1e1c423982b2d 100644 --- a/compiler/rustc_lint/src/context.rs +++ b/compiler/rustc_lint/src/context.rs @@ -22,12 +22,11 @@ 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, 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}; 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_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..e0942f86f1688 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::{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}; 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_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}; From 540eaf985d08b140bf792b7c68731a641ecb026a Mon Sep 17 00:00:00 2001 From: David Wood Date: Wed, 29 Jun 2022 16:07:46 +0100 Subject: [PATCH 04/13] errors: introduce `DecorateLint` Add a new trait to be generated by diagnostic derives which uses a `LintDiagnosticBuilder`. Signed-off-by: David Wood --- compiler/rustc_errors/src/diagnostic.rs | 12 ++++++++++-- compiler/rustc_errors/src/lib.rs | 2 +- compiler/rustc_lint/src/context.rs | 22 +++++++++++++++++++++- compiler/rustc_lint/src/internal.rs | 2 +- compiler/rustc_middle/src/ty/context.rs | 25 ++++++++++++++++++++++++- compiler/rustc_span/src/symbol.rs | 1 + 6 files changed, 58 insertions(+), 6 deletions(-) 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/lib.rs b/compiler/rustc_errors/src/lib.rs index a64dd42ec775e..ffe4ecebb2e36 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -370,7 +370,7 @@ 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, LintDiagnosticBuilder}; diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs index 1e1c423982b2d..83328093e9fa8 100644 --- a/compiler/rustc_lint/src/context.rs +++ b/compiler/rustc_lint/src/context.rs @@ -22,7 +22,9 @@ 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, LintDiagnosticBuilder, 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}; @@ -870,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, @@ -878,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_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index e0942f86f1688..3d8b0763122f1 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -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, LintDiagnosticBuilder, 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_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, From 406579ae1326b5a27d034febe75d728c108e707e Mon Sep 17 00:00:00 2001 From: David Wood Date: Wed, 29 Jun 2022 16:13:01 +0100 Subject: [PATCH 05/13] macros: introduce `build_field_mapping` Move the logic for building a field mapping (which is used by the building of format strings in `suggestion` annotations) into a helper function. Signed-off-by: David Wood --- .../src/diagnostics/diagnostic.rs | 21 +++---------------- .../rustc_macros/src/diagnostics/utils.rs | 21 +++++++++++++++++-- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic.rs b/compiler/rustc_macros/src/diagnostics/diagnostic.rs index d0c8652718969..00f3951d9a024 100644 --- a/compiler/rustc_macros/src/diagnostics/diagnostic.rs +++ b/compiler/rustc_macros/src/diagnostics/diagnostic.rs @@ -5,8 +5,8 @@ use crate::diagnostics::error::{ 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, + build_field_mapping, 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}; @@ -25,26 +25,11 @@ pub(crate) struct SessionDiagnosticDerive<'a> { impl<'a> SessionDiagnosticDerive<'a> { pub(crate) fn new(diag: syn::Ident, sess: syn::Ident, structure: Structure<'a>) -> Self { - // Build the mapping of field names to fields. This allows attributes to peek values from - // other fields. - let mut fields_map = HashMap::new(); - - // Convenience bindings. - let ast = structure.ast(); - - if let syn::Data::Struct(syn::DataStruct { fields, .. }) = &ast.data { - for field in fields.iter() { - if let Some(ident) = &field.ident { - fields_map.insert(ident.to_string(), quote! { &self.#ident }); - } - } - } - Self { builder: SessionDiagnosticDeriveBuilder { diag, sess, - fields: fields_map, + fields: build_field_mapping(&structure), kind: None, code: None, slug: None, diff --git a/compiler/rustc_macros/src/diagnostics/utils.rs b/compiler/rustc_macros/src/diagnostics/utils.rs index 636bcf1f7b1d9..24204556c984c 100644 --- a/compiler/rustc_macros/src/diagnostics/utils.rs +++ b/compiler/rustc_macros/src/diagnostics/utils.rs @@ -2,10 +2,10 @@ use crate::diagnostics::error::{span_err, throw_span_err, SessionDiagnosticDeriv 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`. /// @@ -325,3 +325,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 +} From 7f9d8480d665b8dcc7b1359372e394d455b6e29a Mon Sep 17 00:00:00 2001 From: David Wood Date: Wed, 29 Jun 2022 16:22:27 +0100 Subject: [PATCH 06/13] macros: move `sess` out of builder `sess` field of `SessionDiagnosticDeriveBuilder` is never actually used in the builder's member functions, so it doesn't need to be a field. Signed-off-by: David Wood --- compiler/rustc_macros/src/diagnostics/diagnostic.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic.rs b/compiler/rustc_macros/src/diagnostics/diagnostic.rs index 00f3951d9a024..27344087fecc5 100644 --- a/compiler/rustc_macros/src/diagnostics/diagnostic.rs +++ b/compiler/rustc_macros/src/diagnostics/diagnostic.rs @@ -20,6 +20,7 @@ use synstructure::{BindingInfo, Structure}; /// The central struct for constructing the `into_diagnostic` method from an annotated struct. pub(crate) struct SessionDiagnosticDerive<'a> { structure: Structure<'a>, + sess: syn::Ident, builder: SessionDiagnosticDeriveBuilder, } @@ -28,18 +29,18 @@ impl<'a> SessionDiagnosticDerive<'a> { Self { builder: SessionDiagnosticDeriveBuilder { diag, - sess, 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; @@ -96,7 +97,7 @@ impl<'a> SessionDiagnosticDerive<'a> { .each(|field_binding| builder.generate_field_attrs_code(field_binding)); let span = ast.span().unwrap(); - let (diag, sess) = (&builder.diag, &builder.sess); + let diag = &builder.diag; let init = match (builder.kind, builder.slug) { (None, _) => { span_err(span, "diagnostic kind not specified") @@ -159,7 +160,6 @@ impl<'a> SessionDiagnosticDerive<'a> { } }; - let sess = &builder.sess; structure.gen_impl(quote! { gen impl<'__session_diagnostic_sess> rustc_session::SessionDiagnostic<'__session_diagnostic_sess, #param_ty> for @Self @@ -200,8 +200,6 @@ impl SessionDiagnosticKind { /// 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, From 9d864c8d56fee09abae2be2bed1d4dc1a86b457b Mon Sep 17 00:00:00 2001 From: David Wood Date: Thu, 30 Jun 2022 08:57:45 +0100 Subject: [PATCH 07/13] macros: add diagnostic derive for lints `SessionDiagnostic` isn't suitable for use on lints as whether or not it creates an error or a warning is decided at compile-time by the macro, whereas lints decide this at runtime based on the location of the lint being reported (as it will depend on the user's `allow`/`deny` attributes, etc). Re-using most of the machinery for `SessionDiagnostic`, this macro introduces a `LintDiagnostic` derive which implements a `DecorateLint` trait, taking a `LintDiagnosticBuilder` and adding to the lint according to the diagnostic struct. --- Cargo.lock | 1 + compiler/rustc_lint/Cargo.toml | 1 + compiler/rustc_lint/src/types.rs | 22 +- .../src/diagnostics/diagnostic.rs | 677 +++--------------- .../src/diagnostics/diagnostic_builder.rs | 590 +++++++++++++++ .../rustc_macros/src/diagnostics/error.rs | 22 +- compiler/rustc_macros/src/diagnostics/mod.rs | 53 +- .../src/diagnostics/subdiagnostic.rs | 9 +- .../rustc_macros/src/diagnostics/utils.rs | 20 +- compiler/rustc_macros/src/lib.rs | 19 + .../session-diagnostic/diagnostic-derive.rs | 19 +- .../diagnostic-derive.stderr | 24 +- 12 files changed, 845 insertions(+), 612 deletions(-) create mode 100644 compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs 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_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/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 27344087fecc5..027f377b0acc7 100644 --- a/compiler/rustc_macros/src/diagnostics/diagnostic.rs +++ b/compiler/rustc_macros/src/diagnostics/diagnostic.rs @@ -1,33 +1,24 @@ #![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::{ - build_field_mapping, 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>, sess: syn::Ident, - builder: SessionDiagnosticDeriveBuilder, + builder: DiagnosticDeriveBuilder, } impl<'a> SessionDiagnosticDerive<'a> { pub(crate) fn new(diag: syn::Ident, sess: syn::Ident, structure: Structure<'a>) -> Self { Self { - builder: SessionDiagnosticDeriveBuilder { + builder: DiagnosticDeriveBuilder { diag, fields: build_field_mapping(&structure), kind: None, @@ -43,69 +34,21 @@ impl<'a> SessionDiagnosticDerive<'a> { 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 = &builder.diag; - let init = match (builder.kind, builder.slug) { + 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 \ @@ -113,14 +56,20 @@ impl<'a> SessionDiagnosticDerive<'a> { kind.descr() )) .emit(); - return SessionDiagnosticDeriveError::ErrorHandled.to_compile_error(); + return DiagnosticDeriveError::ErrorHandled.to_compile_error(); + } + (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((SessionDiagnosticKind::Error, _)), Some((slug, _))) => { + (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); } @@ -139,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!(), }; @@ -154,7 +105,7 @@ 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) } @@ -176,523 +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 { - /// 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 d60de7150c529..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; @@ -58,11 +59,53 @@ 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 24204556c984c..8977db4606c56 100644 --- a/compiler/rustc_macros/src/diagnostics/utils.rs +++ b/compiler/rustc_macros/src/diagnostics/utils.rs @@ -1,4 +1,4 @@ -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}; @@ -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 { 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/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`. From 974b1e3e5185e9c9205c8fae4eed3f902d4958af Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Tue, 5 Jul 2022 22:52:21 +0200 Subject: [PATCH 08/13] Clarify the behaviour from inside the query system. --- compiler/rustc_query_impl/src/plumbing.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_query_impl/src/plumbing.rs b/compiler/rustc_query_impl/src/plumbing.rs index 7f38f32e25f02..cf1bef206dd2a 100644 --- a/compiler/rustc_query_impl/src/plumbing.rs +++ b/compiler/rustc_query_impl/src/plumbing.rs @@ -287,11 +287,14 @@ macro_rules! define_queries { } else { Some(key.default_span(*tcx)) }; - // Use `tcx.hir().opt_def_kind()` to reduce the chance of - // accidentally triggering an infinite query loop. - let def_kind = key.key_as_def_id() - .and_then(|def_id| def_id.as_local()) - .map(|def_id| tcx.def_kind(def_id)); + let def_kind = if kind == dep_graph::DepKind::opt_def_kind { + // Try to avoid infinite recursion. + None + } else { + key.key_as_def_id() + .and_then(|def_id| def_id.as_local()) + .and_then(|def_id| tcx.opt_def_kind(def_id)) + }; let hash = || { let mut hcx = tcx.create_stable_hashing_context(); let mut hasher = StableHasher::new(); From 4687afa480846c155af1598940a97d0286a2f1d5 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 5 Jul 2022 17:48:43 -0400 Subject: [PATCH 09/13] fix type in function name --- compiler/rustc_const_eval/src/interpret/machine.rs | 4 ++-- compiler/rustc_const_eval/src/interpret/operator.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/machine.rs b/compiler/rustc_const_eval/src/interpret/machine.rs index 54c9e99cf97c8..4661a7c28286d 100644 --- a/compiler/rustc_const_eval/src/interpret/machine.rs +++ b/compiler/rustc_const_eval/src/interpret/machine.rs @@ -145,7 +145,7 @@ pub trait Machine<'mir, 'tcx>: Sized { } /// Whether CheckedBinOp MIR statements should actually check for overflow. - fn check_binop_checks_overflow(_ecx: &InterpCx<'mir, 'tcx, Self>) -> bool; + fn checked_binop_checks_overflow(_ecx: &InterpCx<'mir, 'tcx, Self>) -> bool; /// Entry point for obtaining the MIR of anything that should get evaluated. /// So not just functions and shims, but also const/static initializers, anonymous @@ -472,7 +472,7 @@ pub macro compile_time_machine(<$mir: lifetime, $tcx: lifetime>) { } #[inline(always)] - fn check_binop_checks_overflow(_ecx: &InterpCx<$mir, $tcx, Self>) -> bool { + fn checked_binop_checks_overflow(_ecx: &InterpCx<$mir, $tcx, Self>) -> bool { true } diff --git a/compiler/rustc_const_eval/src/interpret/operator.rs b/compiler/rustc_const_eval/src/interpret/operator.rs index 942bdb36645bd..f0c113376eab1 100644 --- a/compiler/rustc_const_eval/src/interpret/operator.rs +++ b/compiler/rustc_const_eval/src/interpret/operator.rs @@ -33,7 +33,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { // As per https://github.com/rust-lang/rust/pull/98738, we always return `false` in the 2nd // component when overflow checking is disabled. let overflowed = - overflowed && (force_overflow_checks || M::check_binop_checks_overflow(self)); + overflowed && (force_overflow_checks || M::checked_binop_checks_overflow(self)); // Write the result to `dest`. if let Abi::ScalarPair(..) = dest.layout.abi { // We can use the optimized path and avoid `place_field` (which might do From 503c66992737d81e64b38a123ad9ac00dd88cf38 Mon Sep 17 00:00:00 2001 From: ClementTsang Date: Tue, 5 Jul 2022 21:01:53 -0400 Subject: [PATCH 10/13] fix typo in note about multiple inaccessible type aliases Mainly intended as a small typo fix ("aliass" -> "aliases") for the case where a type cannot be found in scope, and there are multiple inaccessible type aliases that match the missing type. In general this change would use the correct plural form in this scenario for words that end with 's'. --- compiler/rustc_resolve/src/diagnostics.rs | 4 ++- .../ui/imports/inaccessible_type_aliases.rs | 14 +++++++++ .../imports/inaccessible_type_aliases.stderr | 30 +++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 src/test/ui/imports/inaccessible_type_aliases.rs create mode 100644 src/test/ui/imports/inaccessible_type_aliases.stderr diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs index 4fbbd9deaebb4..0f58206eee9f3 100644 --- a/compiler/rustc_resolve/src/diagnostics.rs +++ b/compiler/rustc_resolve/src/diagnostics.rs @@ -2589,8 +2589,10 @@ fn show_candidates( } else { "item".to_string() }; + let plural_descr = + if descr.ends_with("s") { format!("{}es", descr) } else { format!("{}s", descr) }; - let mut msg = format!("{}these {}s exist but are inaccessible", prefix, descr); + let mut msg = format!("{}these {} exist but are inaccessible", prefix, plural_descr); let mut has_colon = false; let mut spans = Vec::new(); diff --git a/src/test/ui/imports/inaccessible_type_aliases.rs b/src/test/ui/imports/inaccessible_type_aliases.rs new file mode 100644 index 0000000000000..c3d4214e282d7 --- /dev/null +++ b/src/test/ui/imports/inaccessible_type_aliases.rs @@ -0,0 +1,14 @@ +mod a { + type Foo = u64; + type Bar = u64; +} + +mod b { + type Foo = u64; +} + +fn main() { + let x: Foo = 100; //~ ERROR: cannot find type `Foo` in this scope + let y: Bar = 100; //~ ERROR: cannot find type `Bar` in this scope + println!("x: {}, y: {}", x, y); +} diff --git a/src/test/ui/imports/inaccessible_type_aliases.stderr b/src/test/ui/imports/inaccessible_type_aliases.stderr new file mode 100644 index 0000000000000..ef224246061d9 --- /dev/null +++ b/src/test/ui/imports/inaccessible_type_aliases.stderr @@ -0,0 +1,30 @@ +error[E0412]: cannot find type `Foo` in this scope + --> $DIR/inaccessible_type_aliases.rs:11:12 + | +LL | let x: Foo = 100; + | ^^^ not found in this scope + | +note: these type aliases exist but are inaccessible + --> $DIR/inaccessible_type_aliases.rs:2:5 + | +LL | type Foo = u64; + | ^^^^^^^^^^^^^^^ `a::Foo`: not accessible +... +LL | type Foo = u64; + | ^^^^^^^^^^^^^^^ `b::Foo`: not accessible + +error[E0412]: cannot find type `Bar` in this scope + --> $DIR/inaccessible_type_aliases.rs:12:12 + | +LL | let y: Bar = 100; + | ^^^ not found in this scope + | +note: type alias `a::Bar` exists but is inaccessible + --> $DIR/inaccessible_type_aliases.rs:3:5 + | +LL | type Bar = u64; + | ^^^^^^^^^^^^^^^ not accessible + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0412`. From 8f867c5445f22fcb119b46219fecc886d4f124b2 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 5 Jul 2022 20:26:26 -0400 Subject: [PATCH 11/13] finally enable Scalar layout sanity checks --- compiler/rustc_const_eval/src/interpret/operand.rs | 5 ++--- compiler/rustc_const_eval/src/interpret/place.rs | 2 +- compiler/rustc_middle/src/ty/layout.rs | 5 ++--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/operand.rs b/compiler/rustc_const_eval/src/interpret/operand.rs index d48f6521ba2aa..145d95a40ea8a 100644 --- a/compiler/rustc_const_eval/src/interpret/operand.rs +++ b/compiler/rustc_const_eval/src/interpret/operand.rs @@ -306,9 +306,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { s.is_ptr() || (number_may_have_provenance && size == self.pointer_size()) }; if let Some(s) = scalar_layout { - //FIXME(#96185): let size = s.size(self); - //FIXME(#96185): assert_eq!(size, mplace.layout.size, "abi::Scalar size does not match layout size"); - let size = mplace.layout.size; //FIXME(#96185): remove this line + let size = s.size(self); + assert_eq!(size, mplace.layout.size, "abi::Scalar size does not match layout size"); let scalar = alloc.read_scalar(alloc_range(Size::ZERO, size), read_provenance(s, size))?; return Ok(Some(ImmTy { imm: scalar.into(), layout: mplace.layout })); diff --git a/compiler/rustc_const_eval/src/interpret/place.rs b/compiler/rustc_const_eval/src/interpret/place.rs index 926afe757ed60..f4dc18af23c99 100644 --- a/compiler/rustc_const_eval/src/interpret/place.rs +++ b/compiler/rustc_const_eval/src/interpret/place.rs @@ -793,7 +793,7 @@ where ) }; let size = s.size(&tcx); - //FIXME(#96185): assert_eq!(dest.layout.size, size, "abi::Scalar size does not match layout size"); + assert_eq!(size, dest.layout.size, "abi::Scalar size does not match layout size"); alloc.write_scalar(alloc_range(Size::ZERO, size), scalar) } Immediate::ScalarPair(a_val, b_val) => { diff --git a/compiler/rustc_middle/src/ty/layout.rs b/compiler/rustc_middle/src/ty/layout.rs index e20f94b15c6ac..065ed76536bee 100644 --- a/compiler/rustc_middle/src/ty/layout.rs +++ b/compiler/rustc_middle/src/ty/layout.rs @@ -235,9 +235,8 @@ fn sanity_check_layout<'tcx>( if cfg!(debug_assertions) { fn check_layout_abi<'tcx>(tcx: TyCtxt<'tcx>, layout: Layout<'tcx>) { match layout.abi() { - Abi::Scalar(_scalar) => { + Abi::Scalar(scalar) => { // No padding in scalars. - /* FIXME(#96185): assert_eq!( layout.align().abi, scalar.align(&tcx).abi, @@ -247,7 +246,7 @@ fn sanity_check_layout<'tcx>( layout.size(), scalar.size(&tcx), "size mismatch between ABI and layout in {layout:#?}" - );*/ + ); } Abi::Vector { count, element } => { // No padding in vectors. Alignment can be strengthened, though. From 2a1a718baabcc1fac08ed9a6ac3e29d5b156c532 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 5 Jul 2022 20:29:37 -0400 Subject: [PATCH 12/13] add test --- src/test/ui/consts/const-enum-cast.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/test/ui/consts/const-enum-cast.rs b/src/test/ui/consts/const-enum-cast.rs index a3255c2f601b3..3996849514412 100644 --- a/src/test/ui/consts/const-enum-cast.rs +++ b/src/test/ui/consts/const-enum-cast.rs @@ -4,6 +4,19 @@ enum A { A1, A2 } enum B { B1=4, B2=2 } +#[allow(dead_code)] +#[repr(align(8))] +enum Aligned { + Zero = 0, + One = 1, +} + +// regression test for https://github.com/rust-lang/rust/issues/96185 +const X: u8 = { + let aligned = Aligned::Zero; + aligned as u8 +}; + pub fn main () { static c1: isize = A::A2 as isize; static c2: isize = B::B2 as isize; @@ -23,4 +36,6 @@ pub fn main () { assert_eq!(c2_2, 4); assert_eq!(a1_2, 0); assert_eq!(a2_2, 4); + + assert_eq!(X, 0); } From 31629860e8bada8156c207b77b91901fc274acf7 Mon Sep 17 00:00:00 2001 From: fee1-dead Date: Wed, 6 Jul 2022 17:02:58 +0800 Subject: [PATCH 13/13] Fix double space --- compiler/rustc_middle/src/hir/map/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_middle/src/hir/map/mod.rs b/compiler/rustc_middle/src/hir/map/mod.rs index 85e3b7424ada9..fb003010292b0 100644 --- a/compiler/rustc_middle/src/hir/map/mod.rs +++ b/compiler/rustc_middle/src/hir/map/mod.rs @@ -225,7 +225,7 @@ impl<'hir> Map<'hir> { self.tcx.definitions_untracked().iter_local_def_id() } - /// Do not call this function directly. The query should be called. + /// Do not call this function directly. The query should be called. pub(super) fn opt_def_kind(self, local_def_id: LocalDefId) -> Option { let hir_id = self.local_def_id_to_hir_id(local_def_id); let def_kind = match self.find(hir_id)? {