Skip to content

Commit 49bbdae

Browse files
committed
Auto merge of #130050 - cjgillot:expect-attr-id, r=fee1-dead
Enumerate lint expectations using AttrId This PR implements the idea I outlined in #127884 (comment) We can uniquely identify a lint expectation `#[expect(lint0, lint1...)]` using the `AttrId` and the index of the lint inside the attribute. This PR uses this property in `check_expectations`. In addition, this PR stops stashing expected diagnostics to wait for the unstable -> stable `LintExpectationId` mapping: if the lint is emitted with an unstable attribute, it must have been emitted by an `eval_always` query (like inside the resolver), so won't be loaded from cache. Decoding an `AttrId` from the on-disk cache ICEs, so we have no risk of accidentally checking an expectation. Fixes #127884 cc `@xFrednet`
2 parents 26b2b8d + 6dfc403 commit 49bbdae

File tree

6 files changed

+72
-167
lines changed

6 files changed

+72
-167
lines changed

compiler/rustc_errors/src/diagnostic.rs

+2-22
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ use std::thread::panicking;
88

99
use rustc_data_structures::fx::FxIndexMap;
1010
use rustc_error_messages::{fluent_value_from_str_list_sep_by_and, FluentValue};
11-
use rustc_lint_defs::{Applicability, LintExpectationId};
11+
use rustc_lint_defs::Applicability;
1212
use rustc_macros::{Decodable, Encodable};
1313
use rustc_span::source_map::Spanned;
1414
use rustc_span::symbol::Symbol;
15-
use rustc_span::{AttrId, Span, DUMMY_SP};
15+
use rustc_span::{Span, DUMMY_SP};
1616
use tracing::debug;
1717

1818
use crate::snippet::Style;
@@ -354,26 +354,6 @@ impl DiagInner {
354354
}
355355
}
356356

357-
pub(crate) fn update_unstable_expectation_id(
358-
&mut self,
359-
unstable_to_stable: &FxIndexMap<AttrId, LintExpectationId>,
360-
) {
361-
if let Level::Expect(expectation_id) | Level::ForceWarning(Some(expectation_id)) =
362-
&mut self.level
363-
&& let LintExpectationId::Unstable { attr_id, lint_index } = *expectation_id
364-
{
365-
// The unstable to stable map only maps the unstable `AttrId` to a stable `HirId` with an attribute index.
366-
// The lint index inside the attribute is manually transferred here.
367-
let Some(stable_id) = unstable_to_stable.get(&attr_id) else {
368-
panic!("{expectation_id:?} must have a matching stable id")
369-
};
370-
371-
let mut stable_id = *stable_id;
372-
stable_id.set_lint_index(lint_index);
373-
*expectation_id = stable_id;
374-
}
375-
}
376-
377357
/// Indicates whether this diagnostic should show up in cargo's future breakage report.
378358
pub(crate) fn has_future_breakage(&self) -> bool {
379359
matches!(self.is_lint, Some(IsLint { has_future_breakage: true, .. }))

compiler/rustc_errors/src/lib.rs

+8-86
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ use rustc_macros::{Decodable, Encodable};
6969
pub use rustc_span::fatal_error::{FatalError, FatalErrorMarker};
7070
use rustc_span::source_map::SourceMap;
7171
pub use rustc_span::ErrorGuaranteed;
72-
use rustc_span::{AttrId, Loc, Span, DUMMY_SP};
72+
use rustc_span::{Loc, Span, DUMMY_SP};
7373
pub use snippet::Style;
7474
// Used by external projects such as `rust-gpu`.
7575
// See https://github.com/rust-lang/rust/pull/115393.
@@ -497,28 +497,18 @@ struct DiagCtxtInner {
497497

498498
future_breakage_diagnostics: Vec<DiagInner>,
499499

500-
/// The [`Self::unstable_expect_diagnostics`] should be empty when this struct is
501-
/// dropped. However, it can have values if the compilation is stopped early
502-
/// or is only partially executed. To avoid ICEs, like in rust#94953 we only
503-
/// check if [`Self::unstable_expect_diagnostics`] is empty, if the expectation ids
504-
/// have been converted.
505-
check_unstable_expect_diagnostics: bool,
506-
507-
/// Expected [`DiagInner`][struct@diagnostic::DiagInner]s store a [`LintExpectationId`] as part
508-
/// of the lint level. [`LintExpectationId`]s created early during the compilation
509-
/// (before `HirId`s have been defined) are not stable and can therefore not be
510-
/// stored on disk. This buffer stores these diagnostics until the ID has been
511-
/// replaced by a stable [`LintExpectationId`]. The [`DiagInner`][struct@diagnostic::DiagInner]s
512-
/// are submitted for storage and added to the list of fulfilled expectations.
513-
unstable_expect_diagnostics: Vec<DiagInner>,
514-
515500
/// expected diagnostic will have the level `Expect` which additionally
516501
/// carries the [`LintExpectationId`] of the expectation that can be
517502
/// marked as fulfilled. This is a collection of all [`LintExpectationId`]s
518503
/// that have been marked as fulfilled this way.
519504
///
505+
/// Emitting expectations after having stolen this field can happen. In particular, an
506+
/// `#[expect(warnings)]` can easily make the `UNFULFILLED_LINT_EXPECTATIONS` lint expect
507+
/// itself. To avoid needless complexity in this corner case, we tolerate failing to track
508+
/// those expectations.
509+
///
520510
/// [RFC-2383]: https://rust-lang.github.io/rfcs/2383-lint-reasons.html
521-
fulfilled_expectations: FxHashSet<LintExpectationId>,
511+
fulfilled_expectations: FxIndexSet<LintExpectationId>,
522512

523513
/// The file where the ICE information is stored. This allows delayed_span_bug backtraces to be
524514
/// stored along side the main panic backtrace.
@@ -605,13 +595,6 @@ impl Drop for DiagCtxtInner {
605595
);
606596
}
607597
}
608-
609-
if self.check_unstable_expect_diagnostics {
610-
assert!(
611-
self.unstable_expect_diagnostics.is_empty(),
612-
"all diagnostics with unstable expectations should have been converted",
613-
);
614-
}
615598
}
616599
}
617600

@@ -740,8 +723,6 @@ impl DiagCtxt {
740723
emitted_diagnostics,
741724
stashed_diagnostics,
742725
future_breakage_diagnostics,
743-
check_unstable_expect_diagnostics,
744-
unstable_expect_diagnostics,
745726
fulfilled_expectations,
746727
ice_file: _,
747728
} = inner.deref_mut();
@@ -761,8 +742,6 @@ impl DiagCtxt {
761742
*emitted_diagnostics = Default::default();
762743
*stashed_diagnostics = Default::default();
763744
*future_breakage_diagnostics = Default::default();
764-
*check_unstable_expect_diagnostics = false;
765-
*unstable_expect_diagnostics = Default::default();
766745
*fulfilled_expectations = Default::default();
767746
}
768747

@@ -1094,44 +1073,10 @@ impl<'a> DiagCtxtHandle<'a> {
10941073
inner.emitter.emit_unused_externs(lint_level, unused_externs)
10951074
}
10961075

1097-
pub fn update_unstable_expectation_id(
1098-
&self,
1099-
unstable_to_stable: FxIndexMap<AttrId, LintExpectationId>,
1100-
) {
1101-
let mut inner = self.inner.borrow_mut();
1102-
let diags = std::mem::take(&mut inner.unstable_expect_diagnostics);
1103-
inner.check_unstable_expect_diagnostics = true;
1104-
1105-
if !diags.is_empty() {
1106-
inner.suppressed_expected_diag = true;
1107-
for mut diag in diags.into_iter() {
1108-
diag.update_unstable_expectation_id(&unstable_to_stable);
1109-
1110-
// Here the diagnostic is given back to `emit_diagnostic` where it was first
1111-
// intercepted. Now it should be processed as usual, since the unstable expectation
1112-
// id is now stable.
1113-
inner.emit_diagnostic(diag, self.tainted_with_errors);
1114-
}
1115-
}
1116-
1117-
inner
1118-
.stashed_diagnostics
1119-
.values_mut()
1120-
.for_each(|(diag, _guar)| diag.update_unstable_expectation_id(&unstable_to_stable));
1121-
inner
1122-
.future_breakage_diagnostics
1123-
.iter_mut()
1124-
.for_each(|diag| diag.update_unstable_expectation_id(&unstable_to_stable));
1125-
}
1126-
11271076
/// This methods steals all [`LintExpectationId`]s that are stored inside
11281077
/// [`DiagCtxtInner`] and indicate that the linked expectation has been fulfilled.
11291078
#[must_use]
1130-
pub fn steal_fulfilled_expectation_ids(&self) -> FxHashSet<LintExpectationId> {
1131-
assert!(
1132-
self.inner.borrow().unstable_expect_diagnostics.is_empty(),
1133-
"`DiagCtxtInner::unstable_expect_diagnostics` should be empty at this point",
1134-
);
1079+
pub fn steal_fulfilled_expectation_ids(&self) -> FxIndexSet<LintExpectationId> {
11351080
std::mem::take(&mut self.inner.borrow_mut().fulfilled_expectations)
11361081
}
11371082

@@ -1440,8 +1385,6 @@ impl DiagCtxtInner {
14401385
emitted_diagnostics: Default::default(),
14411386
stashed_diagnostics: Default::default(),
14421387
future_breakage_diagnostics: Vec::new(),
1443-
check_unstable_expect_diagnostics: false,
1444-
unstable_expect_diagnostics: Vec::new(),
14451388
fulfilled_expectations: Default::default(),
14461389
ice_file: None,
14471390
}
@@ -1471,24 +1414,6 @@ impl DiagCtxtInner {
14711414
mut diagnostic: DiagInner,
14721415
taint: Option<&Cell<Option<ErrorGuaranteed>>>,
14731416
) -> Option<ErrorGuaranteed> {
1474-
match diagnostic.level {
1475-
Expect(expect_id) | ForceWarning(Some(expect_id)) => {
1476-
// The `LintExpectationId` can be stable or unstable depending on when it was
1477-
// created. Diagnostics created before the definition of `HirId`s are unstable and
1478-
// can not yet be stored. Instead, they are buffered until the `LintExpectationId`
1479-
// is replaced by a stable one by the `LintLevelsBuilder`.
1480-
if let LintExpectationId::Unstable { .. } = expect_id {
1481-
// We don't call TRACK_DIAGNOSTIC because we wait for the
1482-
// unstable ID to be updated, whereupon the diagnostic will be
1483-
// passed into this method again.
1484-
self.unstable_expect_diagnostics.push(diagnostic);
1485-
return None;
1486-
}
1487-
// Continue through to the `Expect`/`ForceWarning` case below.
1488-
}
1489-
_ => {}
1490-
}
1491-
14921417
if diagnostic.has_future_breakage() {
14931418
// Future breakages aren't emitted if they're `Level::Allow` or
14941419
// `Level::Expect`, but they still need to be constructed and
@@ -1564,9 +1489,6 @@ impl DiagCtxtInner {
15641489
return None;
15651490
}
15661491
Expect(expect_id) | ForceWarning(Some(expect_id)) => {
1567-
if let LintExpectationId::Unstable { .. } = expect_id {
1568-
unreachable!(); // this case was handled at the top of this function
1569-
}
15701492
self.fulfilled_expectations.insert(expect_id);
15711493
if let Expect(_) = diagnostic.level {
15721494
// Nothing emitted here for expected lints.

compiler/rustc_lint/src/expect.rs

+39-51
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
use rustc_data_structures::fx::FxIndexMap;
2-
use rustc_hir::{HirId, CRATE_OWNER_ID};
1+
use rustc_data_structures::fx::FxHashSet;
2+
use rustc_hir::CRATE_OWNER_ID;
33
use rustc_middle::lint::LintExpectation;
44
use rustc_middle::query::Providers;
55
use rustc_middle::ty::TyCtxt;
66
use rustc_session::lint::builtin::UNFULFILLED_LINT_EXPECTATIONS;
7-
use rustc_session::lint::{Level, LintExpectationId};
7+
use rustc_session::lint::LintExpectationId;
88
use rustc_span::Symbol;
99

1010
use crate::lints::{Expectation, ExpectationNote};
@@ -17,68 +17,56 @@ fn lint_expectations(tcx: TyCtxt<'_>, (): ()) -> Vec<(LintExpectationId, LintExp
1717
let krate = tcx.hir_crate_items(());
1818

1919
let mut expectations = Vec::new();
20-
let mut unstable_to_stable_ids = FxIndexMap::default();
2120

22-
let mut record_stable = |attr_id, hir_id, attr_index| {
23-
let expect_id = LintExpectationId::Stable { hir_id, attr_index, lint_index: None };
24-
unstable_to_stable_ids.entry(attr_id).or_insert(expect_id);
25-
};
26-
let mut push_expectations = |owner| {
21+
for owner in std::iter::once(CRATE_OWNER_ID).chain(krate.owners()) {
2722
let lints = tcx.shallow_lint_levels_on(owner);
28-
if lints.expectations.is_empty() {
29-
return;
30-
}
31-
3223
expectations.extend_from_slice(&lints.expectations);
33-
34-
let attrs = tcx.hir_attrs(owner);
35-
for &(local_id, attrs) in attrs.map.iter() {
36-
// Some attributes appear multiple times in HIR, to ensure they are correctly taken
37-
// into account where they matter. This means we cannot just associate the AttrId to
38-
// the first HirId where we see it, but need to check it actually appears in a lint
39-
// level.
40-
// FIXME(cjgillot): Can this cause an attribute to appear in multiple expectation ids?
41-
if !lints.specs.contains_key(&local_id) {
42-
continue;
43-
}
44-
for (attr_index, attr) in attrs.iter().enumerate() {
45-
let Some(Level::Expect(_)) = Level::from_attr(attr) else { continue };
46-
record_stable(attr.id, HirId { owner, local_id }, attr_index.try_into().unwrap());
47-
}
48-
}
49-
};
50-
51-
push_expectations(CRATE_OWNER_ID);
52-
for owner in krate.owners() {
53-
push_expectations(owner);
5424
}
5525

56-
tcx.dcx().update_unstable_expectation_id(unstable_to_stable_ids);
5726
expectations
5827
}
5928

6029
fn check_expectations(tcx: TyCtxt<'_>, tool_filter: Option<Symbol>) {
6130
let lint_expectations = tcx.lint_expectations(());
6231
let fulfilled_expectations = tcx.dcx().steal_fulfilled_expectation_ids();
6332

64-
for (id, expectation) in lint_expectations {
65-
// This check will always be true, since `lint_expectations` only
66-
// holds stable ids
67-
if let LintExpectationId::Stable { hir_id, .. } = id {
68-
if !fulfilled_expectations.contains(id)
69-
&& tool_filter.map_or(true, |filter| expectation.lint_tool == Some(filter))
70-
{
71-
let rationale = expectation.reason.map(|rationale| ExpectationNote { rationale });
72-
let note = expectation.is_unfulfilled_lint_expectations;
73-
tcx.emit_node_span_lint(
74-
UNFULFILLED_LINT_EXPECTATIONS,
75-
*hir_id,
76-
expectation.emission_span,
77-
Expectation { rationale, note },
78-
);
33+
// Turn a `LintExpectationId` into a `(AttrId, lint_index)` pair.
34+
let canonicalize_id = |expect_id: &LintExpectationId| {
35+
match *expect_id {
36+
LintExpectationId::Unstable { attr_id, lint_index: Some(lint_index) } => {
37+
(attr_id, lint_index)
38+
}
39+
LintExpectationId::Stable { hir_id, attr_index, lint_index: Some(lint_index) } => {
40+
// We are an `eval_always` query, so looking at the attribute's `AttrId` is ok.
41+
let attr_id = tcx.hir().attrs(hir_id)[attr_index as usize].id;
42+
(attr_id, lint_index)
7943
}
80-
} else {
44+
_ => panic!("fulfilled expectations must have a lint index"),
45+
}
46+
};
47+
48+
let fulfilled_expectations: FxHashSet<_> =
49+
fulfilled_expectations.iter().map(canonicalize_id).collect();
50+
51+
for (expect_id, expectation) in lint_expectations {
52+
// This check will always be true, since `lint_expectations` only holds stable ids
53+
let LintExpectationId::Stable { hir_id, .. } = expect_id else {
8154
unreachable!("at this stage all `LintExpectationId`s are stable");
55+
};
56+
57+
let expect_id = canonicalize_id(expect_id);
58+
59+
if !fulfilled_expectations.contains(&expect_id)
60+
&& tool_filter.map_or(true, |filter| expectation.lint_tool == Some(filter))
61+
{
62+
let rationale = expectation.reason.map(|rationale| ExpectationNote { rationale });
63+
let note = expectation.is_unfulfilled_lint_expectations;
64+
tcx.emit_node_span_lint(
65+
UNFULFILLED_LINT_EXPECTATIONS,
66+
*hir_id,
67+
expectation.emission_span,
68+
Expectation { rationale, note },
69+
);
8270
}
8371
}
8472
}
+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//@ check-pass
2+
3+
#![deny(unused_imports)]
4+
#![deny(unfulfilled_lint_expectations)]
5+
6+
#[expect(unused_imports)]
7+
use std::{io, fs};
8+
9+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//@ check-pass
2+
3+
#![expect(warnings)]
4+
5+
#[expect(unused)]
6+
fn main() {}

tests/ui/lint/rfc-2383-lint-reason/force_warn_expected_lints_fulfilled.stderr

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
warning: denote infinite loops with `loop { ... }`
2+
--> $DIR/force_warn_expected_lints_fulfilled.rs:8:5
3+
|
4+
LL | while true {
5+
| ^^^^^^^^^^ help: use `loop`
6+
|
7+
= note: requested on the command line with `--force-warn while-true`
8+
19
warning: unused variable: `x`
210
--> $DIR/force_warn_expected_lints_fulfilled.rs:18:9
311
|
@@ -28,13 +36,5 @@ warning: unused variable: `this_should_fulfill_the_expectation`
2836
LL | let this_should_fulfill_the_expectation = "The `#[allow]` has no power here";
2937
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_this_should_fulfill_the_expectation`
3038

31-
warning: denote infinite loops with `loop { ... }`
32-
--> $DIR/force_warn_expected_lints_fulfilled.rs:8:5
33-
|
34-
LL | while true {
35-
| ^^^^^^^^^^ help: use `loop`
36-
|
37-
= note: requested on the command line with `--force-warn while-true`
38-
3939
warning: 5 warnings emitted
4040

0 commit comments

Comments
 (0)