From 421601518ee531ef53a270e5dd05cc82055453f6 Mon Sep 17 00:00:00 2001 From: Yukang Date: Mon, 6 Jul 2026 16:21:07 +0800 Subject: [PATCH 1/2] Fix unused variable warnings for diverging expressions --- compiler/rustc_mir_transform/src/liveness.rs | 9 ++++++ library/core/src/keyword_docs.rs | 1 - .../unused-never-binding-issue-158783.rs | 31 +++++++++++++++++++ tests/ui/reachable/never-assign-dead-code.rs | 2 +- .../reachable/never-assign-dead-code.stderr | 10 +----- 5 files changed, 42 insertions(+), 11 deletions(-) create mode 100644 tests/ui/lint/unused/unused-never-binding-issue-158783.rs diff --git a/compiler/rustc_mir_transform/src/liveness.rs b/compiler/rustc_mir_transform/src/liveness.rs index af1bad9f1baa1..59bb59065cef8 100644 --- a/compiler/rustc_mir_transform/src/liveness.rs +++ b/compiler/rustc_mir_transform/src/liveness.rs @@ -997,6 +997,15 @@ impl<'a, 'tcx> AssignmentResult<'a, 'tcx> { let statements = &mut self.assignments[index]; if statements.is_empty() { + if self.body.local_kind(local) == LocalKind::Temp + && matches!(binding.opt_match_place, Some((None, _))) + { + // A local from `let PAT = ...` normally has an assignment recorded for the + // value it initializes. If no assignment was recorded in reachable MIR, the + // initializer did not complete, so no initialized value exists to be unused. + continue; + } + if !self.is_local_in_reachable_code(local) { continue; } diff --git a/library/core/src/keyword_docs.rs b/library/core/src/keyword_docs.rs index 596765be5e2dd..f4779d9a1ab1e 100644 --- a/library/core/src/keyword_docs.rs +++ b/library/core/src/keyword_docs.rs @@ -2588,7 +2588,6 @@ mod while_keyword {} /// /// For example, the following Rust function will return `5`, causing `x` to take the [`!` type][never type]: /// ```rust -/// #[expect(unused_variables)] /// fn example() -> i32 { /// let x = { /// return 5; diff --git a/tests/ui/lint/unused/unused-never-binding-issue-158783.rs b/tests/ui/lint/unused/unused-never-binding-issue-158783.rs new file mode 100644 index 0000000000000..dbd3e0cdbe30c --- /dev/null +++ b/tests/ui/lint/unused/unused-never-binding-issue-158783.rs @@ -0,0 +1,31 @@ +//@ check-pass + +#![warn(unused_variables)] + +// A binding initialized by a diverging expression should not be reported as unused. + +fn diverge() -> ! { + loop {} +} + +fn test1() { + let res: Result = diverge(); + eprintln!("{:?}", res); +} + +fn foo() -> ! { + todo!() +} + +fn test2() { + let res: Result = foo(); + match res { + Ok(v) => todo!(), + Err(err) => todo!(), + } +} + +fn main() { + test1(); + test2(); +} diff --git a/tests/ui/reachable/never-assign-dead-code.rs b/tests/ui/reachable/never-assign-dead-code.rs index 05e5d5551336b..826aa76a8ef86 100644 --- a/tests/ui/reachable/never-assign-dead-code.rs +++ b/tests/ui/reachable/never-assign-dead-code.rs @@ -7,7 +7,7 @@ #![warn(unused)] fn main() { - let x: ! = panic!("aah"); //~ WARN unused + let x: ! = panic!("aah"); drop(x); //~ WARN unreachable //~^ WARN unreachable } diff --git a/tests/ui/reachable/never-assign-dead-code.stderr b/tests/ui/reachable/never-assign-dead-code.stderr index 5660bde5c279b..80352b565e6c7 100644 --- a/tests/ui/reachable/never-assign-dead-code.stderr +++ b/tests/ui/reachable/never-assign-dead-code.stderr @@ -21,13 +21,5 @@ LL | drop(x); | | | unreachable call -warning: unused variable: `x` - --> $DIR/never-assign-dead-code.rs:10:9 - | -LL | let x: ! = panic!("aah"); - | ^ help: if this is intentional, prefix it with an underscore: `_x` - | - = note: `#[warn(unused_variables)]` implied by `#[warn(unused)]` - -warning: 3 warnings emitted +warning: 2 warnings emitted From 8a2d4dbae70d6512ab722d3ba8351a1483061ce7 Mon Sep 17 00:00:00 2001 From: Yukang Date: Fri, 10 Jul 2026 09:43:27 +0800 Subject: [PATCH 2/2] Add checks for unused variables in diverging expressions --- compiler/rustc_mir_transform/src/liveness.rs | 54 +++++++++++++++++-- library/core/src/keyword_docs.rs | 1 + .../unused-never-binding-issue-158783.rs | 8 +++ .../unused-never-binding-issue-158783.stderr | 14 +++++ 4 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 tests/ui/lint/unused/unused-never-binding-issue-158783.stderr diff --git a/compiler/rustc_mir_transform/src/liveness.rs b/compiler/rustc_mir_transform/src/liveness.rs index 59bb59065cef8..0fccb4b194df7 100644 --- a/compiler/rustc_mir_transform/src/liveness.rs +++ b/compiler/rustc_mir_transform/src/liveness.rs @@ -906,6 +906,49 @@ impl<'a, 'tcx> AssignmentResult<'a, 'tcx> { false } + /// Check for source-level uses that may have been removed from reachable MIR. + fn is_local_used_in_source(&self, name: Symbol, def_span: Span) -> bool { + use rustc_hir as hir; + use rustc_hir::def::Res; + use rustc_hir::intravisit::{self, Visitor}; + + let Some(body_def_id) = self.body.source.def_id().as_local() else { return false }; + let Some(hir_body) = self.tcx.hir_maybe_body_owned_by(body_def_id) else { return false }; + let typeck_results = self.tcx.typeck(body_def_id); + + struct LocalUseVisitor<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + typeck_results: &'a ty::TypeckResults<'tcx>, + name: Symbol, + def_span: Span, + found: bool, + } + + impl<'a, 'tcx> Visitor<'tcx> for LocalUseVisitor<'a, 'tcx> { + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { + if self.found { + return; + } + + if let hir::ExprKind::Path(qpath) = &expr.kind + && let Res::Local(hir_id) = self.typeck_results.qpath_res(qpath, expr.hir_id) + && self.tcx.hir_name(hir_id) == self.name + && self.tcx.hir_span(hir_id) == self.def_span + { + self.found = true; + return; + } + + intravisit::walk_expr(self, expr); + } + } + + let mut visitor = + LocalUseVisitor { tcx: self.tcx, typeck_results, name, def_span, found: false }; + visitor.visit_body(hir_body); + visitor.found + } + /// Report fully unused locals, and forget the corresponding assignments. fn report_fully_unused(&mut self) { let tcx = self.tcx; @@ -995,14 +1038,17 @@ impl<'a, 'tcx> AssignmentResult<'a, 'tcx> { } }; + let is_used_after_uninitialized = self.body.local_kind(local) == LocalKind::Temp + && matches!(binding.opt_match_place, Some((None, _))) + && self.is_local_used_in_source(name, def_span); + let statements = &mut self.assignments[index]; if statements.is_empty() { - if self.body.local_kind(local) == LocalKind::Temp - && matches!(binding.opt_match_place, Some((None, _))) - { + if is_used_after_uninitialized { // A local from `let PAT = ...` normally has an assignment recorded for the // value it initializes. If no assignment was recorded in reachable MIR, the - // initializer did not complete, so no initialized value exists to be unused. + // initializer did not complete. If the local still has a source-level use, + // that use was made unreachable by the diverging initializer. continue; } diff --git a/library/core/src/keyword_docs.rs b/library/core/src/keyword_docs.rs index f4779d9a1ab1e..596765be5e2dd 100644 --- a/library/core/src/keyword_docs.rs +++ b/library/core/src/keyword_docs.rs @@ -2588,6 +2588,7 @@ mod while_keyword {} /// /// For example, the following Rust function will return `5`, causing `x` to take the [`!` type][never type]: /// ```rust +/// #[expect(unused_variables)] /// fn example() -> i32 { /// let x = { /// return 5; diff --git a/tests/ui/lint/unused/unused-never-binding-issue-158783.rs b/tests/ui/lint/unused/unused-never-binding-issue-158783.rs index dbd3e0cdbe30c..a75363be4b28b 100644 --- a/tests/ui/lint/unused/unused-never-binding-issue-158783.rs +++ b/tests/ui/lint/unused/unused-never-binding-issue-158783.rs @@ -25,7 +25,15 @@ fn test2() { } } +fn test3() -> i32 { + let x = { + //~^ WARN unused variable: `x` + return 5; + }; +} + fn main() { test1(); test2(); + test3(); } diff --git a/tests/ui/lint/unused/unused-never-binding-issue-158783.stderr b/tests/ui/lint/unused/unused-never-binding-issue-158783.stderr new file mode 100644 index 0000000000000..14a54cbdccc21 --- /dev/null +++ b/tests/ui/lint/unused/unused-never-binding-issue-158783.stderr @@ -0,0 +1,14 @@ +warning: unused variable: `x` + --> $DIR/unused-never-binding-issue-158783.rs:29:9 + | +LL | let x = { + | ^ help: if this is intentional, prefix it with an underscore: `_x` + | +note: the lint level is defined here + --> $DIR/unused-never-binding-issue-158783.rs:3:9 + | +LL | #![warn(unused_variables)] + | ^^^^^^^^^^^^^^^^ + +warning: 1 warning emitted +