Skip to content

Commit 6548a36

Browse files
authored
Rollup merge of #94670 - xFrednet:rfc-2383-expect-impl-after-party, r=flip1995,wesleywiser
Improve `expect` impl and handle `#[expect(unfulfilled_lint_expectations)]` (RFC 2383) This PR updates unstable `ExpectationIds` in stashed diagnostics and adds some asserts to ensure that the stored expectations are really empty in the end. Additionally, it handles the `#[expect(unfulfilled_lint_expectations)]` case. According to the [Errors and lints docs](https://rustc-dev-guide.rust-lang.org/diagnostics.html#diagnostic-levels) the `error` level should only be used _"when the compiler detects a problem that makes it unable to compile the program"_. As this isn't the case with `#[expect(unfulfilled_lint_expectations)]` I decided to only create a warning. To avoid adding a new lint only for this case, I simply emit a `unfulfilled_lint_expectations` diagnostic with an additional note. --- r? `@wesleywiser` I'm requesting a review from you since you reviewed the previous PR #87835. You are welcome to reassign it if you're busy 🙃 rfc: [RFC-2383](https://rust-lang.github.io/rfcs/2383-lint-reasons.html) tracking issue: #85549 cc: `@flip1995` In case you're also interested in this :)
2 parents 774655d + be84049 commit 6548a36

File tree

8 files changed

+167
-30
lines changed

8 files changed

+167
-30
lines changed

compiler/rustc_errors/src/diagnostic.rs

+24-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ use crate::Substitution;
55
use crate::SubstitutionPart;
66
use crate::SuggestionStyle;
77
use crate::ToolMetadata;
8-
use rustc_lint_defs::Applicability;
8+
use rustc_data_structures::stable_map::FxHashMap;
9+
use rustc_lint_defs::{Applicability, LintExpectationId};
910
use rustc_serialize::json::Json;
1011
use rustc_span::edition::LATEST_STABLE_EDITION;
1112
use rustc_span::{MultiSpan, Span, DUMMY_SP};
@@ -138,6 +139,28 @@ impl Diagnostic {
138139
}
139140
}
140141

142+
pub fn update_unstable_expectation_id(
143+
&mut self,
144+
unstable_to_stable: &FxHashMap<LintExpectationId, LintExpectationId>,
145+
) {
146+
if let Level::Expect(expectation_id) = &mut self.level {
147+
if expectation_id.is_stable() {
148+
return;
149+
}
150+
151+
// The unstable to stable map only maps the unstable `AttrId` to a stable `HirId` with an attribute index.
152+
// The lint index inside the attribute is manually transferred here.
153+
let lint_index = expectation_id.get_lint_index();
154+
expectation_id.set_lint_index(None);
155+
let mut stable_id = *unstable_to_stable
156+
.get(&expectation_id)
157+
.expect("each unstable `LintExpectationId` must have a matching stable id");
158+
159+
stable_id.set_lint_index(lint_index);
160+
*expectation_id = stable_id;
161+
}
162+
}
163+
141164
pub fn has_future_breakage(&self) -> bool {
142165
match self.code {
143166
Some(DiagnosticId::Lint { has_future_breakage, .. }) => has_future_breakage,

compiler/rustc_errors/src/lib.rs

+18-12
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,11 @@ impl Drop for HandlerInner {
522522
"no warnings or errors encountered even though `delayed_good_path_bugs` issued",
523523
);
524524
}
525+
526+
assert!(
527+
self.unstable_expect_diagnostics.is_empty(),
528+
"all diagnostics with unstable expectations should have been converted",
529+
);
525530
}
526531
}
527532

@@ -942,29 +947,30 @@ impl Handler {
942947

943948
let mut inner = self.inner.borrow_mut();
944949
for mut diag in diags.into_iter() {
945-
let mut unstable_id = diag
950+
diag.update_unstable_expectation_id(unstable_to_stable);
951+
952+
let stable_id = diag
946953
.level
947954
.get_expectation_id()
948955
.expect("all diagnostics inside `unstable_expect_diagnostics` must have a `LintExpectationId`");
949-
950-
// The unstable to stable map only maps the unstable `AttrId` to a stable `HirId` with an attribute index.
951-
// The lint index inside the attribute is manually transferred here.
952-
let lint_index = unstable_id.get_lint_index();
953-
unstable_id.set_lint_index(None);
954-
let mut stable_id = *unstable_to_stable
955-
.get(&unstable_id)
956-
.expect("each unstable `LintExpectationId` must have a matching stable id");
957-
958-
stable_id.set_lint_index(lint_index);
959-
diag.level = Level::Expect(stable_id);
960956
inner.fulfilled_expectations.insert(stable_id);
961957

962958
(*TRACK_DIAGNOSTICS)(&diag);
963959
}
960+
961+
inner
962+
.stashed_diagnostics
963+
.values_mut()
964+
.for_each(|diag| diag.update_unstable_expectation_id(unstable_to_stable));
965+
inner
966+
.future_breakage_diagnostics
967+
.iter_mut()
968+
.for_each(|diag| diag.update_unstable_expectation_id(unstable_to_stable));
964969
}
965970

966971
/// This methods steals all [`LintExpectationId`]s that are stored inside
967972
/// [`HandlerInner`] and indicate that the linked expectation has been fulfilled.
973+
#[must_use]
968974
pub fn steal_fulfilled_expectation_ids(&self) -> FxHashSet<LintExpectationId> {
969975
assert!(
970976
self.inner.borrow().unstable_expect_diagnostics.is_empty(),

compiler/rustc_lint/src/expect.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,6 @@ fn emit_unfulfilled_expectation_lint(
3030
hir_id: HirId,
3131
expectation: &LintExpectation,
3232
) {
33-
// FIXME: The current implementation doesn't cover cases where the
34-
// `unfulfilled_lint_expectations` is actually expected by another lint
35-
// expectation. This can be added here by checking the lint level and
36-
// retrieving the `LintExpectationId` if it was expected.
3733
tcx.struct_span_lint_hir(
3834
builtin::UNFULFILLED_LINT_EXPECTATIONS,
3935
hir_id,
@@ -43,6 +39,11 @@ fn emit_unfulfilled_expectation_lint(
4339
if let Some(rationale) = expectation.reason {
4440
diag.note(&rationale.as_str());
4541
}
42+
43+
if expectation.is_unfulfilled_lint_expectations {
44+
diag.note("the `unfulfilled_lint_expectations` lint can't be expected and will always produce this message");
45+
}
46+
4647
diag.emit();
4748
},
4849
);

compiler/rustc_lint/src/levels.rs

+28-8
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use rustc_middle::lint::{
1414
use rustc_middle::ty::query::Providers;
1515
use rustc_middle::ty::{RegisteredTools, TyCtxt};
1616
use rustc_session::lint::{
17-
builtin::{self, FORBIDDEN_LINT_GROUPS},
17+
builtin::{self, FORBIDDEN_LINT_GROUPS, UNFULFILLED_LINT_EXPECTATIONS},
1818
Level, Lint, LintExpectationId, LintId,
1919
};
2020
use rustc_session::parse::{add_feature_diagnostics, feature_err};
@@ -218,6 +218,14 @@ impl<'s> LintLevelsBuilder<'s> {
218218
}
219219
}
220220
}
221+
222+
// The lint `unfulfilled_lint_expectations` can't be expected, as it would suppress itself.
223+
// Handling expectations of this lint would add additional complexity with little to no
224+
// benefit. The expect level for this lint will therefore be ignored.
225+
if let Level::Expect(_) = level && id == LintId::of(UNFULFILLED_LINT_EXPECTATIONS) {
226+
return;
227+
}
228+
221229
if let Level::ForceWarn = old_level {
222230
self.current_specs_mut().insert(id, (old_level, old_src));
223231
} else {
@@ -350,6 +358,22 @@ impl<'s> LintLevelsBuilder<'s> {
350358
self.store.check_lint_name(&name, tool_name, self.registered_tools);
351359
match &lint_result {
352360
CheckLintNameResult::Ok(ids) => {
361+
// This checks for instances where the user writes `#[expect(unfulfilled_lint_expectations)]`
362+
// in that case we want to avoid overriding the lint level but instead add an expectation that
363+
// can't be fulfilled. The lint message will include an explanation, that the
364+
// `unfulfilled_lint_expectations` lint can't be expected.
365+
if let Level::Expect(expect_id) = level {
366+
// The `unfulfilled_lint_expectations` lint is not part of any lint groups. Therefore. we
367+
// only need to check the slice if it contains a single lint.
368+
let is_unfulfilled_lint_expectations = match ids {
369+
[lint] => *lint == LintId::of(UNFULFILLED_LINT_EXPECTATIONS),
370+
_ => false,
371+
};
372+
self.lint_expectations.push((
373+
expect_id,
374+
LintExpectation::new(reason, sp, is_unfulfilled_lint_expectations),
375+
));
376+
}
353377
let src = LintLevelSource::Node(
354378
meta_item.path.segments.last().expect("empty lint name").ident.name,
355379
sp,
@@ -360,10 +384,6 @@ impl<'s> LintLevelsBuilder<'s> {
360384
self.insert_spec(id, (level, src));
361385
}
362386
}
363-
if let Level::Expect(expect_id) = level {
364-
self.lint_expectations
365-
.push((expect_id, LintExpectation::new(reason, sp)));
366-
}
367387
}
368388

369389
CheckLintNameResult::Tool(result) => {
@@ -381,7 +401,7 @@ impl<'s> LintLevelsBuilder<'s> {
381401
}
382402
if let Level::Expect(expect_id) = level {
383403
self.lint_expectations
384-
.push((expect_id, LintExpectation::new(reason, sp)));
404+
.push((expect_id, LintExpectation::new(reason, sp, false)));
385405
}
386406
}
387407
Err((Some(ids), ref new_lint_name)) => {
@@ -425,7 +445,7 @@ impl<'s> LintLevelsBuilder<'s> {
425445
}
426446
if let Level::Expect(expect_id) = level {
427447
self.lint_expectations
428-
.push((expect_id, LintExpectation::new(reason, sp)));
448+
.push((expect_id, LintExpectation::new(reason, sp, false)));
429449
}
430450
}
431451
Err((None, _)) => {
@@ -531,7 +551,7 @@ impl<'s> LintLevelsBuilder<'s> {
531551
}
532552
if let Level::Expect(expect_id) = level {
533553
self.lint_expectations
534-
.push((expect_id, LintExpectation::new(reason, sp)));
554+
.push((expect_id, LintExpectation::new(reason, sp, false)));
535555
}
536556
} else {
537557
panic!("renamed lint does not exist: {}", new_name);

compiler/rustc_lint_defs/src/lib.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ pub enum Applicability {
5454
/// Expected `Diagnostic`s get the lint level `Expect` which stores the `LintExpectationId`
5555
/// to match it with the actual expectation later on.
5656
///
57-
/// The `LintExpectationId` has to be has stable between compilations, as diagnostic
57+
/// The `LintExpectationId` has to be stable between compilations, as diagnostic
5858
/// instances might be loaded from cache. Lint messages can be emitted during an
5959
/// `EarlyLintPass` operating on the AST and during a `LateLintPass` traversing the
6060
/// HIR tree. The AST doesn't have enough information to create a stable id. The
@@ -71,7 +71,7 @@ pub enum Applicability {
7171
#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash, Encodable, Decodable)]
7272
pub enum LintExpectationId {
7373
/// Used for lints emitted during the `EarlyLintPass`. This id is not
74-
/// has stable and should not be cached.
74+
/// hash stable and should not be cached.
7575
Unstable { attr_id: AttrId, lint_index: Option<u16> },
7676
/// The [`HirId`] that the lint expectation is attached to. This id is
7777
/// stable and can be cached. The additional index ensures that nodes with
@@ -113,7 +113,9 @@ impl<HCX: rustc_hir::HashStableContext> HashStable<HCX> for LintExpectationId {
113113
lint_index.hash_stable(hcx, hasher);
114114
}
115115
_ => {
116-
unreachable!("HashStable should only be called for a filled `LintExpectationId`")
116+
unreachable!(
117+
"HashStable should only be called for filled and stable `LintExpectationId`"
118+
)
117119
}
118120
}
119121
}

compiler/rustc_middle/src/lint.rs

+10-2
Original file line numberDiff line numberDiff line change
@@ -204,11 +204,19 @@ pub struct LintExpectation {
204204
pub reason: Option<Symbol>,
205205
/// The [`Span`] of the attribute that this expectation originated from.
206206
pub emission_span: Span,
207+
/// Lint messages for the `unfulfilled_lint_expectations` lint will be
208+
/// adjusted to include an additional note. Therefore, we have to track if
209+
/// the expectation is for the lint.
210+
pub is_unfulfilled_lint_expectations: bool,
207211
}
208212

209213
impl LintExpectation {
210-
pub fn new(reason: Option<Symbol>, attr_span: Span) -> Self {
211-
Self { reason, emission_span: attr_span }
214+
pub fn new(
215+
reason: Option<Symbol>,
216+
emission_span: Span,
217+
is_unfulfilled_lint_expectations: bool,
218+
) -> Self {
219+
Self { reason, emission_span, is_unfulfilled_lint_expectations }
212220
}
213221
}
214222

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// check-pass
2+
// ignore-tidy-linelength
3+
4+
#![feature(lint_reasons)]
5+
#![warn(unused_mut)]
6+
7+
#![expect(unfulfilled_lint_expectations, reason = "idk why you would expect this")]
8+
//~^ WARNING this lint expectation is unfulfilled
9+
//~| NOTE `#[warn(unfulfilled_lint_expectations)]` on by default
10+
//~| NOTE idk why you would expect this
11+
//~| NOTE the `unfulfilled_lint_expectations` lint can't be expected and will always produce this message
12+
13+
#[expect(unfulfilled_lint_expectations, reason = "a local: idk why you would expect this")]
14+
//~^ WARNING this lint expectation is unfulfilled
15+
//~| NOTE a local: idk why you would expect this
16+
//~| NOTE the `unfulfilled_lint_expectations` lint can't be expected and will always produce this message
17+
pub fn normal_test_fn() {
18+
#[expect(unused_mut, reason = "this expectation will create a diagnostic with the default lint level")]
19+
//~^ WARNING this lint expectation is unfulfilled
20+
//~| NOTE this expectation will create a diagnostic with the default lint level
21+
let mut v = vec![1, 1, 2, 3, 5];
22+
v.sort();
23+
24+
// Check that lint lists including `unfulfilled_lint_expectations` are also handled correctly
25+
#[expect(unused, unfulfilled_lint_expectations, reason = "the expectation for `unused` should be fulfilled")]
26+
//~^ WARNING this lint expectation is unfulfilled
27+
//~| NOTE the expectation for `unused` should be fulfilled
28+
//~| NOTE the `unfulfilled_lint_expectations` lint can't be expected and will always produce this message
29+
let value = "I'm unused";
30+
}
31+
32+
#[expect(warnings, reason = "this suppresses all warnings and also suppresses itself. No warning will be issued")]
33+
pub fn expect_warnings() {
34+
// This lint trigger will be suppressed
35+
#[warn(unused_mut)]
36+
let mut v = vec![1, 1, 2, 3, 5];
37+
}
38+
39+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
warning: this lint expectation is unfulfilled
2+
--> $DIR/expect_unfulfilled_expectation.rs:7:11
3+
|
4+
LL | #![expect(unfulfilled_lint_expectations, reason = "idk why you would expect this")]
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: `#[warn(unfulfilled_lint_expectations)]` on by default
8+
= note: idk why you would expect this
9+
= note: the `unfulfilled_lint_expectations` lint can't be expected and will always produce this message
10+
11+
warning: this lint expectation is unfulfilled
12+
--> $DIR/expect_unfulfilled_expectation.rs:13:10
13+
|
14+
LL | #[expect(unfulfilled_lint_expectations, reason = "a local: idk why you would expect this")]
15+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
16+
|
17+
= note: a local: idk why you would expect this
18+
= note: the `unfulfilled_lint_expectations` lint can't be expected and will always produce this message
19+
20+
warning: this lint expectation is unfulfilled
21+
--> $DIR/expect_unfulfilled_expectation.rs:18:14
22+
|
23+
LL | #[expect(unused_mut, reason = "this expectation will create a diagnostic with the default lint level")]
24+
| ^^^^^^^^^^
25+
|
26+
= note: this expectation will create a diagnostic with the default lint level
27+
28+
warning: this lint expectation is unfulfilled
29+
--> $DIR/expect_unfulfilled_expectation.rs:25:22
30+
|
31+
LL | #[expect(unused, unfulfilled_lint_expectations, reason = "the expectation for `unused` should be fulfilled")]
32+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
33+
|
34+
= note: the expectation for `unused` should be fulfilled
35+
= note: the `unfulfilled_lint_expectations` lint can't be expected and will always produce this message
36+
37+
warning: 4 warnings emitted
38+

0 commit comments

Comments
 (0)