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)
This commit is contained in:
xFrednet 2021-08-06 23:28:58 +02:00
parent f467a58b7b
commit 2ca9037b61
No known key found for this signature in database
GPG Key ID: FCDCBF29AF64D601
7 changed files with 102 additions and 19 deletions

View File

@ -133,11 +133,7 @@ impl Diagnostic {
| 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,
}
}

View File

@ -451,6 +451,15 @@ struct HandlerInner {
deduplicated_warn_count: usize,
future_breakage_diagnostics: Vec<Diagnostic>,
/// 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<LintExpectationId>,
}
/// A key denoting where from a diagnostic was stashed.
@ -571,6 +580,7 @@ impl Handler {
emitted_diagnostics: Default::default(),
stashed_diagnostics: Default::default(),
future_breakage_diagnostics: Vec::new(),
fulfilled_expectations: Default::default(),
}),
}
}
@ -912,6 +922,12 @@ impl Handler {
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<LintExpectationId> {
std::mem::take(&mut self.inner.borrow_mut().fulfilled_expectations)
}
}
impl HandlerInner {
@ -959,7 +975,8 @@ impl HandlerInner {
(*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;

View File

@ -7,17 +7,15 @@ use rustc_errors::{struct_span_err, Applicability, Diagnostic};
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<LintExpectationId, LintExpectation>,
sets: LintLevelSets,
id_to_set: FxHashMap<HirId, LintStackIndex>,
cur: LintStackIndex,
@ -66,6 +65,7 @@ impl<'s> LintLevelsBuilder<'s> {
) -> 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 @@ impl<'s> LintLevelsBuilder<'s> {
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 @@ impl<'s> LintLevelsBuilder<'s> {
}
}
}
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 @@ impl<'s> LintLevelsBuilder<'s> {
}
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,
}
}
}

View File

@ -133,10 +133,10 @@ impl Level {
}
/// Converts a symbol to a level.
pub fn from_symbol(x: Symbol) -> Option<Level> {
pub fn from_symbol(x: Symbol, possible_lint_expect_id: u32) -> Option<Level> {
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),

View File

@ -6,6 +6,7 @@ use rustc_errors::{Diagnostic, DiagnosticBuilder, DiagnosticId};
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 @@ impl LintLevelSets {
#[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<LintExpectationId, LintExpectation>,
pub sets: LintLevelSets,
pub id_to_set: FxHashMap<HirId, LintStackIndex>,
}
@ -178,14 +186,42 @@ impl LintLevelMap {
impl<'a> HashStable<StableHashingContext<'a>> 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<Symbol>,
/// 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<Symbol>,
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 {

View File

@ -2755,7 +2755,11 @@ impl<'tcx> TyCtxt<'tcx> {
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);

View File

@ -331,7 +331,11 @@ impl Session {
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<S: Into<MultiSpan>>(