From 2ca9037b6154227306d9b40a87d79e691f1c9126 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Fri, 6 Aug 2021 23:28:58 +0200 Subject: [PATCH] Set `LintExpectationId` in level and collect fulfilled ones (RFC-2383) * Collect lint expectations and set expectation ID in level (RFC-2383) * Collect IDs of fulfilled lint expectations from diagnostics (RFC 2383) --- compiler/rustc_errors/src/diagnostic.rs | 6 +--- compiler/rustc_errors/src/lib.rs | 19 ++++++++++- compiler/rustc_lint/src/levels.rs | 38 +++++++++++++++++----- compiler/rustc_lint_defs/src/lib.rs | 4 +-- compiler/rustc_middle/src/lint.rs | 42 +++++++++++++++++++++++-- compiler/rustc_middle/src/ty/context.rs | 6 +++- compiler/rustc_session/src/session.rs | 6 +++- 7 files changed, 102 insertions(+), 19 deletions(-) diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs index 802f2560601..a59d91ea789 100644 --- a/compiler/rustc_errors/src/diagnostic.rs +++ b/compiler/rustc_errors/src/diagnostic.rs @@ -133,11 +133,7 @@ pub fn is_error(&self) -> bool { | Level::Error { .. } | Level::FailureNote => true, - Level::Warning - | Level::Note - | Level::Help - | Level::Allow - | Level::Expect(_) => false, + Level::Warning | Level::Note | Level::Help | Level::Allow | Level::Expect(_) => false, } } diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index 41d2b285997..83e52e002ae 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -451,6 +451,15 @@ struct HandlerInner { deduplicated_warn_count: usize, future_breakage_diagnostics: Vec, + + /// Lint [`Diagnostic`]s can be expected as described in [RFC-2383]. An + /// expected diagnostic will have the level `Expect` which additionally + /// carries the [`LintExpectationId`] of the expectation that can be + /// marked as fulfilled. This is a collection of all [`LintExpectationId`]s + /// that have been marked as fulfilled this way. + /// + /// [RFC-2383]: https://rust-lang.github.io/rfcs/2383-lint-reasons.html + fulfilled_expectations: FxHashSet, } /// A key denoting where from a diagnostic was stashed. @@ -571,6 +580,7 @@ pub fn with_emitter_and_flags( emitted_diagnostics: Default::default(), stashed_diagnostics: Default::default(), future_breakage_diagnostics: Vec::new(), + fulfilled_expectations: Default::default(), }), } } @@ -912,6 +922,12 @@ pub fn emit_future_breakage_report(&self, diags: Vec) { pub fn emit_unused_externs(&self, lint_level: &str, unused_externs: &[&str]) { self.inner.borrow_mut().emit_unused_externs(lint_level, unused_externs) } + + /// This methods steals all [`LintExpectationId`]s that are stored inside + /// [`HandlerInner`] and indicate that the linked expectation has been fulfilled. + pub fn steal_fulfilled_expectation_ids(&self) -> FxHashSet { + std::mem::take(&mut self.inner.borrow_mut().fulfilled_expectations) + } } impl HandlerInner { @@ -959,7 +975,8 @@ fn emit_diagnostic(&mut self, diagnostic: &Diagnostic) { (*TRACK_DIAGNOSTICS)(diagnostic); - if let Level::Expect(_) = diagnostic.level { + if let Level::Expect(expectation_id) = diagnostic.level { + self.fulfilled_expectations.insert(expectation_id); return; } else if diagnostic.level == Allow { return; diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs index d7cdb08d817..a876e35c0a8 100644 --- a/compiler/rustc_lint/src/levels.rs +++ b/compiler/rustc_lint/src/levels.rs @@ -7,17 +7,15 @@ use rustc_hir as hir; use rustc_hir::{intravisit, HirId}; use rustc_middle::hir::nested_filter; -use rustc_middle::lint::LevelAndSource; -use rustc_middle::lint::LintDiagnosticBuilder; use rustc_middle::lint::{ - struct_lint_level, LintLevelMap, LintLevelSets, LintLevelSource, LintSet, LintStackIndex, - COMMAND_LINE, + struct_lint_level, LevelAndSource, LintDiagnosticBuilder, LintExpectation, LintLevelMap, + LintLevelSets, LintLevelSource, LintSet, LintStackIndex, COMMAND_LINE, }; use rustc_middle::ty::query::Providers; use rustc_middle::ty::{RegisteredTools, TyCtxt}; use rustc_session::lint::{ builtin::{self, FORBIDDEN_LINT_GROUPS}, - Level, Lint, LintId, + Level, Lint, LintExpectationId, LintId, }; use rustc_session::parse::feature_err; use rustc_session::Session; @@ -44,6 +42,7 @@ fn lint_levels(tcx: TyCtxt<'_>, (): ()) -> LintLevelMap { pub struct LintLevelsBuilder<'s> { sess: &'s Session, + lint_expectations: FxHashMap, sets: LintLevelSets, id_to_set: FxHashMap, cur: LintStackIndex, @@ -66,6 +65,7 @@ pub fn new( ) -> Self { let mut builder = LintLevelsBuilder { sess, + lint_expectations: Default::default(), sets: LintLevelSets::new(), cur: COMMAND_LINE, id_to_set: Default::default(), @@ -231,7 +231,7 @@ pub(crate) fn push(&mut self, attrs: &[ast::Attribute], is_crate_node: bool) -> let sess = self.sess; let bad_attr = |span| struct_span_err!(sess, span, E0452, "malformed lint attribute input"); for attr in attrs { - let Some(level) = Level::from_symbol(attr.name_or_empty()) else { + let Some(level) = Level::from_symbol(attr.name_or_empty(), attr.id.as_u32()) else { continue }; @@ -476,6 +476,26 @@ pub(crate) fn push(&mut self, attrs: &[ast::Attribute], is_crate_node: bool) -> } } } + + if !specs.is_empty() { + // Only lints that are currently registered in the lint store + // have been found and added to `specs`. Creating the expectation + // here ensures that it can be fulfilled during this compilation + // session. + if let Level::Expect(expect_id) = level { + let has_lints = specs + .values() + .any(|(lvl, _src)| matches!(lvl, Level::Expect(check_id) if check_id.eq(&expect_id))); + + if has_lints { + let lint = builtin::UNFULFILLED_LINT_EXPECTATIONS; + let (lvl, src) = + self.sets.get_lint_level(lint, self.cur, Some(&specs), &sess); + let expectation = LintExpectation::new(reason, attr.span, lvl, src); + self.lint_expectations.insert(expect_id, expectation); + } + } + } } if !is_crate_node { @@ -563,7 +583,11 @@ pub fn register_id(&mut self, id: HirId) { } pub fn build_map(self) -> LintLevelMap { - LintLevelMap { sets: self.sets, id_to_set: self.id_to_set } + LintLevelMap { + sets: self.sets, + id_to_set: self.id_to_set, + lint_expectations: self.lint_expectations, + } } } diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index e6643cfd544..6fca39b2c6b 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -133,10 +133,10 @@ pub fn from_str(x: &str) -> Option { } /// Converts a symbol to a level. - pub fn from_symbol(x: Symbol) -> Option { + pub fn from_symbol(x: Symbol, possible_lint_expect_id: u32) -> Option { match x { sym::allow => Some(Level::Allow), - sym::expect => Some(Level::Expect(LintExpectationId::from(0u32))), + sym::expect => Some(Level::Expect(LintExpectationId::from(possible_lint_expect_id))), sym::warn => Some(Level::Warn), sym::deny => Some(Level::Deny), sym::forbid => Some(Level::Forbid), diff --git a/compiler/rustc_middle/src/lint.rs b/compiler/rustc_middle/src/lint.rs index 5e7fc33953a..9eb7aca13c0 100644 --- a/compiler/rustc_middle/src/lint.rs +++ b/compiler/rustc_middle/src/lint.rs @@ -6,6 +6,7 @@ use rustc_hir::HirId; use rustc_index::vec::IndexVec; use rustc_query_system::ich::StableHashingContext; +use rustc_session::lint::LintExpectationId; use rustc_session::lint::{ builtin::{self, FORBIDDEN_LINT_GROUPS}, FutureIncompatibilityReason, Level, Lint, LintId, @@ -153,6 +154,13 @@ pub fn get_lint_id_level( #[derive(Debug)] pub struct LintLevelMap { + /// This is a collection of lint expectations as described in RFC 2383, that + /// can be fulfilled during this compilation session. This means that at least + /// one expected lint is currently registered in the lint store. + /// + /// The [`LintExpectationId`] is stored as a part of the [`Expect`](Level::Expect) + /// lint level. + pub lint_expectations: FxHashMap, pub sets: LintLevelSets, pub id_to_set: FxHashMap, } @@ -178,14 +186,42 @@ pub fn level_and_source( impl<'a> HashStable> for LintLevelMap { #[inline] fn hash_stable(&self, hcx: &mut StableHashingContext<'a>, hasher: &mut StableHasher) { - let LintLevelMap { ref sets, ref id_to_set } = *self; + let LintLevelMap { ref sets, ref id_to_set, ref lint_expectations } = *self; id_to_set.hash_stable(hcx, hasher); + lint_expectations.hash_stable(hcx, hasher); hcx.while_hashing_spans(true, |hcx| sets.hash_stable(hcx, hasher)) } } +/// This struct represents a lint expectation and holds all required information +/// to emit the `unfulfilled_lint_expectations` lint if it is unfulfilled after +/// the `LateLintPass` has completed. +#[derive(Clone, Debug, HashStable)] +pub struct LintExpectation { + /// The reason for this expectation that can optionally be added as part of + /// the attribute. It will be displayed as part of the lint message. + pub reason: Option, + /// The [`Span`] of the attribute that this expectation originated from. + pub emission_span: Span, + /// The [`Level`] that this lint diagnostic should be emitted if unfulfilled. + pub emission_level: Level, + /// The [`LintLevelSource`] information needed for [`struct_lint_level`]. + pub emission_level_source: LintLevelSource, +} + +impl LintExpectation { + pub fn new( + reason: Option, + attr_span: Span, + emission_level: Level, + emission_level_source: LintLevelSource, + ) -> Self { + Self { reason, emission_span: attr_span, emission_level, emission_level_source } + } +} + pub struct LintDiagnosticBuilder<'a>(DiagnosticBuilder<'a, ()>); impl<'a> LintDiagnosticBuilder<'a> { @@ -225,7 +261,9 @@ pub fn explain_lint_level_source( Level::Forbid => "-F", Level::Allow => "-A", Level::ForceWarn => "--force-warn", - Level::Expect(_) => unreachable!("the expect level does not have a commandline flag"), + Level::Expect(_) => { + unreachable!("the expect level does not have a commandline flag") + } }; let hyphen_case_lint_name = name.replace('_', "-"); if lint_flag_val.as_str() == name { diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index f9435682e53..bd48a9867f9 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -2755,7 +2755,11 @@ pub fn maybe_lint_level_root_bounded(self, mut id: HirId, bound: HirId) -> HirId return bound; } - if hir.attrs(id).iter().any(|attr| Level::from_symbol(attr.name_or_empty()).is_some()) { + if hir + .attrs(id) + .iter() + .any(|attr| Level::from_symbol(attr.name_or_empty(), attr.id.as_u32()).is_some()) + { return id; } let next = hir.get_parent_node(id); diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index edc98abca29..4a7e7e267a2 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -331,7 +331,11 @@ pub fn struct_span_allow>( pub fn struct_allow(&self, msg: &str) -> DiagnosticBuilder<'_, ()> { self.diagnostic().struct_allow(msg) } - pub fn struct_expect(&self, msg: &str, id: lint::LintExpectationId) -> DiagnosticBuilder<'_, ()> { + pub fn struct_expect( + &self, + msg: &str, + id: lint::LintExpectationId, + ) -> DiagnosticBuilder<'_, ()> { self.diagnostic().struct_expect(msg, id) } pub fn struct_span_err>( -- 2.44.0