diff --git a/compiler/rustc_mir_transform/src/liveness.rs b/compiler/rustc_mir_transform/src/liveness.rs index af1bad9f1baa1..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,8 +1038,20 @@ 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 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. If the local still has a source-level use, + // that use was made unreachable by the diverging initializer. + continue; + } + if !self.is_local_in_reachable_code(local) { continue; } 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..a75363be4b28b --- /dev/null +++ b/tests/ui/lint/unused/unused-never-binding-issue-158783.rs @@ -0,0 +1,39 @@ +//@ 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 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 + 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