Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions compiler/rustc_mir_transform/src/liveness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,58 @@ impl<'a, 'tcx> AssignmentResult<'a, 'tcx> {
false
}

/// Check for source-level uses that may have been removed from reachable MIR.
Comment thread
chenyukang marked this conversation as resolved.
/// For example:
/// ```rust
/// fn example() {
/// let x = todo!();
/// eprintln!("{x}");
/// }
/// ```
/// The use of x is unreachable, but we'll still want to know if x is used to correctly emit
/// unused variable warning.
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;
Expand Down Expand Up @@ -995,8 +1047,22 @@ impl<'a, 'tcx> AssignmentResult<'a, 'tcx> {
}
};

// the is_local_used_in_source is sufficient to check if the local is used in the source code,
// but we keep the local_kind check for a cheap filter to avoid heavy check
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);
Comment thread
chenyukang marked this conversation as resolved.

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;
}
Expand Down
39 changes: 39 additions & 0 deletions tests/ui/lint/unused/unused-never-binding-issue-158783.rs
Original file line number Diff line number Diff line change
@@ -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<u32, u32> = diverge();
eprintln!("{:?}", res);
}

fn foo() -> ! {
todo!()
}

fn test2() {
let res: Result<u32, u32> = 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();
}
14 changes: 14 additions & 0 deletions tests/ui/lint/unused/unused-never-binding-issue-158783.stderr
Original file line number Diff line number Diff line change
@@ -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

2 changes: 1 addition & 1 deletion tests/ui/reachable/never-assign-dead-code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#![warn(unused)]

fn main() {
let x: ! = panic!("aah"); //~ WARN unused
let x: ! = panic!("aah");
drop(x); //~ WARN unreachable
//~^ WARN unreachable
}
10 changes: 1 addition & 9 deletions tests/ui/reachable/never-assign-dead-code.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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

Loading