From f3afa22943d76eb2b316b5037caba0a6bbebf75f Mon Sep 17 00:00:00 2001 From: Pranav Dronavalli Date: Wed, 12 Aug 2026 16:37:39 -0500 Subject: [PATCH] fix nested dead code lint expectations not fulfulling --- compiler/rustc_passes/src/dead.rs | 56 ++++++++++++++++++- .../expect_inside_dead_code.rs | 56 +++++++++++++++++++ .../expect_inside_dead_code.stderr | 10 ++++ 3 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 tests/ui/lint/rfc-2383-lint-reason/expect_inside_dead_code.rs create mode 100644 tests/ui/lint/rfc-2383-lint-reason/expect_inside_dead_code.stderr diff --git a/compiler/rustc_passes/src/dead.rs b/compiler/rustc_passes/src/dead.rs index 9b3a4099b8bee..a0fd1b392727a 100644 --- a/compiler/rustc_passes/src/dead.rs +++ b/compiler/rustc_passes/src/dead.rs @@ -1099,6 +1099,57 @@ impl<'tcx> DeadVisitor<'tcx> { (level_spec.level(), level_spec.lint_id()) } + fn fulfill_dead_code_expectations(&self, def_id: LocalDefId, include: bool) { + let fulfill_if_expected = |node: DefId| { + // Only consider local, dead symbols where lint level carries an expectation + if let Some(node) = node.as_local() + && !self.live_symbols.contains(&node) + && let (_, Some(expectation)) = self.def_lint_level_plus(node) + { + // Same mechanism as LintContext::fulfill_expectation. + self.tcx + .dcx() + .struct_expect( + "this is a dummy diagnostic, to submit and store an expectation", + expectation.into(), + ) + .emit(); + } + }; + + if include { + fulfill_if_expected(def_id.to_def_id()); + } + + match self.tcx.def_kind(def_id) { + DefKind::Struct | DefKind::Union | DefKind::Enum => { + let adt = self.tcx.adt_def(def_id); + for variant in adt.variants() { + if variant.def_id != def_id.to_def_id() { + fulfill_if_expected(variant.def_id); + } + for field in &variant.fields { + fulfill_if_expected(field.did); + } + } + } + DefKind::Variant => { + let parent_enum = self.tcx.local_parent(def_id); + let variant = self.tcx.adt_def(parent_enum).variant_with_id(def_id.to_def_id()); + //Check to see if fields carry expectation + for field in &variant.fields { + fulfill_if_expected(field.did); + } + } + DefKind::Trait => { + for &assoc_def_id in self.tcx.associated_item_def_ids(def_id) { + fulfill_if_expected(assoc_def_id); + } + } + _ => {} + } + } + fn dead_code_pub_in_binary_note(&self) -> Option { self.target_lint.name.eq(DEAD_CODE_PUB_IN_BINARY.name).then_some(DeadCodePubInBinaryNote) } @@ -1405,10 +1456,12 @@ fn lint_dead_codes<'tcx>( if !live_symbols.contains(&item.owner_id.def_id) { let parent = tcx.local_parent(item.owner_id.def_id); if parent != module.to_local_def_id() && !live_symbols.contains(&parent) { - // We already have diagnosed something. + // We already have diagnosed something, but check parent's field for #[expect] + visitor.fulfill_dead_code_expectations(item.owner_id.def_id, true); continue; } visitor.check_definition(item.owner_id.def_id); + visitor.fulfill_dead_code_expectations(item.owner_id.def_id, false); continue; } @@ -1422,6 +1475,7 @@ fn lint_dead_codes<'tcx>( // Record to group diagnostics. let level_plus = visitor.def_lint_level_plus(def_id); dead_variants.push(DeadItem { def_id, name: variant.name, level_plus }); + visitor.fulfill_dead_code_expectations(def_id, false); continue; } diff --git a/tests/ui/lint/rfc-2383-lint-reason/expect_inside_dead_code.rs b/tests/ui/lint/rfc-2383-lint-reason/expect_inside_dead_code.rs new file mode 100644 index 0000000000000..8640f56f7d08d --- /dev/null +++ b/tests/ui/lint/rfc-2383-lint-reason/expect_inside_dead_code.rs @@ -0,0 +1,56 @@ +//@ check-pass +// Expectations on dead code covered by a dead parent's diagnostic must still be fulfilled. + +#[cfg_attr(all(), expect(unused))] +struct Foo { + #[expect(unused)] + x: u32, +} + +#[expect(dead_code)] +struct Direct { + #[expect(dead_code)] + x: u32, +} + +#[expect(unused)] +enum DeadEnum { + Variant { + #[expect(unused)] + x: u32, + }, +} + +enum PartiallyDead { + Used, + #[expect(unused)] + Dead { + #[expect(unused)] + x: u32, + }, +} + +#[expect(unused)] +fn dead_outer() { + #[expect(unused)] + fn dead_inner() {} +} + +#[expect(unused)] +trait DeadTrait { + #[expect(unused)] + fn dead_method(&self); +} + +// `y` is read, so this expectation must stay unfulfilled. +struct Live { + #[expect(unused)] + //~^ WARNING this lint expectation is unfulfilled + //~| NOTE `#[warn(unfulfilled_lint_expectations)]` on by default + y: u32, +} + +fn main() { + let _ = PartiallyDead::Used; + let _ = Live { y: 1 }.y; +} diff --git a/tests/ui/lint/rfc-2383-lint-reason/expect_inside_dead_code.stderr b/tests/ui/lint/rfc-2383-lint-reason/expect_inside_dead_code.stderr new file mode 100644 index 0000000000000..e583497561125 --- /dev/null +++ b/tests/ui/lint/rfc-2383-lint-reason/expect_inside_dead_code.stderr @@ -0,0 +1,10 @@ +warning: this lint expectation is unfulfilled + --> $DIR/expect_inside_dead_code.rs:47:14 + | +LL | #[expect(unused)] + | ^^^^^^ + | + = note: `#[warn(unfulfilled_lint_expectations)]` on by default + +warning: 1 warning emitted +