Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 10913c0

Browse files
committedMar 3, 2022
Auto merge of rust-lang#87835 - xFrednet:rfc-2383-expect-attribute-with-ids, r=wesleywiser
Implementation of the `expect` attribute (RFC 2383) This is an implementation of the `expect` attribute as described in [RFC-2383](https://rust-lang.github.io/rfcs/2383-lint-reasons.html). The attribute allows the suppression of lint message by expecting them. Unfulfilled lint expectations (meaning no expected lint was caught) will emit the `unfulfilled_lint_expectations` lint at the `expect` attribute. ### Example #### input ```rs // required feature flag #![feature(lint_reasons)] #[expect(unused_mut)] // Will warn about an unfulfilled expectation #[expect(unused_variables)] // Will be fulfilled by x fn main() { let x = 0; } ``` #### output ```txt warning: this lint expectation is unfulfilled --> $DIR/trigger_lint.rs:3:1 | LL | #[expect(unused_mut)] // Will warn about an unfulfilled expectation | ^^^^^^^^^^ | = note: `#[warn(unfulfilled_lint_expectations)]` on by default ``` ### Implementation This implementation introduces `Expect` as a new lint level for diagnostics, which have been expected. All lint expectations marked via the `expect` attribute are collected in the [`LintLevelsBuilder`] and assigned an ID that is stored in the new lint level. The `LintLevelsBuilder` stores all found expectations and the data needed to emit the `unfulfilled_lint_expectations` in the [`LintLevelsMap`] which is the result of the [`lint_levels()`] query. The [`rustc_errors::HandlerInner`] is the central error handler in rustc and handles the emission of all diagnostics. Lint message with the level `Expect` are suppressed during this emission, while the expectation ID is stored in a set which marks them as fulfilled. The last step is then so simply check if all expectations collected by the [`LintLevelsBuilder`] in the [`LintLevelsMap`] have been marked as fulfilled in the [`rustc_errors::HandlerInner`]. Otherwise, a new lint message will be emitted. The implementation of the `LintExpectationId` required some special handling to make it stable between sessions. Lints can be emitted during [`EarlyLintPass`]es. At this stage, it's not possible to create a stable identifier. The level instead stores an unstable identifier, which is later converted to a stable `LintExpectationId`. ### Followup TO-DOs All open TO-DOs have been marked with `FIXME` comments in the code. This is the combined list of them: * [ ] The current implementation doesn't cover cases where the `unfulfilled_lint_expectations` lint is actually expected by another `expect` attribute. * This should be easily possible, but I wanted to get some feedback before putting more work into this. * This could also be done in a new PR to not add to much more code to this one * [ ] Update unstable documentation to reflect this change. * [ ] Update unstable expectation ids in [`HandlerInner::stashed_diagnostics`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_errors/struct.HandlerInner.html#structfield.stashed_diagnostics) ### Open questions I also have a few open questions where I would like to get feedback on: 1. The RFC discussion included a suggestion to change the `expect` attribute to something else. (Initiated by `@Ixrec` [here](rust-lang/rfcs#2383 (comment)), suggestion from `@scottmcm` to use `#[should_lint(...)]` [here](rust-lang/rfcs#2383 (comment))). No real conclusion was drawn on that point from my understanding. Is this still open for discussion, or was this discarded with the merge of the RFC? 2. How should the expect attribute deal with the new `force-warn` lint level? --- This approach was inspired by a discussion with `@LeSeulArtichaut.` RFC tracking issue: rust-lang#54503 Mentoring/Implementation issue: rust-lang#85549 [`LintLevelsBuilder`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/levels/struct.LintLevelsBuilder.html [`LintLevelsMap`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/lint/struct.LintLevelMap.html [`lint_levels()`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html#method.lint_levels [`rustc_errors::HandlerInner`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_errors/struct.HandlerInner.html [`EarlyLintPass`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.EarlyLintPass.html
2 parents 32cbc76 + 5275d02 commit 10913c0

37 files changed

+1040
-26
lines changed
 

‎Cargo.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3882,6 +3882,7 @@ version = "0.0.0"
38823882
dependencies = [
38833883
"rustc_ast",
38843884
"rustc_data_structures",
3885+
"rustc_hir",
38853886
"rustc_macros",
38863887
"rustc_serialize",
38873888
"rustc_span",

‎compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ fn annotation_type_for_level(level: Level) -> AnnotationType {
7575
// FIXME(#59346): Not sure how to map this level
7676
Level::FailureNote => AnnotationType::Error,
7777
Level::Allow => panic!("Should not call with Allow"),
78+
Level::Expect(_) => panic!("Should not call with Expect"),
7879
}
7980
}
8081

‎compiler/rustc_errors/src/diagnostic.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ impl Diagnostic {
133133
| Level::Error { .. }
134134
| Level::FailureNote => true,
135135

136-
Level::Warning | Level::Note | Level::Help | Level::Allow => false,
136+
Level::Warning | Level::Note | Level::Help | Level::Allow | Level::Expect(_) => false,
137137
}
138138
}
139139

‎compiler/rustc_errors/src/lib.rs

Lines changed: 90 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ extern crate tracing;
2020

2121
pub use emitter::ColorConfig;
2222

23+
use rustc_lint_defs::LintExpectationId;
2324
use Level::*;
2425

2526
use emitter::{is_case_difference, Emitter, EmitterWriter};
2627
use registry::Registry;
27-
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
28+
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
2829
use rustc_data_structures::stable_hasher::StableHasher;
2930
use rustc_data_structures::sync::{self, Lock, Lrc};
3031
use rustc_data_structures::AtomicRef;
@@ -450,6 +451,22 @@ struct HandlerInner {
450451
deduplicated_warn_count: usize,
451452

452453
future_breakage_diagnostics: Vec<Diagnostic>,
454+
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+
463+
/// expected diagnostic will have the level `Expect` which additionally
464+
/// carries the [`LintExpectationId`] of the expectation that can be
465+
/// marked as fulfilled. This is a collection of all [`LintExpectationId`]s
466+
/// that have been marked as fulfilled this way.
467+
///
468+
/// [RFC-2383]: https://rust-lang.github.io/rfcs/2383-lint-reasons.html
469+
fulfilled_expectations: FxHashSet<LintExpectationId>,
453470
}
454471

455472
/// A key denoting where from a diagnostic was stashed.
@@ -570,6 +587,8 @@ impl Handler {
570587
emitted_diagnostics: Default::default(),
571588
stashed_diagnostics: Default::default(),
572589
future_breakage_diagnostics: Vec::new(),
590+
unstable_expect_diagnostics: Vec::new(),
591+
fulfilled_expectations: Default::default(),
573592
}),
574593
}
575594
}
@@ -677,6 +696,11 @@ impl Handler {
677696
DiagnosticBuilder::new(self, Level::Allow, msg)
678697
}
679698

699+
/// Construct a builder at the `Expect` level with the `msg`.
700+
pub fn struct_expect(&self, msg: &str, id: LintExpectationId) -> DiagnosticBuilder<'_, ()> {
701+
DiagnosticBuilder::new(self, Level::Expect(id), msg)
702+
}
703+
680704
/// Construct a builder at the `Error` level at the given `span` and with the `msg`.
681705
pub fn struct_span_err(
682706
&self,
@@ -906,6 +930,48 @@ impl Handler {
906930
pub fn emit_unused_externs(&self, lint_level: &str, unused_externs: &[&str]) {
907931
self.inner.borrow_mut().emit_unused_externs(lint_level, unused_externs)
908932
}
933+
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+
let mut unstable_id = diag
946+
.level
947+
.get_expectation_id()
948+
.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);
960+
inner.fulfilled_expectations.insert(stable_id);
961+
962+
(*TRACK_DIAGNOSTICS)(&diag);
963+
}
964+
}
965+
966+
/// This methods steals all [`LintExpectationId`]s that are stored inside
967+
/// [`HandlerInner`] and indicate that the linked expectation has been fulfilled.
968+
pub fn steal_fulfilled_expectation_ids(&self) -> FxHashSet<LintExpectationId> {
969+
assert!(
970+
self.inner.borrow().unstable_expect_diagnostics.is_empty(),
971+
"`HandlerInner::unstable_expect_diagnostics` should be empty at this point",
972+
);
973+
std::mem::take(&mut self.inner.borrow_mut().fulfilled_expectations)
974+
}
909975
}
910976

911977
impl HandlerInner {
@@ -951,9 +1017,21 @@ impl HandlerInner {
9511017
return;
9521018
}
9531019

1020+
// The `LintExpectationId` can be stable or unstable depending on when it was created.
1021+
// Diagnostics created before the definition of `HirId`s are unstable and can not yet
1022+
// be stored. Instead, they are buffered until the `LintExpectationId` is replaced by
1023+
// a stable one by the `LintLevelsBuilder`.
1024+
if let Level::Expect(LintExpectationId::Unstable { .. }) = diagnostic.level {
1025+
self.unstable_expect_diagnostics.push(diagnostic.clone());
1026+
return;
1027+
}
1028+
9541029
(*TRACK_DIAGNOSTICS)(diagnostic);
9551030

956-
if diagnostic.level == Allow {
1031+
if let Level::Expect(expectation_id) = diagnostic.level {
1032+
self.fulfilled_expectations.insert(expectation_id);
1033+
return;
1034+
} else if diagnostic.level == Allow {
9571035
return;
9581036
}
9591037

@@ -1250,6 +1328,7 @@ pub enum Level {
12501328
Help,
12511329
FailureNote,
12521330
Allow,
1331+
Expect(LintExpectationId),
12531332
}
12541333

12551334
impl fmt::Display for Level {
@@ -1275,7 +1354,7 @@ impl Level {
12751354
spec.set_fg(Some(Color::Cyan)).set_intense(true);
12761355
}
12771356
FailureNote => {}
1278-
Allow => unreachable!(),
1357+
Allow | Expect(_) => unreachable!(),
12791358
}
12801359
spec
12811360
}
@@ -1289,12 +1368,20 @@ impl Level {
12891368
Help => "help",
12901369
FailureNote => "failure-note",
12911370
Allow => panic!("Shouldn't call on allowed error"),
1371+
Expect(_) => panic!("Shouldn't call on expected error"),
12921372
}
12931373
}
12941374

12951375
pub fn is_failure_note(&self) -> bool {
12961376
matches!(*self, FailureNote)
12971377
}
1378+
1379+
pub fn get_expectation_id(&self) -> Option<LintExpectationId> {
1380+
match self {
1381+
Level::Expect(id) => Some(*id),
1382+
_ => None,
1383+
}
1384+
}
12981385
}
12991386

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

‎compiler/rustc_feature/src/builtin_attrs.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,10 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
282282
ungated!(
283283
allow, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#), DuplicatesOk
284284
),
285+
gated!(
286+
expect, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#), DuplicatesOk,
287+
lint_reasons, experimental!(expect)
288+
),
285289
ungated!(
286290
forbid, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#), DuplicatesOk
287291
),

‎compiler/rustc_lint/src/context.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ struct LintGroup {
109109
depr: Option<LintAlias>,
110110
}
111111

112+
#[derive(Debug)]
112113
pub enum CheckLintNameResult<'a> {
113114
Ok(&'a [LintId]),
114115
/// Lint doesn't exist. Potentially contains a suggestion for a correct lint name.
@@ -377,6 +378,9 @@ impl LintStore {
377378
Level::ForceWarn => "--force-warn",
378379
Level::Deny => "-D",
379380
Level::Forbid => "-F",
381+
Level::Expect(_) => {
382+
unreachable!("lints with the level of `expect` should not run this code");
383+
}
380384
},
381385
lint_name
382386
);

‎compiler/rustc_lint/src/early.rs

Lines changed: 2 additions & 1 deletion
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

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
use crate::builtin;
2+
use rustc_hir::HirId;
3+
use rustc_middle::{lint::LintExpectation, ty::TyCtxt};
4+
use rustc_session::lint::LintExpectationId;
5+
use rustc_span::symbol::sym;
6+
7+
pub fn check_expectations(tcx: TyCtxt<'_>) {
8+
if !tcx.sess.features_untracked().enabled(sym::lint_reasons) {
9+
return;
10+
}
11+
12+
let fulfilled_expectations = tcx.sess.diagnostic().steal_fulfilled_expectation_ids();
13+
let lint_expectations = &tcx.lint_levels(()).lint_expectations;
14+
15+
for (id, expectation) in lint_expectations {
16+
if !fulfilled_expectations.contains(id) {
17+
// This check will always be true, since `lint_expectations` only
18+
// holds stable ids
19+
if let LintExpectationId::Stable { hir_id, .. } = id {
20+
emit_unfulfilled_expectation_lint(tcx, *hir_id, expectation);
21+
} else {
22+
unreachable!("at this stage all `LintExpectationId`s are stable");
23+
}
24+
}
25+
}
26+
}
27+
28+
fn emit_unfulfilled_expectation_lint(
29+
tcx: TyCtxt<'_>,
30+
hir_id: HirId,
31+
expectation: &LintExpectation,
32+
) {
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.
37+
tcx.struct_span_lint_hir(
38+
builtin::UNFULFILLED_LINT_EXPECTATIONS,
39+
hir_id,
40+
expectation.emission_span,
41+
|diag| {
42+
let mut diag = diag.build("this lint expectation is unfulfilled");
43+
if let Some(rationale) = expectation.reason {
44+
diag.note(&rationale.as_str());
45+
}
46+
diag.emit();
47+
},
48+
);
49+
}

‎compiler/rustc_lint/src/late.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,4 +503,7 @@ pub fn check_crate<'tcx, T: LateLintPass<'tcx>>(
503503
});
504504
},
505505
);
506+
507+
// This check has to be run after all lints are done processing for this crate
508+
tcx.sess.time("check_lint_expectations", || crate::expect::check_expectations(tcx));
506509
}

‎compiler/rustc_lint/src/levels.rs

Lines changed: 79 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,15 @@ use rustc_errors::{struct_span_err, Applicability, Diagnostic};
77
use rustc_hir as hir;
88
use rustc_hir::{intravisit, HirId};
99
use rustc_middle::hir::nested_filter;
10-
use rustc_middle::lint::LevelAndSource;
11-
use rustc_middle::lint::LintDiagnosticBuilder;
1210
use rustc_middle::lint::{
13-
struct_lint_level, LintLevelMap, LintLevelSets, LintLevelSource, LintSet, LintStackIndex,
14-
COMMAND_LINE,
11+
struct_lint_level, LevelAndSource, LintDiagnosticBuilder, LintExpectation, LintLevelMap,
12+
LintLevelSets, LintLevelSource, LintSet, LintStackIndex, COMMAND_LINE,
1513
};
1614
use rustc_middle::ty::query::Providers;
1715
use rustc_middle::ty::{RegisteredTools, TyCtxt};
1816
use rustc_session::lint::{
1917
builtin::{self, FORBIDDEN_LINT_GROUPS},
20-
Level, Lint, LintId,
18+
Level, Lint, LintExpectationId, LintId,
2119
};
2220
use rustc_session::parse::feature_err;
2321
use rustc_session::Session;
@@ -34,16 +32,23 @@ fn lint_levels(tcx: TyCtxt<'_>, (): ()) -> LintLevelMap {
3432

3533
builder.levels.id_to_set.reserve(krate.owners.len() + 1);
3634

37-
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+
3838
builder.levels.register_id(hir::CRATE_HIR_ID);
3939
tcx.hir().walk_toplevel_module(&mut builder);
4040
builder.levels.pop(push);
4141

42+
builder.levels.update_unstable_expectation_ids();
4243
builder.levels.build_map()
4344
}
4445

4546
pub struct LintLevelsBuilder<'s> {
4647
sess: &'s Session,
48+
lint_expectations: Vec<(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>,
4752
sets: LintLevelSets,
4853
id_to_set: FxHashMap<HirId, LintStackIndex>,
4954
cur: LintStackIndex,
@@ -66,6 +71,8 @@ impl<'s> LintLevelsBuilder<'s> {
6671
) -> Self {
6772
let mut builder = LintLevelsBuilder {
6873
sess,
74+
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,24 @@ 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()) else {
235-
continue
245+
for (attr_index, attr) in attrs.iter().enumerate() {
246+
let level = match Level::from_attr(attr) {
247+
None => continue,
248+
Some(Level::Expect(unstable_id)) if let Some(hir_id) = source_hir_id => {
249+
let stable_id = self.create_stable_id(unstable_id, hir_id, attr_index);
250+
251+
Level::Expect(stable_id)
252+
}
253+
Some(lvl) => lvl,
236254
};
237255

238256
let Some(mut metas) = attr.meta_item_list() else {
@@ -285,9 +303,17 @@ impl<'s> LintLevelsBuilder<'s> {
285303
}
286304
}
287305

288-
for li in metas {
306+
for (lint_index, li) in metas.iter_mut().enumerate() {
307+
let level = match level {
308+
Level::Expect(mut id) => {
309+
id.set_lint_index(Some(lint_index as u16));
310+
Level::Expect(id)
311+
}
312+
level => level,
313+
};
314+
289315
let sp = li.span();
290-
let mut meta_item = match li {
316+
let meta_item = match li {
291317
ast::NestedMetaItem::MetaItem(meta_item) if meta_item.is_word() => meta_item,
292318
_ => {
293319
let mut err = bad_attr(sp);
@@ -327,6 +353,10 @@ impl<'s> LintLevelsBuilder<'s> {
327353
self.check_gated_lint(id, attr.span);
328354
self.insert_spec(&mut specs, id, (level, src));
329355
}
356+
if let Level::Expect(expect_id) = level {
357+
self.lint_expectations
358+
.push((expect_id, LintExpectation::new(reason, sp)));
359+
}
330360
}
331361

332362
CheckLintNameResult::Tool(result) => {
@@ -342,6 +372,10 @@ impl<'s> LintLevelsBuilder<'s> {
342372
for id in ids {
343373
self.insert_spec(&mut specs, *id, (level, src));
344374
}
375+
if let Level::Expect(expect_id) = level {
376+
self.lint_expectations
377+
.push((expect_id, LintExpectation::new(reason, sp)));
378+
}
345379
}
346380
Err((Some(ids), ref new_lint_name)) => {
347381
let lint = builtin::RENAMED_AND_REMOVED_LINTS;
@@ -378,6 +412,10 @@ impl<'s> LintLevelsBuilder<'s> {
378412
for id in ids {
379413
self.insert_spec(&mut specs, *id, (level, src));
380414
}
415+
if let Level::Expect(expect_id) = level {
416+
self.lint_expectations
417+
.push((expect_id, LintExpectation::new(reason, sp)));
418+
}
381419
}
382420
Err((None, _)) => {
383421
// If Tool(Err(None, _)) is returned, then either the lint does not
@@ -471,6 +509,10 @@ impl<'s> LintLevelsBuilder<'s> {
471509
self.check_gated_lint(id, attr.span);
472510
self.insert_spec(&mut specs, id, (level, src));
473511
}
512+
if let Level::Expect(expect_id) = level {
513+
self.lint_expectations
514+
.push((expect_id, LintExpectation::new(reason, sp)));
515+
}
474516
} else {
475517
panic!("renamed lint does not exist: {}", new_name);
476518
}
@@ -519,6 +561,20 @@ impl<'s> LintLevelsBuilder<'s> {
519561
BuilderPush { prev, changed: prev != self.cur }
520562
}
521563

564+
fn create_stable_id(
565+
&mut self,
566+
unstable_id: LintExpectationId,
567+
hir_id: HirId,
568+
attr_index: usize,
569+
) -> LintExpectationId {
570+
let stable_id =
571+
LintExpectationId::Stable { hir_id, attr_index: attr_index as u16, lint_index: None };
572+
573+
self.expectation_id_map.insert(unstable_id, stable_id);
574+
575+
stable_id
576+
}
577+
522578
/// Checks if the lint is gated on a feature that is not enabled.
523579
fn check_gated_lint(&self, lint_id: LintId, span: Span) {
524580
if let Some(feature) = lint_id.lint.feature_gate {
@@ -562,8 +618,16 @@ impl<'s> LintLevelsBuilder<'s> {
562618
self.id_to_set.insert(id, self.cur);
563619
}
564620

621+
fn update_unstable_expectation_ids(&self) {
622+
self.sess.diagnostic().update_unstable_expectation_id(&self.expectation_id_map);
623+
}
624+
565625
pub fn build_map(self) -> LintLevelMap {
566-
LintLevelMap { sets: self.sets, id_to_set: self.id_to_set }
626+
LintLevelMap {
627+
sets: self.sets,
628+
id_to_set: self.id_to_set,
629+
lint_expectations: self.lint_expectations,
630+
}
567631
}
568632
}
569633

@@ -579,7 +643,8 @@ impl LintLevelMapBuilder<'_> {
579643
{
580644
let is_crate_hir = id == hir::CRATE_HIR_ID;
581645
let attrs = self.tcx.hir().attrs(id);
582-
let push = self.levels.push(attrs, is_crate_hir);
646+
let push = self.levels.push(attrs, is_crate_hir, Some(id));
647+
583648
if push.changed {
584649
self.levels.register_id(id);
585650
}

‎compiler/rustc_lint/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ pub mod builtin;
5151
mod context;
5252
mod early;
5353
mod enum_intrinsics_non_enums;
54+
mod expect;
5455
pub mod hidden_unicode_codepoints;
5556
mod internal;
5657
mod late;

‎compiler/rustc_lint_defs/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +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_hir = { path = "../rustc_hir" }

‎compiler/rustc_lint_defs/src/builtin.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,39 @@ declare_lint! {
495495
"unrecognized lint attribute"
496496
}
497497

498+
declare_lint! {
499+
/// The `unfulfilled_lint_expectations` lint detects lint trigger expectations
500+
/// that have not been fulfilled.
501+
///
502+
/// ### Example
503+
///
504+
/// ```rust
505+
/// #![feature(lint_reasons)]
506+
///
507+
/// #[expect(unused_variables)]
508+
/// let x = 10;
509+
/// println!("{}", x);
510+
/// ```
511+
///
512+
/// {{produces}}
513+
///
514+
/// ### Explanation
515+
///
516+
/// It was expected that the marked code would emit a lint. This expectation
517+
/// has not been fulfilled.
518+
///
519+
/// The `expect` attribute can be removed if this is intended behavior otherwise
520+
/// it should be investigated why the expected lint is no longer issued.
521+
///
522+
/// Part of RFC 2383. The progress is being tracked in [#54503]
523+
///
524+
/// [#54503]: https://github.com/rust-lang/rust/issues/54503
525+
pub UNFULFILLED_LINT_EXPECTATIONS,
526+
Warn,
527+
"unfulfilled lint expectation",
528+
@feature_gate = rustc_span::sym::lint_reasons;
529+
}
530+
498531
declare_lint! {
499532
/// The `unused_variables` lint detects variables which are not used in
500533
/// any way.
@@ -3007,6 +3040,7 @@ declare_lint_pass! {
30073040
UNUSED_CRATE_DEPENDENCIES,
30083041
UNUSED_QUALIFICATIONS,
30093042
UNKNOWN_LINTS,
3043+
UNFULFILLED_LINT_EXPECTATIONS,
30103044
UNUSED_VARIABLES,
30113045
UNUSED_ASSIGNMENTS,
30123046
DEAD_CODE,

‎compiler/rustc_lint_defs/src/lib.rs

Lines changed: 120 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
#![feature(min_specialization)]
2+
13
#[macro_use]
24
extern crate rustc_macros;
35

46
pub use self::Level::*;
57
use rustc_ast::node_id::{NodeId, NodeMap};
8+
use rustc_ast::{AttrId, Attribute};
69
use rustc_data_structures::stable_hasher::{HashStable, StableHasher, ToStableHashKey};
10+
use rustc_hir::HirId;
711
use rustc_serialize::json::Json;
812
use rustc_span::edition::Edition;
913
use rustc_span::{sym, symbol::Ident, MultiSpan, Span, Symbol};
@@ -46,13 +50,119 @@ pub enum Applicability {
4650
Unspecified,
4751
}
4852

53+
/// Each lint expectation has a `LintExpectationId` assigned by the `LintLevelsBuilder`.
54+
/// Expected `Diagnostic`s get the lint level `Expect` which stores the `LintExpectationId`
55+
/// to match it with the actual expectation later on.
56+
///
57+
/// The `LintExpectationId` has to be has stable between compilations, as diagnostic
58+
/// instances might be loaded from cache. Lint messages can be emitted during an
59+
/// `EarlyLintPass` operating on the AST and during a `LateLintPass` traversing the
60+
/// HIR tree. The AST doesn't have enough information to create a stable id. The
61+
/// `LintExpectationId` will instead store the [`AttrId`] defining the expectation.
62+
/// These `LintExpectationId` will be updated to use the stable [`HirId`] once the
63+
/// AST has been lowered. The transformation is done by the `LintLevelsBuilder`
64+
///
65+
/// Each lint inside the `expect` attribute is tracked individually, the `lint_index`
66+
/// identifies the lint inside the attribute and ensures that the IDs are unique.
67+
///
68+
/// The index values have a type of `u16` to reduce the size of the `LintExpectationId`.
69+
/// It's reasonable to assume that no user will define 2^16 attributes on one node or
70+
/// have that amount of lints listed. `u16` values should therefore suffice.
71+
#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash, Encodable, Decodable)]
72+
pub enum LintExpectationId {
73+
/// Used for lints emitted during the `EarlyLintPass`. This id is not
74+
/// has stable and should not be cached.
75+
Unstable { attr_id: AttrId, lint_index: Option<u16> },
76+
/// The [`HirId`] that the lint expectation is attached to. This id is
77+
/// stable and can be cached. The additional index ensures that nodes with
78+
/// several expectations can correctly match diagnostics to the individual
79+
/// expectation.
80+
Stable { hir_id: HirId, attr_index: u16, lint_index: Option<u16> },
81+
}
82+
83+
impl LintExpectationId {
84+
pub fn is_stable(&self) -> bool {
85+
match self {
86+
LintExpectationId::Unstable { .. } => false,
87+
LintExpectationId::Stable { .. } => true,
88+
}
89+
}
90+
91+
pub fn get_lint_index(&self) -> Option<u16> {
92+
let (LintExpectationId::Unstable { lint_index, .. }
93+
| LintExpectationId::Stable { lint_index, .. }) = self;
94+
95+
*lint_index
96+
}
97+
98+
pub fn set_lint_index(&mut self, new_lint_index: Option<u16>) {
99+
let (LintExpectationId::Unstable { ref mut lint_index, .. }
100+
| LintExpectationId::Stable { ref mut lint_index, .. }) = self;
101+
102+
*lint_index = new_lint_index
103+
}
104+
}
105+
106+
impl<HCX: rustc_hir::HashStableContext> HashStable<HCX> for LintExpectationId {
107+
#[inline]
108+
fn hash_stable(&self, hcx: &mut HCX, hasher: &mut StableHasher) {
109+
match self {
110+
LintExpectationId::Stable { hir_id, attr_index, lint_index: Some(lint_index) } => {
111+
hir_id.hash_stable(hcx, hasher);
112+
attr_index.hash_stable(hcx, hasher);
113+
lint_index.hash_stable(hcx, hasher);
114+
}
115+
_ => {
116+
unreachable!("HashStable should only be called for a filled `LintExpectationId`")
117+
}
118+
}
119+
}
120+
}
121+
122+
impl<HCX: rustc_hir::HashStableContext> ToStableHashKey<HCX> for LintExpectationId {
123+
type KeyType = (HirId, u16, u16);
124+
125+
#[inline]
126+
fn to_stable_hash_key(&self, _: &HCX) -> Self::KeyType {
127+
match self {
128+
LintExpectationId::Stable { hir_id, attr_index, lint_index: Some(lint_index) } => {
129+
(*hir_id, *attr_index, *lint_index)
130+
}
131+
_ => {
132+
unreachable!("HashStable should only be called for a filled `LintExpectationId`")
133+
}
134+
}
135+
}
136+
}
137+
49138
/// Setting for how to handle a lint.
139+
///
140+
/// See: <https://doc.rust-lang.org/rustc/lints/levels.html>
50141
#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
51142
pub enum Level {
143+
/// The `allow` level will not issue any message.
52144
Allow,
145+
/// The `expect` level will suppress the lint message but in turn produce a message
146+
/// if the lint wasn't issued in the expected scope. `Expect` should not be used as
147+
/// an initial level for a lint.
148+
///
149+
/// Note that this still means that the lint is enabled in this position and should
150+
/// be emitted, this will in turn fulfill the expectation and suppress the lint.
151+
///
152+
/// See RFC 2383.
153+
///
154+
/// The `LintExpectationId` is used to later link a lint emission to the actual
155+
/// expectation. It can be ignored in most cases.
156+
Expect(LintExpectationId),
157+
/// The `warn` level will produce a warning if the lint was violated, however the
158+
/// compiler will continue with its execution.
53159
Warn,
54160
ForceWarn,
161+
/// The `deny` level will produce an error and stop further execution after the lint
162+
/// pass is complete.
55163
Deny,
164+
/// `Forbid` is equivalent to the `deny` level but can't be overwritten like the previous
165+
/// levels.
56166
Forbid,
57167
}
58168

@@ -63,28 +173,34 @@ impl Level {
63173
pub fn as_str(self) -> &'static str {
64174
match self {
65175
Level::Allow => "allow",
176+
Level::Expect(_) => "expect",
66177
Level::Warn => "warn",
67178
Level::ForceWarn => "force-warn",
68179
Level::Deny => "deny",
69180
Level::Forbid => "forbid",
70181
}
71182
}
72183

73-
/// Converts a lower-case string to a level.
184+
/// Converts a lower-case string to a level. This will never construct the expect
185+
/// level as that would require a [`LintExpectationId`]
74186
pub fn from_str(x: &str) -> Option<Level> {
75187
match x {
76188
"allow" => Some(Level::Allow),
77189
"warn" => Some(Level::Warn),
78190
"deny" => Some(Level::Deny),
79191
"forbid" => Some(Level::Forbid),
80-
_ => None,
192+
"expect" | _ => None,
81193
}
82194
}
83195

84196
/// Converts a symbol to a level.
85-
pub fn from_symbol(x: Symbol) -> Option<Level> {
86-
match x {
197+
pub fn from_attr(attr: &Attribute) -> Option<Level> {
198+
match attr.name_or_empty() {
87199
sym::allow => Some(Level::Allow),
200+
sym::expect => Some(Level::Expect(LintExpectationId::Unstable {
201+
attr_id: attr.id,
202+
lint_index: None,
203+
})),
88204
sym::warn => Some(Level::Warn),
89205
sym::deny => Some(Level::Deny),
90206
sym::forbid => Some(Level::Forbid),

‎compiler/rustc_middle/src/lint.rs

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use rustc_index::vec::IndexVec;
88
use rustc_query_system::ich::StableHashingContext;
99
use rustc_session::lint::{
1010
builtin::{self, FORBIDDEN_LINT_GROUPS},
11-
FutureIncompatibilityReason, Level, Lint, LintId,
11+
FutureIncompatibilityReason, Level, Lint, LintExpectationId, LintId,
1212
};
1313
use rustc_session::{DiagnosticMessageId, Session};
1414
use rustc_span::hygiene::MacroKind;
@@ -153,6 +153,13 @@ impl LintLevelSets {
153153

154154
#[derive(Debug)]
155155
pub struct LintLevelMap {
156+
/// This is a collection of lint expectations as described in RFC 2383, that
157+
/// can be fulfilled during this compilation session. This means that at least
158+
/// one expected lint is currently registered in the lint store.
159+
///
160+
/// The [`LintExpectationId`] is stored as a part of the [`Expect`](Level::Expect)
161+
/// lint level.
162+
pub lint_expectations: Vec<(LintExpectationId, LintExpectation)>,
156163
pub sets: LintLevelSets,
157164
pub id_to_set: FxHashMap<HirId, LintStackIndex>,
158165
}
@@ -178,14 +185,33 @@ impl LintLevelMap {
178185
impl<'a> HashStable<StableHashingContext<'a>> for LintLevelMap {
179186
#[inline]
180187
fn hash_stable(&self, hcx: &mut StableHashingContext<'a>, hasher: &mut StableHasher) {
181-
let LintLevelMap { ref sets, ref id_to_set } = *self;
188+
let LintLevelMap { ref sets, ref id_to_set, ref lint_expectations } = *self;
182189

183190
id_to_set.hash_stable(hcx, hasher);
191+
lint_expectations.hash_stable(hcx, hasher);
184192

185193
hcx.while_hashing_spans(true, |hcx| sets.hash_stable(hcx, hasher))
186194
}
187195
}
188196

197+
/// This struct represents a lint expectation and holds all required information
198+
/// to emit the `unfulfilled_lint_expectations` lint if it is unfulfilled after
199+
/// the `LateLintPass` has completed.
200+
#[derive(Clone, Debug, HashStable)]
201+
pub struct LintExpectation {
202+
/// The reason for this expectation that can optionally be added as part of
203+
/// the attribute. It will be displayed as part of the lint message.
204+
pub reason: Option<Symbol>,
205+
/// The [`Span`] of the attribute that this expectation originated from.
206+
pub emission_span: Span,
207+
}
208+
209+
impl LintExpectation {
210+
pub fn new(reason: Option<Symbol>, attr_span: Span) -> Self {
211+
Self { reason, emission_span: attr_span }
212+
}
213+
}
214+
189215
pub struct LintDiagnosticBuilder<'a>(DiagnosticBuilder<'a, ()>);
190216

191217
impl<'a> LintDiagnosticBuilder<'a> {
@@ -225,6 +251,9 @@ pub fn explain_lint_level_source(
225251
Level::Forbid => "-F",
226252
Level::Allow => "-A",
227253
Level::ForceWarn => "--force-warn",
254+
Level::Expect(_) => {
255+
unreachable!("the expect level does not have a commandline flag")
256+
}
228257
};
229258
let hyphen_case_lint_name = name.replace('_', "-");
230259
if lint_flag_val.as_str() == name {
@@ -314,6 +343,16 @@ pub fn struct_lint_level<'s, 'd>(
314343
return;
315344
}
316345
}
346+
(Level::Expect(expect_id), _) => {
347+
// This case is special as we actually allow the lint itself in this context, but
348+
// we can't return early like in the case for `Level::Allow` because we still
349+
// need the lint diagnostic to be emitted to `rustc_error::HanderInner`.
350+
//
351+
// We can also not mark the lint expectation as fulfilled here right away, as it
352+
// can still be cancelled in the decorate function. All of this means that we simply
353+
// create a `DiagnosticBuilder` and continue as we would for warnings.
354+
sess.struct_expect("", expect_id)
355+
}
317356
(Level::Warn | Level::ForceWarn, Some(span)) => sess.struct_span_warn(span, ""),
318357
(Level::Warn | Level::ForceWarn, None) => sess.struct_warn(""),
319358
(Level::Deny | Level::Forbid, Some(span)) => {
@@ -346,6 +385,17 @@ pub fn struct_lint_level<'s, 'd>(
346385
}
347386
}
348387

388+
// Lint diagnostics that are covered by the expect level will not be emitted outside
389+
// the compiler. It is therefore not necessary to add any information for the user.
390+
// This will therefore directly call the decorate function which will in turn emit
391+
// the `Diagnostic`.
392+
if let Level::Expect(_) = level {
393+
let name = lint.name_lower();
394+
err.code(DiagnosticId::Lint { name, has_future_breakage, is_force_warn: false });
395+
decorate(LintDiagnosticBuilder::new(err));
396+
return;
397+
}
398+
349399
explain_lint_level_source(sess, lint, level, src, &mut err);
350400

351401
let name = lint.name_lower();

‎compiler/rustc_middle/src/ty/context.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2755,7 +2755,7 @@ impl<'tcx> TyCtxt<'tcx> {
27552755
return bound;
27562756
}
27572757

2758-
if hir.attrs(id).iter().any(|attr| Level::from_symbol(attr.name_or_empty()).is_some()) {
2758+
if hir.attrs(id).iter().any(|attr| Level::from_attr(attr).is_some()) {
27592759
return id;
27602760
}
27612761
let next = hir.get_parent_node(id);

‎compiler/rustc_session/src/session.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,13 @@ impl Session {
331331
pub fn struct_allow(&self, msg: &str) -> DiagnosticBuilder<'_, ()> {
332332
self.diagnostic().struct_allow(msg)
333333
}
334+
pub fn struct_expect(
335+
&self,
336+
msg: &str,
337+
id: lint::LintExpectationId,
338+
) -> DiagnosticBuilder<'_, ()> {
339+
self.diagnostic().struct_expect(msg, id)
340+
}
334341
pub fn struct_span_err<S: Into<MultiSpan>>(
335342
&self,
336343
sp: S,
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// check-pass
2+
3+
#![feature(lint_reasons)]
4+
5+
#![warn(unused)]
6+
7+
// This expect attribute should catch all lint triggers
8+
#[expect(unused_variables)]
9+
fn check_multiple_lints_1() {
10+
let value_i = 0xff00ff;
11+
let value_ii = 0xff00ff;
12+
let value_iii = 0xff00ff;
13+
let value_iiii = 0xff00ff;
14+
let value_iiiii = 0xff00ff;
15+
}
16+
17+
// This expect attribute should catch all lint triggers
18+
#[expect(unused_mut)]
19+
fn check_multiple_lints_2() {
20+
let mut a = 0xa;
21+
let mut b = 0xb;
22+
let mut c = 0xc;
23+
println!("The ABC goes as: {:#x} {:#x} {:#x}", a, b, c);
24+
}
25+
26+
// This expect attribute should catch all lint triggers
27+
#[expect(while_true)]
28+
fn check_multiple_lints_3() {
29+
// `while_true` is an early lint
30+
while true {}
31+
32+
while true {}
33+
34+
while true {}
35+
36+
while true {}
37+
38+
while true {}
39+
}
40+
41+
fn main() {
42+
check_multiple_lints_1();
43+
check_multiple_lints_2();
44+
check_multiple_lints_3();
45+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// check-pass
2+
3+
#![feature(lint_reasons)]
4+
5+
#![warn(unused)]
6+
7+
#![expect(unused_mut)]
8+
//~^ WARNING this lint expectation is unfulfilled [unfulfilled_lint_expectations]
9+
//~| NOTE `#[warn(unfulfilled_lint_expectations)]` on by default
10+
11+
#![expect(unused_variables)]
12+
13+
fn main() {
14+
let x = 0;
15+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
warning: this lint expectation is unfulfilled
2+
--> $DIR/crate_level_expect.rs:7:11
3+
|
4+
LL | #![expect(unused_mut)]
5+
| ^^^^^^^^^^
6+
|
7+
= note: `#[warn(unfulfilled_lint_expectations)]` on by default
8+
9+
warning: 1 warning emitted
10+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// check-pass
2+
3+
#![feature(lint_reasons)]
4+
5+
#![warn(unused)]
6+
7+
macro_rules! expect_inside_macro {
8+
() => {
9+
#[expect(unused_variables)]
10+
let x = 0;
11+
};
12+
}
13+
14+
fn main() {
15+
expect_inside_macro!();
16+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// check-pass
2+
3+
#![feature(lint_reasons)]
4+
5+
#![warn(unused_variables)]
6+
7+
macro_rules! trigger_unused_variables_macro {
8+
() => {
9+
let x = 0;
10+
//~^ WARNING unused variable: `x` [unused_variables]
11+
//~| WARNING unused variable: `x` [unused_variables]
12+
};
13+
}
14+
15+
pub fn check_macro() {
16+
// This should trigger the `unused_variables` from inside the macro
17+
trigger_unused_variables_macro!();
18+
}
19+
20+
// This should be fulfilled by the macro
21+
#[expect(unused_variables)]
22+
pub fn check_expect_on_item() {
23+
trigger_unused_variables_macro!();
24+
}
25+
26+
pub fn check_expect_on_macro() {
27+
// This should be fulfilled by the macro
28+
#[expect(unused_variables)]
29+
trigger_unused_variables_macro!();
30+
31+
// FIXME: Lint attributes currently don't work directly on macros, and
32+
// therefore also doesn't work for the new `expect` attribute. This bug
33+
// is being tracked in rust#87391. The test will until then produce two
34+
// warnings about the unused variable x.
35+
//
36+
// The expectation is still marked as fulfilled. I'm not totally why but
37+
// my guess is that this will remain working when rust#87391 has been fixed.
38+
}
39+
40+
fn main() {
41+
42+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
warning: unused variable: `x`
2+
--> $DIR/expect_lint_from_macro.rs:9:13
3+
|
4+
LL | let x = 0;
5+
| ^ help: if this is intentional, prefix it with an underscore: `_x`
6+
...
7+
LL | trigger_unused_variables_macro!();
8+
| --------------------------------- in this macro invocation
9+
|
10+
note: the lint level is defined here
11+
--> $DIR/expect_lint_from_macro.rs:5:9
12+
|
13+
LL | #![warn(unused_variables)]
14+
| ^^^^^^^^^^^^^^^^
15+
= note: this warning originates in the macro `trigger_unused_variables_macro` (in Nightly builds, run with -Z macro-backtrace for more info)
16+
17+
warning: unused variable: `x`
18+
--> $DIR/expect_lint_from_macro.rs:9:13
19+
|
20+
LL | let x = 0;
21+
| ^ help: if this is intentional, prefix it with an underscore: `_x`
22+
...
23+
LL | trigger_unused_variables_macro!();
24+
| --------------------------------- in this macro invocation
25+
|
26+
= note: this warning originates in the macro `trigger_unused_variables_macro` (in Nightly builds, run with -Z macro-backtrace for more info)
27+
28+
warning: 2 warnings emitted
29+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// should error due to missing feature gate.
2+
3+
#![warn(unused)]
4+
5+
#[expect(unused)]
6+
//~^ ERROR: the `#[expect]` attribute is an experimental feature [E0658]
7+
fn main() {
8+
let x = 1;
9+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
error[E0658]: the `#[expect]` attribute is an experimental feature
2+
--> $DIR/expect_missing_feature_gate.rs:5:1
3+
|
4+
LL | #[expect(unused)]
5+
| ^^^^^^^^^^^^^^^^^
6+
|
7+
= note: see issue #54503 <https://github.com/rust-lang/rust/issues/54503> for more information
8+
= help: add `#![feature(lint_reasons)]` to the crate attributes to enable
9+
10+
error: aborting due to previous error
11+
12+
For more information about this error, try `rustc --explain E0658`.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// check-pass
2+
3+
#![feature(lint_reasons)]
4+
5+
#![warn(unused)]
6+
7+
// The warnings are not double triggers, they identify different unfulfilled lint
8+
// expectations one for each listed lint.
9+
10+
#[expect(unused_variables, unused_mut, while_true)]
11+
//~^ WARNING this lint expectation is unfulfilled [unfulfilled_lint_expectations]
12+
//~| NOTE `#[warn(unfulfilled_lint_expectations)]` on by default
13+
//~| WARNING this lint expectation is unfulfilled [unfulfilled_lint_expectations]
14+
fn check_multiple_lints_1() {
15+
// This only trigger `unused_variables`
16+
let who_am_i = 666;
17+
}
18+
19+
#[expect(unused_variables, unused_mut, while_true)]
20+
//~^ WARNING this lint expectation is unfulfilled [unfulfilled_lint_expectations]
21+
//~| WARNING this lint expectation is unfulfilled [unfulfilled_lint_expectations]
22+
fn check_multiple_lints_2() {
23+
// This only triggers `unused_mut`
24+
let mut x = 0;
25+
println!("I use x: {}", x);
26+
}
27+
28+
#[expect(unused_variables, unused_mut, while_true)]
29+
//~^ WARNING this lint expectation is unfulfilled [unfulfilled_lint_expectations]
30+
//~| WARNING this lint expectation is unfulfilled [unfulfilled_lint_expectations]
31+
fn check_multiple_lints_3() {
32+
// This only triggers `while_true` which is also an early lint
33+
while true {}
34+
}
35+
36+
#[expect(unused, while_true)]
37+
//~^ WARNING this lint expectation is unfulfilled [unfulfilled_lint_expectations]
38+
fn check_multiple_lints_with_lint_group_1() {
39+
let who_am_i = 666;
40+
41+
let mut x = 0;
42+
println!("I use x: {}", x);
43+
}
44+
45+
#[expect(unused, while_true)]
46+
//~^ WARNING this lint expectation is unfulfilled [unfulfilled_lint_expectations]
47+
fn check_multiple_lints_with_lint_group_2() {
48+
while true {}
49+
}
50+
51+
fn main() {
52+
check_multiple_lints_1();
53+
check_multiple_lints_2();
54+
check_multiple_lints_3();
55+
56+
check_multiple_lints_with_lint_group_1();
57+
check_multiple_lints_with_lint_group_2();
58+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
warning: this lint expectation is unfulfilled
2+
--> $DIR/expect_multiple_lints.rs:10:28
3+
|
4+
LL | #[expect(unused_variables, unused_mut, while_true)]
5+
| ^^^^^^^^^^
6+
|
7+
= note: `#[warn(unfulfilled_lint_expectations)]` on by default
8+
9+
warning: this lint expectation is unfulfilled
10+
--> $DIR/expect_multiple_lints.rs:10:40
11+
|
12+
LL | #[expect(unused_variables, unused_mut, while_true)]
13+
| ^^^^^^^^^^
14+
15+
warning: this lint expectation is unfulfilled
16+
--> $DIR/expect_multiple_lints.rs:19:10
17+
|
18+
LL | #[expect(unused_variables, unused_mut, while_true)]
19+
| ^^^^^^^^^^^^^^^^
20+
21+
warning: this lint expectation is unfulfilled
22+
--> $DIR/expect_multiple_lints.rs:19:40
23+
|
24+
LL | #[expect(unused_variables, unused_mut, while_true)]
25+
| ^^^^^^^^^^
26+
27+
warning: this lint expectation is unfulfilled
28+
--> $DIR/expect_multiple_lints.rs:28:10
29+
|
30+
LL | #[expect(unused_variables, unused_mut, while_true)]
31+
| ^^^^^^^^^^^^^^^^
32+
33+
warning: this lint expectation is unfulfilled
34+
--> $DIR/expect_multiple_lints.rs:28:28
35+
|
36+
LL | #[expect(unused_variables, unused_mut, while_true)]
37+
| ^^^^^^^^^^
38+
39+
warning: this lint expectation is unfulfilled
40+
--> $DIR/expect_multiple_lints.rs:36:18
41+
|
42+
LL | #[expect(unused, while_true)]
43+
| ^^^^^^^^^^
44+
45+
warning: this lint expectation is unfulfilled
46+
--> $DIR/expect_multiple_lints.rs:45:10
47+
|
48+
LL | #[expect(unused, while_true)]
49+
| ^^^^^^
50+
51+
warning: 8 warnings emitted
52+
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// ignore-tidy-linelength
2+
3+
#![feature(lint_reasons)]
4+
#![warn(unused_mut)]
5+
6+
#[expect(
7+
unused_mut,
8+
//~^ WARNING this lint expectation is unfulfilled [unfulfilled_lint_expectations]
9+
//~| NOTE `#[warn(unfulfilled_lint_expectations)]` on by default
10+
//~| NOTE this `expect` is overridden by a `allow` attribute before the `unused_mut` lint is triggered
11+
reason = "this `expect` is overridden by a `allow` attribute before the `unused_mut` lint is triggered"
12+
)]
13+
mod foo {
14+
fn bar() {
15+
#[allow(
16+
unused_mut,
17+
reason = "this overrides the previous `expect` lint level and allows the `unused_mut` lint here"
18+
)]
19+
let mut v = 0;
20+
}
21+
}
22+
23+
#[expect(
24+
unused_mut,
25+
//~^ WARNING this lint expectation is unfulfilled [unfulfilled_lint_expectations]
26+
//~| NOTE this `expect` is overridden by a `warn` attribute before the `unused_mut` lint is triggered
27+
reason = "this `expect` is overridden by a `warn` attribute before the `unused_mut` lint is triggered"
28+
)]
29+
mod oof {
30+
#[warn(
31+
unused_mut,
32+
//~^ NOTE the lint level is defined here
33+
reason = "this overrides the previous `expect` lint level and warns about the `unused_mut` lint here"
34+
)]
35+
fn bar() {
36+
let mut v = 0;
37+
//~^ WARNING variable does not need to be mutable [unused_mut]
38+
//~| NOTE this overrides the previous `expect` lint level and warns about the `unused_mut` lint here
39+
//~| HELP remove this `mut`
40+
}
41+
}
42+
43+
#[expect(unused_variables)]
44+
//~^ WARNING this lint expectation is unfulfilled
45+
#[forbid(unused_variables)]
46+
//~^ NOTE the lint level is defined here
47+
fn check_expect_then_forbid() {
48+
let this_is_my_function = 3;
49+
//~^ ERROR unused variable: `this_is_my_function` [unused_variables]
50+
//~| HELP if this is intentional, prefix it with an underscore
51+
}
52+
53+
fn main() {}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
error: unused variable: `this_is_my_function`
2+
--> $DIR/expect_nested_lint_levels.rs:48:9
3+
|
4+
LL | let this_is_my_function = 3;
5+
| ^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_this_is_my_function`
6+
|
7+
note: the lint level is defined here
8+
--> $DIR/expect_nested_lint_levels.rs:45:10
9+
|
10+
LL | #[forbid(unused_variables)]
11+
| ^^^^^^^^^^^^^^^^
12+
13+
warning: variable does not need to be mutable
14+
--> $DIR/expect_nested_lint_levels.rs:36:13
15+
|
16+
LL | let mut v = 0;
17+
| ----^
18+
| |
19+
| help: remove this `mut`
20+
|
21+
= note: this overrides the previous `expect` lint level and warns about the `unused_mut` lint here
22+
note: the lint level is defined here
23+
--> $DIR/expect_nested_lint_levels.rs:31:9
24+
|
25+
LL | unused_mut,
26+
| ^^^^^^^^^^
27+
28+
warning: this lint expectation is unfulfilled
29+
--> $DIR/expect_nested_lint_levels.rs:7:5
30+
|
31+
LL | unused_mut,
32+
| ^^^^^^^^^^
33+
|
34+
= note: `#[warn(unfulfilled_lint_expectations)]` on by default
35+
= note: this `expect` is overridden by a `allow` attribute before the `unused_mut` lint is triggered
36+
37+
warning: this lint expectation is unfulfilled
38+
--> $DIR/expect_nested_lint_levels.rs:24:5
39+
|
40+
LL | unused_mut,
41+
| ^^^^^^^^^^
42+
|
43+
= note: this `expect` is overridden by a `warn` attribute before the `unused_mut` lint is triggered
44+
45+
warning: this lint expectation is unfulfilled
46+
--> $DIR/expect_nested_lint_levels.rs:43:10
47+
|
48+
LL | #[expect(unused_variables)]
49+
| ^^^^^^^^^^^^^^^^
50+
51+
error: aborting due to previous error; 4 warnings emitted
52+
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#![feature(lint_reasons)]
2+
3+
#[forbid(unused_variables)]
4+
//~^ NOTE `forbid` level set here
5+
//~| NOTE `forbid` level set here
6+
#[expect(unused_variables)]
7+
//~^ ERROR incompatible with previous forbid [E0453]
8+
//~| NOTE overruled by previous forbid
9+
//~| ERROR incompatible with previous forbid [E0453]
10+
//~| NOTE overruled by previous forbid
11+
fn expect_forbidden_lint_1() {}
12+
13+
#[forbid(while_true)]
14+
//~^ NOTE `forbid` level set here
15+
//~| NOTE `forbid` level set here
16+
//~| NOTE the lint level is defined here
17+
#[expect(while_true)]
18+
//~^ ERROR incompatible with previous forbid [E0453]
19+
//~| NOTE overruled by previous forbid
20+
//~| ERROR incompatible with previous forbid [E0453]
21+
//~| NOTE overruled by previous forbid
22+
fn expect_forbidden_lint_2() {
23+
// This while loop will produce a `while_true` lint as the lint level
24+
// at this node is still `forbid` and the `while_true` check happens
25+
// before the compilation terminates due to `E0453`
26+
while true {}
27+
//~^ ERROR denote infinite loops with `loop { ... }`
28+
//~| HELP use `loop`
29+
}
30+
31+
fn main() {
32+
expect_forbidden_lint_1();
33+
expect_forbidden_lint_2();
34+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
error[E0453]: expect(unused_variables) incompatible with previous forbid
2+
--> $DIR/expect_with_forbid.rs:6:10
3+
|
4+
LL | #[forbid(unused_variables)]
5+
| ---------------- `forbid` level set here
6+
...
7+
LL | #[expect(unused_variables)]
8+
| ^^^^^^^^^^^^^^^^ overruled by previous forbid
9+
10+
error[E0453]: expect(while_true) incompatible with previous forbid
11+
--> $DIR/expect_with_forbid.rs:17:10
12+
|
13+
LL | #[forbid(while_true)]
14+
| ---------- `forbid` level set here
15+
...
16+
LL | #[expect(while_true)]
17+
| ^^^^^^^^^^ overruled by previous forbid
18+
19+
error[E0453]: expect(unused_variables) incompatible with previous forbid
20+
--> $DIR/expect_with_forbid.rs:6:10
21+
|
22+
LL | #[forbid(unused_variables)]
23+
| ---------------- `forbid` level set here
24+
...
25+
LL | #[expect(unused_variables)]
26+
| ^^^^^^^^^^^^^^^^ overruled by previous forbid
27+
28+
error[E0453]: expect(while_true) incompatible with previous forbid
29+
--> $DIR/expect_with_forbid.rs:17:10
30+
|
31+
LL | #[forbid(while_true)]
32+
| ---------- `forbid` level set here
33+
...
34+
LL | #[expect(while_true)]
35+
| ^^^^^^^^^^ overruled by previous forbid
36+
37+
error: denote infinite loops with `loop { ... }`
38+
--> $DIR/expect_with_forbid.rs:26:5
39+
|
40+
LL | while true {}
41+
| ^^^^^^^^^^ help: use `loop`
42+
|
43+
note: the lint level is defined here
44+
--> $DIR/expect_with_forbid.rs:13:10
45+
|
46+
LL | #[forbid(while_true)]
47+
| ^^^^^^^^^^
48+
49+
error: aborting due to 5 previous errors
50+
51+
For more information about this error, try `rustc --explain E0453`.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// check-pass
2+
3+
#![feature(lint_reasons)]
4+
#![warn(unused)]
5+
6+
#![expect(unused_variables, reason = "<This should fail and display this reason>")]
7+
//~^ WARNING this lint expectation is unfulfilled [unfulfilled_lint_expectations]
8+
//~| NOTE `#[warn(unfulfilled_lint_expectations)]` on by default
9+
//~| NOTE <This should fail and display this reason>
10+
11+
fn main() {}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
warning: this lint expectation is unfulfilled
2+
--> $DIR/expect_with_reason.rs:6:11
3+
|
4+
LL | #![expect(unused_variables, reason = "<This should fail and display this reason>")]
5+
| ^^^^^^^^^^^^^^^^
6+
|
7+
= note: `#[warn(unfulfilled_lint_expectations)]` on by default
8+
= note: <This should fail and display this reason>
9+
10+
warning: 1 warning emitted
11+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// check-pass
2+
3+
#![feature(lint_reasons)]
4+
5+
fn expect_early_pass_lints() {
6+
#[expect(while_true)]
7+
while true {
8+
println!("I never stop")
9+
}
10+
11+
#[expect(unused_doc_comments)]
12+
/// This comment triggers the `unused_doc_comments` lint
13+
let _sheep = "wolf";
14+
15+
let x = 123;
16+
#[expect(ellipsis_inclusive_range_patterns)]
17+
match x {
18+
0...100 => {}
19+
_ => {}
20+
}
21+
}
22+
23+
fn main() {}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// check-pass
2+
3+
#![feature(lint_reasons)]
4+
#![warn(unused)]
5+
6+
#[expect(unused_variables)]
7+
fn check_specific_lint() {
8+
let x = 2;
9+
}
10+
11+
#[expect(unused)]
12+
fn check_lint_group() {
13+
let x = 15;
14+
}
15+
16+
#[expect(unused_variables)]
17+
fn check_multiple_lint_emissions() {
18+
let r = 1;
19+
let u = 8;
20+
let s = 2;
21+
let t = 9;
22+
}
23+
24+
mod check_fulfilled_expect_in_macro {
25+
macro_rules! expect_inside_macro {
26+
() => {
27+
#[expect(unused_variables)]
28+
let x = 0;
29+
};
30+
}
31+
32+
pub fn check_macro() {
33+
expect_inside_macro!();
34+
}
35+
}
36+
37+
fn main() {
38+
check_specific_lint();
39+
check_lint_group();
40+
check_multiple_lint_emissions();
41+
42+
check_fulfilled_expect_in_macro::check_macro();
43+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// check-pass
2+
3+
#![feature(lint_reasons)]
4+
#![warn(unused)]
5+
6+
#[warn(unused_variables)]
7+
#[expect(unused_variables)]
8+
//~^ WARNING this lint expectation is unfulfilled [unfulfilled_lint_expectations]
9+
//~| NOTE `#[warn(unfulfilled_lint_expectations)]` on by default
10+
#[allow(unused_variables)]
11+
#[expect(unused_variables)] // Only this expectation will be fulfilled
12+
fn main() {
13+
let x = 2;
14+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
warning: this lint expectation is unfulfilled
2+
--> $DIR/multiple_expect_attrs.rs:7:10
3+
|
4+
LL | #[expect(unused_variables)]
5+
| ^^^^^^^^^^^^^^^^
6+
|
7+
= note: `#[warn(unfulfilled_lint_expectations)]` on by default
8+
9+
warning: 1 warning emitted
10+

0 commit comments

Comments
 (0)
This repository has been archived.