Skip to content

Commit 33a5945

Browse files
committed
Make LintExpectationId stable between compilation sessions (RFC-2383)
1 parent 44cb8fa commit 33a5945

File tree

10 files changed

+171
-39
lines changed

10 files changed

+171
-39
lines changed

Cargo.lock

+1-1
Original file line numberDiff line numberDiff line change
@@ -3882,7 +3882,7 @@ version = "0.0.0"
38823882
dependencies = [
38833883
"rustc_ast",
38843884
"rustc_data_structures",
3885-
"rustc_index",
3885+
"rustc_hir",
38863886
"rustc_macros",
38873887
"rustc_serialize",
38883888
"rustc_span",

compiler/rustc_errors/src/lib.rs

+48-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use Level::*;
2525

2626
use emitter::{is_case_difference, Emitter, EmitterWriter};
2727
use registry::Registry;
28-
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
28+
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
2929
use rustc_data_structures::stable_hasher::StableHasher;
3030
use rustc_data_structures::sync::{self, Lock, Lrc};
3131
use rustc_data_structures::AtomicRef;
@@ -452,7 +452,14 @@ struct HandlerInner {
452452

453453
future_breakage_diagnostics: Vec<Diagnostic>,
454454

455-
/// Lint [`Diagnostic`]s can be expected as described in [RFC-2383]. An
455+
/// Expected [`Diagnostic`]s store a [`LintExpectationId`] as part of
456+
/// the lint level. [`LintExpectationId`]s created early during the compilation
457+
/// (before `HirId`s have been defined) are not stable and can therefore not be
458+
/// stored on disk. This buffer stores these diagnostics until the ID has been
459+
/// replaced by a stable [`LintExpectationId`]. The [`Diagnostic`]s are the
460+
/// submitted for storage and added to the list of fulfilled expectations.
461+
unstable_expect_diagnostics: Vec<Diagnostic>,
462+
456463
/// expected diagnostic will have the level `Expect` which additionally
457464
/// carries the [`LintExpectationId`] of the expectation that can be
458465
/// marked as fulfilled. This is a collection of all [`LintExpectationId`]s
@@ -580,6 +587,7 @@ impl Handler {
580587
emitted_diagnostics: Default::default(),
581588
stashed_diagnostics: Default::default(),
582589
future_breakage_diagnostics: Vec::new(),
590+
unstable_expect_diagnostics: Vec::new(),
583591
fulfilled_expectations: Default::default(),
584592
}),
585593
}
@@ -923,6 +931,28 @@ impl Handler {
923931
self.inner.borrow_mut().emit_unused_externs(lint_level, unused_externs)
924932
}
925933

934+
pub fn update_unstable_expectation_id(
935+
&self,
936+
unstable_to_stable: &FxHashMap<LintExpectationId, LintExpectationId>,
937+
) {
938+
let diags = std::mem::take(&mut self.inner.borrow_mut().unstable_expect_diagnostics);
939+
if diags.is_empty() {
940+
return;
941+
}
942+
943+
let mut inner = self.inner.borrow_mut();
944+
for mut diag in diags.into_iter() {
945+
if let Some(unstable_id) = diag.level.get_expectation_id() {
946+
if let Some(stable_id) = unstable_to_stable.get(&unstable_id) {
947+
diag.level = Level::Expect(*stable_id);
948+
inner.fulfilled_expectations.insert(*stable_id);
949+
}
950+
}
951+
952+
(*TRACK_DIAGNOSTICS)(&diag);
953+
}
954+
}
955+
926956
/// This methods steals all [`LintExpectationId`]s that are stored inside
927957
/// [`HandlerInner`] and indicate that the linked expectation has been fulfilled.
928958
pub fn steal_fulfilled_expectation_ids(&self) -> FxHashSet<LintExpectationId> {
@@ -973,6 +1003,15 @@ impl HandlerInner {
9731003
return;
9741004
}
9751005

1006+
// The `LintExpectationId` can be stable or unstable depending on when it was created.
1007+
// Diagnostics created before the definition of `HirId`s are unstable and can not yet
1008+
// be stored. Instead, they are buffered until the `LintExpectationId` is replaced by
1009+
// a stable one by the `LintLevelsBuilder`.
1010+
if let Level::Expect(LintExpectationId::Unstable(_)) = diagnostic.level {
1011+
self.unstable_expect_diagnostics.push(diagnostic.clone());
1012+
return;
1013+
}
1014+
9761015
(*TRACK_DIAGNOSTICS)(diagnostic);
9771016

9781017
if let Level::Expect(expectation_id) = diagnostic.level {
@@ -1322,6 +1361,13 @@ impl Level {
13221361
pub fn is_failure_note(&self) -> bool {
13231362
matches!(*self, FailureNote)
13241363
}
1364+
1365+
pub fn get_expectation_id(&self) -> Option<LintExpectationId> {
1366+
match self {
1367+
Level::Expect(id) => Some(*id),
1368+
_ => None,
1369+
}
1370+
}
13251371
}
13261372

13271373
// FIXME(eddyb) this doesn't belong here AFAICT, should be moved to callsite.

compiler/rustc_lint/src/early.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ impl<'a, T: EarlyLintPass> EarlyContextAndPass<'a, T> {
5959
F: FnOnce(&mut Self),
6060
{
6161
let is_crate_node = id == ast::CRATE_NODE_ID;
62-
let push = self.context.builder.push(attrs, is_crate_node);
62+
let push = self.context.builder.push(attrs, is_crate_node, None);
63+
6364
self.check_id(id);
6465
self.enter_attrs(attrs);
6566
f(self);

compiler/rustc_lint/src/expect.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ pub fn check_expectations(tcx: TyCtxt<'_>) {
2525
}
2626

2727
fn emit_unfulfilled_expectation_lint(tcx: TyCtxt<'_>, expectation: &LintExpectation) {
28-
// FIXME The current implementation doesn't cover cases where the
28+
// FIXME: The current implementation doesn't cover cases where the
2929
// `unfulfilled_lint_expectations` is actually expected by another lint
3030
// expectation. This can be added here as we have the lint level of this
3131
// expectation, and we can also mark the lint expectation it would fulfill

compiler/rustc_lint/src/levels.rs

+44-6
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,23 @@ fn lint_levels(tcx: TyCtxt<'_>, (): ()) -> LintLevelMap {
3232

3333
builder.levels.id_to_set.reserve(krate.owners.len() + 1);
3434

35-
let push = builder.levels.push(tcx.hir().attrs(hir::CRATE_HIR_ID), true);
35+
let push =
36+
builder.levels.push(tcx.hir().attrs(hir::CRATE_HIR_ID), true, Some(hir::CRATE_HIR_ID));
37+
3638
builder.levels.register_id(hir::CRATE_HIR_ID);
3739
tcx.hir().walk_toplevel_module(&mut builder);
3840
builder.levels.pop(push);
3941

42+
builder.levels.update_unstable_expectation_ids();
4043
builder.levels.build_map()
4144
}
4245

4346
pub struct LintLevelsBuilder<'s> {
4447
sess: &'s Session,
4548
lint_expectations: FxHashMap<LintExpectationId, LintExpectation>,
49+
/// Each expectation has a stable and an unstable identifier. This map
50+
/// is used to map from unstable to stable [`LintExpectationId`]s.
51+
expectation_id_map: FxHashMap<LintExpectationId, LintExpectationId>,
4652
sets: LintLevelSets,
4753
id_to_set: FxHashMap<HirId, LintStackIndex>,
4854
cur: LintStackIndex,
@@ -66,6 +72,7 @@ impl<'s> LintLevelsBuilder<'s> {
6672
let mut builder = LintLevelsBuilder {
6773
sess,
6874
lint_expectations: Default::default(),
75+
expectation_id_map: Default::default(),
6976
sets: LintLevelSets::new(),
7077
cur: COMMAND_LINE,
7178
id_to_set: Default::default(),
@@ -226,13 +233,26 @@ impl<'s> LintLevelsBuilder<'s> {
226233
/// `#[allow]`
227234
///
228235
/// Don't forget to call `pop`!
229-
pub(crate) fn push(&mut self, attrs: &[ast::Attribute], is_crate_node: bool) -> BuilderPush {
236+
pub(crate) fn push(
237+
&mut self,
238+
attrs: &[ast::Attribute],
239+
is_crate_node: bool,
240+
source_hir_id: Option<HirId>,
241+
) -> BuilderPush {
230242
let mut specs = FxHashMap::default();
231243
let sess = self.sess;
232244
let bad_attr = |span| struct_span_err!(sess, span, E0452, "malformed lint attribute input");
233-
for attr in attrs {
234-
let Some(level) = Level::from_symbol(attr.name_or_empty(), attr.id.as_u32()) else {
235-
continue
245+
for (attr_index, attr) in attrs.iter().enumerate() {
246+
let level = match Level::from_symbol(attr.name_or_empty(), || {
247+
LintExpectationId::Unstable(attr.id)
248+
}) {
249+
None => continue,
250+
Some(Level::Expect(unstable_id)) if source_hir_id.is_some() => {
251+
let stable_id =
252+
self.create_stable_id(unstable_id, source_hir_id.unwrap(), attr_index);
253+
Level::Expect(stable_id)
254+
}
255+
Some(lvl) => lvl,
236256
};
237257

238258
let Some(mut metas) = attr.meta_item_list() else {
@@ -539,6 +559,19 @@ impl<'s> LintLevelsBuilder<'s> {
539559
BuilderPush { prev, changed: prev != self.cur }
540560
}
541561

562+
fn create_stable_id(
563+
&mut self,
564+
unstable_id: LintExpectationId,
565+
hir_id: HirId,
566+
attr_index: usize,
567+
) -> LintExpectationId {
568+
let stable_id = LintExpectationId::Stable { hir_id, attr_index };
569+
570+
self.expectation_id_map.insert(unstable_id, stable_id);
571+
572+
stable_id
573+
}
574+
542575
/// Checks if the lint is gated on a feature that is not enabled.
543576
fn check_gated_lint(&self, lint_id: LintId, span: Span) {
544577
if let Some(feature) = lint_id.lint.feature_gate {
@@ -582,6 +615,10 @@ impl<'s> LintLevelsBuilder<'s> {
582615
self.id_to_set.insert(id, self.cur);
583616
}
584617

618+
fn update_unstable_expectation_ids(&self) {
619+
self.sess.diagnostic().update_unstable_expectation_id(&self.expectation_id_map);
620+
}
621+
585622
pub fn build_map(self) -> LintLevelMap {
586623
LintLevelMap {
587624
sets: self.sets,
@@ -603,7 +640,8 @@ impl LintLevelMapBuilder<'_> {
603640
{
604641
let is_crate_hir = id == hir::CRATE_HIR_ID;
605642
let attrs = self.tcx.hir().attrs(id);
606-
let push = self.levels.push(attrs, is_crate_hir);
643+
let push = self.levels.push(attrs, is_crate_hir, Some(id));
644+
607645
if push.changed {
608646
self.levels.register_id(id);
609647
}

compiler/rustc_lint/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ pub mod builtin;
5151
mod context;
5252
mod early;
5353
mod enum_intrinsics_non_enums;
54-
pub mod hidden_unicode_codepoints;
5554
mod expect;
55+
pub mod hidden_unicode_codepoints;
5656
mod internal;
5757
mod late;
5858
mod levels;

compiler/rustc_lint_defs/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ rustc_span = { path = "../rustc_span" }
1010
rustc_serialize = { path = "../rustc_serialize" }
1111
rustc_macros = { path = "../rustc_macros" }
1212
rustc_target = { path = "../rustc_target" }
13-
rustc_index = { path = "../rustc_index" }
13+
rustc_hir = { path = "../rustc_hir" }

compiler/rustc_lint_defs/src/lib.rs

+64-18
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ extern crate rustc_macros;
55

66
pub use self::Level::*;
77
use rustc_ast::node_id::{NodeId, NodeMap};
8+
use rustc_ast::AttrId;
89
use rustc_data_structures::stable_hasher::{HashStable, StableHasher, ToStableHashKey};
10+
use rustc_hir::HirId;
911
use rustc_serialize::json::Json;
1012
use rustc_span::edition::Edition;
1113
use rustc_span::{sym, symbol::Ident, MultiSpan, Span, Symbol};
@@ -48,29 +50,70 @@ pub enum Applicability {
4850
Unspecified,
4951
}
5052

51-
rustc_index::newtype_index! {
52-
/// FIXME: The lint expectation ID is currently a simple copy of the `AttrId`
53-
/// that the expectation originated from. In the future it should be generated
54-
/// by other means. This is for one to keep the IDs independent of each other
55-
/// and also to ensure that it is actually stable between compilation sessions.
56-
/// (The `AttrId` for instance, is not stable).
57-
///
58-
/// Additionally, it would be nice if this generation could be moved into
59-
/// [`Level::from_symbol`] to have it all contained in one module and to
60-
/// make it simpler to use.
61-
pub struct LintExpectationId {
62-
DEBUG_FORMAT = "LintExpectationId({})"
53+
/// Each lint expectation has a `LintExpectationId` assigned by the
54+
/// [`LintLevelsBuilder`][`rustc_lint::levels::LintLevelsBuilder`]. Expected
55+
/// [`Diagnostic`][`rustc_errors::Diagnostic`]s get the lint level `Expect` which
56+
/// stores the `LintExpectationId` to match it with the actual expectation later on.
57+
///
58+
/// The `LintExpectationId` has to be has stable between compilations, as diagnostic
59+
/// instances might be loaded from cache. Lint messages can be emitted during an
60+
/// `EarlyLintPass` operating on the AST and during a `LateLintPass` traversing the
61+
/// HIR tree. The AST doesn't have enough information to create a stable id. The
62+
/// `LintExpectationId` will instead store the [`AttrId`] defining the expectation.
63+
/// These `LintExpectationId` will be updated to use the stable [`HirId`] once the
64+
/// AST has been lowered. The transformation is done by the
65+
/// [`LintLevelsBuilder`][`rustc_lint::levels::LintLevelsBuilder`]
66+
#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash, Encodable, Decodable)]
67+
pub enum LintExpectationId {
68+
/// Used for lints emitted during the `EarlyLintPass`. This id is not
69+
/// has stable and should not be cached.
70+
Unstable(AttrId),
71+
/// The [`HirId`] that the lint expectation is attached to. This id is
72+
/// stable and can be cached. The additional index ensures that nodes with
73+
/// several expectations can correctly match diagnostics to the individual
74+
/// expectation.
75+
Stable { hir_id: HirId, attr_index: usize },
76+
}
77+
78+
impl LintExpectationId {
79+
pub fn is_stable(&self) -> bool {
80+
match self {
81+
LintExpectationId::Unstable(_) => false,
82+
LintExpectationId::Stable { .. } => true,
83+
}
6384
}
6485
}
6586

66-
rustc_data_structures::impl_stable_hash_via_hash!(LintExpectationId);
87+
impl<HCX: rustc_hir::HashStableContext> HashStable<HCX> for LintExpectationId {
88+
#[inline]
89+
fn hash_stable(&self, hcx: &mut HCX, hasher: &mut StableHasher) {
90+
match self {
91+
LintExpectationId::Unstable(_) => {
92+
unreachable!(
93+
"HashStable should never be called for an unstable `LintExpectationId`"
94+
)
95+
}
96+
LintExpectationId::Stable { hir_id, attr_index } => {
97+
hir_id.hash_stable(hcx, hasher);
98+
attr_index.hash_stable(hcx, hasher);
99+
}
100+
}
101+
}
102+
}
67103

68-
impl<HCX> ToStableHashKey<HCX> for LintExpectationId {
69-
type KeyType = u32;
104+
impl<HCX: rustc_hir::HashStableContext> ToStableHashKey<HCX> for LintExpectationId {
105+
type KeyType = (HirId, usize);
70106

71107
#[inline]
72108
fn to_stable_hash_key(&self, _: &HCX) -> Self::KeyType {
73-
self.as_u32()
109+
match self {
110+
LintExpectationId::Unstable(_) => {
111+
unreachable!(
112+
"HashStable should never be called for an unstable `LintExpectationId`"
113+
)
114+
}
115+
LintExpectationId::Stable { hir_id, attr_index } => (*hir_id, *attr_index),
116+
}
74117
}
75118
}
76119

@@ -133,10 +176,13 @@ impl Level {
133176
}
134177

135178
/// Converts a symbol to a level.
136-
pub fn from_symbol(x: Symbol, possible_lint_expect_id: u32) -> Option<Level> {
179+
pub fn from_symbol<F>(x: Symbol, create_expectation_id: F) -> Option<Level>
180+
where
181+
F: FnOnce() -> LintExpectationId,
182+
{
137183
match x {
138184
sym::allow => Some(Level::Allow),
139-
sym::expect => Some(Level::Expect(LintExpectationId::from(possible_lint_expect_id))),
185+
sym::expect => Some(Level::Expect(create_expectation_id())),
140186
sym::warn => Some(Level::Warn),
141187
sym::deny => Some(Level::Deny),
142188
sym::forbid => Some(Level::Forbid),

compiler/rustc_middle/src/lint.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@ use rustc_errors::{Diagnostic, DiagnosticBuilder, DiagnosticId};
66
use rustc_hir::HirId;
77
use rustc_index::vec::IndexVec;
88
use rustc_query_system::ich::StableHashingContext;
9-
use rustc_session::lint::LintExpectationId;
109
use rustc_session::lint::{
1110
builtin::{self, FORBIDDEN_LINT_GROUPS},
12-
FutureIncompatibilityReason, Level, Lint, LintId,
11+
FutureIncompatibilityReason, Level, Lint, LintExpectationId, LintId,
1312
};
1413
use rustc_session::{DiagnosticMessageId, Session};
1514
use rustc_span::hygiene::MacroKind;

compiler/rustc_middle/src/ty/context.rs

+8-6
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ use rustc_middle::mir::FakeReadCause;
4949
use rustc_query_system::ich::{NodeIdHashingMode, StableHashingContext};
5050
use rustc_serialize::opaque::{FileEncodeResult, FileEncoder};
5151
use rustc_session::config::{BorrowckMode, CrateType, OutputFilenames};
52-
use rustc_session::lint::{Level, Lint};
52+
use rustc_session::lint::{Level, Lint, LintExpectationId};
5353
use rustc_session::Limit;
5454
use rustc_session::Session;
5555
use rustc_span::def_id::{DefPathHash, StableCrateId};
@@ -2755,11 +2755,13 @@ impl<'tcx> TyCtxt<'tcx> {
27552755
return bound;
27562756
}
27572757

2758-
if hir
2759-
.attrs(id)
2760-
.iter()
2761-
.any(|attr| Level::from_symbol(attr.name_or_empty(), attr.id.as_u32()).is_some())
2762-
{
2758+
if hir.attrs(id).iter().enumerate().any(|(attr_index, attr)| {
2759+
Level::from_symbol(attr.name_or_empty(), || LintExpectationId::Stable {
2760+
hir_id: id,
2761+
attr_index,
2762+
})
2763+
.is_some()
2764+
}) {
27632765
return id;
27642766
}
27652767
let next = hir.get_parent_node(id);

0 commit comments

Comments
 (0)