From f999471ebf35ad09750005f037c48f5bdad98123 Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Sat, 10 Aug 2024 19:46:48 +0000 Subject: [PATCH 1/7] Report uninhabited call return types on MIR. --- compiler/rustc_mir_build/messages.ftl | 5 ++ .../rustc_mir_build/src/builder/expr/into.rs | 13 +--- compiler/rustc_mir_build/src/builder/mod.rs | 76 +++++++++++++++++++ compiler/rustc_mir_build/src/errors.rs | 12 +++ compiler/rustc_passes/messages.ftl | 5 -- compiler/rustc_passes/src/errors.rs | 12 --- compiler/rustc_passes/src/liveness.rs | 49 +----------- tests/ui/enum-discriminant/issue-46519.rs | 1 + tests/ui/enum-discriminant/issue-46519.stderr | 18 +++++ .../intrinsics/panic-uninitialized-zeroed.rs | 2 +- tests/ui/lint/dead-code/issue-85071-2.rs | 2 +- tests/ui/lint/dead-code/issue-85071-2.stderr | 33 ++++---- tests/ui/lint/dead-code/issue-85071.rs | 2 +- tests/ui/lint/dead-code/issue-85071.stderr | 33 ++++---- 14 files changed, 150 insertions(+), 113 deletions(-) create mode 100644 tests/ui/enum-discriminant/issue-46519.stderr diff --git a/compiler/rustc_mir_build/messages.ftl b/compiler/rustc_mir_build/messages.ftl index fae159103e70d..ff6fef52c10e2 100644 --- a/compiler/rustc_mir_build/messages.ftl +++ b/compiler/rustc_mir_build/messages.ftl @@ -346,6 +346,11 @@ mir_build_union_field_requires_unsafe_unsafe_op_in_unsafe_fn_allowed = mir_build_union_pattern = cannot use unions in constant patterns .label = can't use a `union` here +mir_build_unreachable_due_to_uninhabited = unreachable {$descr} + .label = unreachable {$descr} + .label_orig = any code following this expression is unreachable + .note = this expression has type `{$ty}`, which is uninhabited + mir_build_unreachable_making_this_unreachable = collectively making this unreachable mir_build_unreachable_making_this_unreachable_n_more = ...and {$covered_by_many_n_more_count} other patterns collectively make this unreachable diff --git a/compiler/rustc_mir_build/src/builder/expr/into.rs b/compiler/rustc_mir_build/src/builder/expr/into.rs index 2074fbce0aef0..b04e56a0a2dd8 100644 --- a/compiler/rustc_mir_build/src/builder/expr/into.rs +++ b/compiler/rustc_mir_build/src/builder/expr/into.rs @@ -269,18 +269,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { args, unwind: UnwindAction::Continue, destination, - // The presence or absence of a return edge affects control-flow sensitive - // MIR checks and ultimately whether code is accepted or not. We can only - // omit the return edge if a return type is visibly uninhabited to a module - // that makes the call. - target: expr - .ty - .is_inhabited_from( - this.tcx, - this.parent_module, - this.infcx.typing_env(this.param_env), - ) - .then_some(success), + target: Some(success), call_source: if from_hir_call { CallSource::Normal } else { diff --git a/compiler/rustc_mir_build/src/builder/mod.rs b/compiler/rustc_mir_build/src/builder/mod.rs index 3d5f6f4cf451e..d14b8f505b3e6 100644 --- a/compiler/rustc_mir_build/src/builder/mod.rs +++ b/compiler/rustc_mir_build/src/builder/mod.rs @@ -23,10 +23,12 @@ use rustc_middle::mir::*; use rustc_middle::thir::{self, ExprId, LintLevel, LocalVarId, Param, ParamId, PatKind, Thir}; use rustc_middle::ty::{self, ScalarInt, Ty, TyCtxt, TypeVisitableExt, TypingMode}; use rustc_middle::{bug, span_bug}; +use rustc_session::lint; use rustc_span::{Span, Symbol, sym}; use crate::builder::expr::as_place::PlaceBuilder; use crate::builder::scope::DropKind; +use crate::errors; pub(crate) fn closure_saved_names_of_captured_variables<'tcx>( tcx: TyCtxt<'tcx>, @@ -534,6 +536,7 @@ fn construct_fn<'tcx>( return_block.unit() }); + builder.lint_and_remove_uninhabited(); let mut body = builder.finish(); body.spread_arg = if abi == ExternAbi::RustCall { @@ -591,6 +594,7 @@ fn construct_const<'a, 'tcx>( builder.build_drop_trees(); + builder.lint_and_remove_uninhabited(); builder.finish() } @@ -789,6 +793,78 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { builder } + fn lint_and_remove_uninhabited(&mut self) { + let mut lints = vec![]; + + for bbdata in self.cfg.basic_blocks.iter_mut() { + let term = bbdata.terminator_mut(); + let TerminatorKind::Call { ref mut target, destination, .. } = term.kind else { + continue; + }; + let Some(target_bb) = *target else { continue }; + + let ty = destination.ty(&self.local_decls, self.tcx).ty; + let ty_is_inhabited = ty.is_inhabited_from( + self.tcx, + self.parent_module, + self.infcx.typing_env(self.param_env), + ); + + if !ty_is_inhabited { + // Unreachable code warnings are already emitted during type checking. + // However, during type checking, full type information is being + // calculated but not yet available, so the check for diverging + // expressions due to uninhabited result types is pretty crude and + // only checks whether ty.is_never(). Here, we have full type + // information available and can issue warnings for less obviously + // uninhabited types (e.g. empty enums). The check above is used so + // that we do not emit the same warning twice if the uninhabited type + // is indeed `!`. + if !ty.is_never() { + lints.push((target_bb, ty, term.source_info.span)); + } + + // The presence or absence of a return edge affects control-flow sensitive + // MIR checks and ultimately whether code is accepted or not. We can only + // omit the return edge if a return type is visibly uninhabited to a module + // that makes the call. + *target = None; + } + } + + for (target_bb, orig_ty, orig_span) in lints { + if orig_span.in_external_macro(self.tcx.sess.source_map()) { + continue; + } + let target_bb = &self.cfg.basic_blocks[target_bb]; + let (target_loc, descr) = target_bb + .statements + .iter() + .find_map(|stmt| match stmt.kind { + StatementKind::StorageLive(_) | StatementKind::StorageDead(_) => None, + StatementKind::FakeRead(..) => Some((stmt.source_info, "definition")), + _ => Some((stmt.source_info, "expression")), + }) + .unwrap_or_else(|| (target_bb.terminator().source_info, "expression")); + let lint_root = self.source_scopes[target_loc.scope] + .local_data + .as_ref() + .unwrap_crate_local() + .lint_root; + self.tcx.emit_node_span_lint( + lint::builtin::UNREACHABLE_CODE, + lint_root, + target_loc.span, + errors::UnreachableDueToUninhabited { + expr: target_loc.span, + orig: orig_span, + descr, + ty: orig_ty, + }, + ); + } + } + fn finish(self) -> Body<'tcx> { let mut body = Body::new( MirSource::item(self.def_id.to_def_id()), diff --git a/compiler/rustc_mir_build/src/errors.rs b/compiler/rustc_mir_build/src/errors.rs index ae09db5023527..550d22e056b32 100644 --- a/compiler/rustc_mir_build/src/errors.rs +++ b/compiler/rustc_mir_build/src/errors.rs @@ -721,6 +721,18 @@ pub(crate) struct WantedConstant { pub(crate) const_path: String, } +#[derive(LintDiagnostic)] +#[diag(mir_build_unreachable_due_to_uninhabited)] +pub(crate) struct UnreachableDueToUninhabited<'desc, 'tcx> { + pub descr: &'desc str, + #[label] + pub expr: Span, + #[label(mir_build_label_orig)] + #[note] + pub orig: Span, + pub ty: Ty<'tcx>, +} + #[derive(Diagnostic)] #[diag(mir_build_const_pattern_depends_on_generic_parameter, code = E0158)] pub(crate) struct ConstPatternDependsOnGenericParameter { diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index a4ef065ea2c83..7eb4d9c358612 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -726,11 +726,6 @@ passes_unnecessary_partial_stable_feature = the feature `{$feature}` has been pa passes_unnecessary_stable_feature = the feature `{$feature}` has been stable since {$since} and no longer requires an attribute to enable -passes_unreachable_due_to_uninhabited = unreachable {$descr} - .label = unreachable {$descr} - .label_orig = any code following this expression is unreachable - .note = this expression has type `{$ty}`, which is uninhabited - passes_unrecognized_argument = unrecognized argument diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 74ce92624bd49..57e92a6dbdb2e 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -1539,18 +1539,6 @@ pub(crate) struct ProcMacroBadSig { pub kind: ProcMacroKind, } -#[derive(LintDiagnostic)] -#[diag(passes_unreachable_due_to_uninhabited)] -pub(crate) struct UnreachableDueToUninhabited<'desc, 'tcx> { - pub descr: &'desc str, - #[label] - pub expr: Span, - #[label(passes_label_orig)] - #[note] - pub orig: Span, - pub ty: Ty<'tcx>, -} - #[derive(LintDiagnostic)] #[diag(passes_unused_var_maybe_capture_ref)] #[help] diff --git a/compiler/rustc_passes/src/liveness.rs b/compiler/rustc_passes/src/liveness.rs index 763d9fda80494..c8bc3338a29ea 100644 --- a/compiler/rustc_passes/src/liveness.rs +++ b/compiler/rustc_passes/src/liveness.rs @@ -94,7 +94,7 @@ use rustc_hir::{Expr, HirId, HirIdMap, HirIdSet}; use rustc_index::IndexVec; use rustc_middle::query::Providers; use rustc_middle::span_bug; -use rustc_middle::ty::{self, RootVariableMinCaptureList, Ty, TyCtxt}; +use rustc_middle::ty::{self, RootVariableMinCaptureList, TyCtxt}; use rustc_session::lint; use rustc_span::{BytePos, Span, Symbol, sym}; use tracing::{debug, instrument}; @@ -1314,52 +1314,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> { fn check_is_ty_uninhabited(&mut self, expr: &Expr<'_>, succ: LiveNode) -> LiveNode { let ty = self.typeck_results.expr_ty(expr); let m = self.ir.tcx.parent_module(expr.hir_id).to_def_id(); - if ty.is_inhabited_from(self.ir.tcx, m, self.typing_env) { - return succ; - } - match self.ir.lnks[succ] { - LiveNodeKind::ExprNode(succ_span, succ_id) => { - self.warn_about_unreachable(expr.span, ty, succ_span, succ_id, "expression"); - } - LiveNodeKind::VarDefNode(succ_span, succ_id) => { - self.warn_about_unreachable(expr.span, ty, succ_span, succ_id, "definition"); - } - _ => {} - }; - self.exit_ln - } - - fn warn_about_unreachable<'desc>( - &mut self, - orig_span: Span, - orig_ty: Ty<'tcx>, - expr_span: Span, - expr_id: HirId, - descr: &'desc str, - ) { - if !orig_ty.is_never() { - // Unreachable code warnings are already emitted during type checking. - // However, during type checking, full type information is being - // calculated but not yet available, so the check for diverging - // expressions due to uninhabited result types is pretty crude and - // only checks whether ty.is_never(). Here, we have full type - // information available and can issue warnings for less obviously - // uninhabited types (e.g. empty enums). The check above is used so - // that we do not emit the same warning twice if the uninhabited type - // is indeed `!`. - - self.ir.tcx.emit_node_span_lint( - lint::builtin::UNREACHABLE_CODE, - expr_id, - expr_span, - errors::UnreachableDueToUninhabited { - expr: expr_span, - orig: orig_span, - descr, - ty: orig_ty, - }, - ); - } + if ty.is_inhabited_from(self.ir.tcx, m, self.typing_env) { succ } else { self.exit_ln } } } diff --git a/tests/ui/enum-discriminant/issue-46519.rs b/tests/ui/enum-discriminant/issue-46519.rs index e5f0138c95cee..bad568ca6c5a7 100644 --- a/tests/ui/enum-discriminant/issue-46519.rs +++ b/tests/ui/enum-discriminant/issue-46519.rs @@ -7,6 +7,7 @@ #[should_panic(expected = "creating inhabited type")] fn test() { FontLanguageOverride::system_font(SystemFont::new()); + //~^ WARNING unreachable expression } pub enum FontLanguageOverride { diff --git a/tests/ui/enum-discriminant/issue-46519.stderr b/tests/ui/enum-discriminant/issue-46519.stderr new file mode 100644 index 0000000000000..cfb43e7c18310 --- /dev/null +++ b/tests/ui/enum-discriminant/issue-46519.stderr @@ -0,0 +1,18 @@ +warning: unreachable expression + --> $DIR/issue-46519.rs:9:5 + | +LL | FontLanguageOverride::system_font(SystemFont::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-----------------^ + | | | + | | any code following this expression is unreachable + | unreachable expression + | +note: this expression has type `SystemFont`, which is uninhabited + --> $DIR/issue-46519.rs:9:39 + | +LL | FontLanguageOverride::system_font(SystemFont::new()); + | ^^^^^^^^^^^^^^^^^ + = note: `#[warn(unreachable_code)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/intrinsics/panic-uninitialized-zeroed.rs b/tests/ui/intrinsics/panic-uninitialized-zeroed.rs index 346a94c37dd86..31f40c990a70b 100644 --- a/tests/ui/intrinsics/panic-uninitialized-zeroed.rs +++ b/tests/ui/intrinsics/panic-uninitialized-zeroed.rs @@ -5,7 +5,7 @@ //@ [strict]compile-flags: -Zstrict-init-checks //@ needs-subprocess -#![allow(deprecated, invalid_value)] +#![allow(deprecated, invalid_value, unreachable_code)] #![feature(never_type)] use std::{ diff --git a/tests/ui/lint/dead-code/issue-85071-2.rs b/tests/ui/lint/dead-code/issue-85071-2.rs index 5db8735899410..06bbcac737397 100644 --- a/tests/ui/lint/dead-code/issue-85071-2.rs +++ b/tests/ui/lint/dead-code/issue-85071-2.rs @@ -17,6 +17,6 @@ fn main() { let s = S; let x = s.f(); //~^ WARNING: unused variable: `x` + //~| WARNING: unreachable definition let _y = x; - //~^ WARNING: unreachable definition } diff --git a/tests/ui/lint/dead-code/issue-85071-2.stderr b/tests/ui/lint/dead-code/issue-85071-2.stderr index 5e963183d094b..ab88d72359695 100644 --- a/tests/ui/lint/dead-code/issue-85071-2.stderr +++ b/tests/ui/lint/dead-code/issue-85071-2.stderr @@ -1,34 +1,33 @@ -warning: unreachable definition - --> $DIR/issue-85071-2.rs:20:9 +warning: unused variable: `x` + --> $DIR/issue-85071-2.rs:18:9 | LL | let x = s.f(); - | ----- any code following this expression is unreachable -LL | -LL | let _y = x; - | ^^ unreachable definition - | -note: this expression has type `Foo`, which is uninhabited - --> $DIR/issue-85071-2.rs:18:13 + | ^ help: if this is intentional, prefix it with an underscore: `_x` | -LL | let x = s.f(); - | ^^^^^ note: the lint level is defined here - --> $DIR/issue-85071-2.rs:7:26 + --> $DIR/issue-85071-2.rs:7:9 | LL | #![warn(unused_variables,unreachable_code)] - | ^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^ -warning: unused variable: `x` +warning: unreachable definition --> $DIR/issue-85071-2.rs:18:9 | LL | let x = s.f(); - | ^ help: if this is intentional, prefix it with an underscore: `_x` + | ^ ----- any code following this expression is unreachable + | | + | unreachable definition + | +note: this expression has type `Foo`, which is uninhabited + --> $DIR/issue-85071-2.rs:18:13 | +LL | let x = s.f(); + | ^^^^^ note: the lint level is defined here - --> $DIR/issue-85071-2.rs:7:9 + --> $DIR/issue-85071-2.rs:7:26 | LL | #![warn(unused_variables,unreachable_code)] - | ^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^ warning: 2 warnings emitted diff --git a/tests/ui/lint/dead-code/issue-85071.rs b/tests/ui/lint/dead-code/issue-85071.rs index 84f2c9fc74eeb..6f177905b5968 100644 --- a/tests/ui/lint/dead-code/issue-85071.rs +++ b/tests/ui/lint/dead-code/issue-85071.rs @@ -14,6 +14,6 @@ fn f() -> Foo {todo!()} fn main() { let x = f(); //~^ WARNING: unused variable: `x` + //~| WARNING: unreachable definition let _ = x; - //~^ WARNING: unreachable expression } diff --git a/tests/ui/lint/dead-code/issue-85071.stderr b/tests/ui/lint/dead-code/issue-85071.stderr index 721fb8148d96b..c94923063903a 100644 --- a/tests/ui/lint/dead-code/issue-85071.stderr +++ b/tests/ui/lint/dead-code/issue-85071.stderr @@ -1,34 +1,33 @@ -warning: unreachable expression - --> $DIR/issue-85071.rs:17:13 +warning: unused variable: `x` + --> $DIR/issue-85071.rs:15:9 | LL | let x = f(); - | --- any code following this expression is unreachable -LL | -LL | let _ = x; - | ^ unreachable expression - | -note: this expression has type `Foo`, which is uninhabited - --> $DIR/issue-85071.rs:15:13 + | ^ help: if this is intentional, prefix it with an underscore: `_x` | -LL | let x = f(); - | ^^^ note: the lint level is defined here - --> $DIR/issue-85071.rs:9:26 + --> $DIR/issue-85071.rs:9:9 | LL | #![warn(unused_variables,unreachable_code)] - | ^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^ -warning: unused variable: `x` +warning: unreachable definition --> $DIR/issue-85071.rs:15:9 | LL | let x = f(); - | ^ help: if this is intentional, prefix it with an underscore: `_x` + | ^ --- any code following this expression is unreachable + | | + | unreachable definition + | +note: this expression has type `Foo`, which is uninhabited + --> $DIR/issue-85071.rs:15:13 | +LL | let x = f(); + | ^^^ note: the lint level is defined here - --> $DIR/issue-85071.rs:9:9 + --> $DIR/issue-85071.rs:9:26 | LL | #![warn(unused_variables,unreachable_code)] - | ^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^ warning: 2 warnings emitted From 927374b95cffbdc39ff827cfe2c7e5ae5f7e49ee Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Tue, 6 Sep 2022 18:58:52 +0200 Subject: [PATCH 2/7] Record for each MIR local where it has been introduced. --- .../src/diagnostics/move_errors.rs | 4 +--- .../src/diagnostics/mutability_errors.rs | 4 ++-- compiler/rustc_middle/src/mir/mod.rs | 18 ++++++------------ compiler/rustc_middle/src/thir.rs | 1 + compiler/rustc_mir_build/src/builder/block.rs | 2 ++ .../src/builder/matches/match_pair.rs | 3 ++- .../rustc_mir_build/src/builder/matches/mod.rs | 12 ++++++++++++ compiler/rustc_mir_build/src/builder/mod.rs | 1 + .../rustc_mir_build/src/thir/pattern/mod.rs | 11 ++++++++--- compiler/rustc_mir_build/src/thir/print.rs | 3 ++- tests/ui/thir-print/thir-tree-match.stdout | 1 + 11 files changed, 38 insertions(+), 22 deletions(-) diff --git a/compiler/rustc_borrowck/src/diagnostics/move_errors.rs b/compiler/rustc_borrowck/src/diagnostics/move_errors.rs index 0394a42ea9c77..6ff9ed9fa3848 100644 --- a/compiler/rustc_borrowck/src/diagnostics/move_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/move_errors.rs @@ -140,9 +140,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { // whether or not the right-hand side is a place expression if let LocalInfo::User(BindingForm::Var(VarBindingForm { opt_match_place: Some((opt_match_place, match_span)), - binding_mode: _, - opt_ty_info: _, - pat_span: _, + .. })) = *local_decl.local_info() { let stmt_source_info = self.body.source_info(location); diff --git a/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs b/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs index a5c9bad3ac2db..a04c8fa3ca35a 100644 --- a/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs @@ -307,8 +307,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { LocalInfo::User(BindingForm::Var(mir::VarBindingForm { binding_mode: BindingMode(ByRef::No, Mutability::Not), opt_ty_info: Some(sp), - opt_match_place: _, - pat_span: _, + .. })) => { if suggest { err.span_note(sp, "the binding is already a mutable borrow"); @@ -738,6 +737,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { opt_ty_info: _, opt_match_place: _, pat_span, + introductions: _, })) => pat_span, _ => local_decl.source_info.span, }; diff --git a/compiler/rustc_middle/src/mir/mod.rs b/compiler/rustc_middle/src/mir/mod.rs index adc100941a39a..43799f83bb6d3 100644 --- a/compiler/rustc_middle/src/mir/mod.rs +++ b/compiler/rustc_middle/src/mir/mod.rs @@ -926,6 +926,8 @@ pub struct VarBindingForm<'tcx> { pub opt_match_place: Option<(Option>, Span)>, /// The span of the pattern in which this variable was bound. pub pat_span: Span, + /// For each introduction place, record here the span and whether this was a shorthand pattern. + pub introductions: Vec<(Span, /* is_shorthand */ bool)>, } #[derive(Clone, Debug, TyEncodable, TyDecodable)] @@ -1131,12 +1133,8 @@ impl<'tcx> LocalDecl<'tcx> { matches!( self.local_info(), LocalInfo::User( - BindingForm::Var(VarBindingForm { - binding_mode: BindingMode(ByRef::No, _), - opt_ty_info: _, - opt_match_place: _, - pat_span: _, - }) | BindingForm::ImplicitSelf(ImplicitSelfKind::Imm), + BindingForm::Var(VarBindingForm { binding_mode: BindingMode(ByRef::No, _), .. }) + | BindingForm::ImplicitSelf(ImplicitSelfKind::Imm), ) ) } @@ -1148,12 +1146,8 @@ impl<'tcx> LocalDecl<'tcx> { matches!( self.local_info(), LocalInfo::User( - BindingForm::Var(VarBindingForm { - binding_mode: BindingMode(ByRef::No, _), - opt_ty_info: _, - opt_match_place: _, - pat_span: _, - }) | BindingForm::ImplicitSelf(_), + BindingForm::Var(VarBindingForm { binding_mode: BindingMode(ByRef::No, _), .. }) + | BindingForm::ImplicitSelf(_), ) ) } diff --git a/compiler/rustc_middle/src/thir.rs b/compiler/rustc_middle/src/thir.rs index b9a014d14c0d0..be2c4d67b7da9 100644 --- a/compiler/rustc_middle/src/thir.rs +++ b/compiler/rustc_middle/src/thir.rs @@ -777,6 +777,7 @@ pub enum PatKind<'tcx> { /// (The same binding can occur multiple times in different branches of /// an or-pattern, but only one of them will be primary.) is_primary: bool, + is_shorthand: bool, }, /// `Foo(...)` or `Foo{...}` or `Foo`, where `Foo` is a variant name from an ADT with diff --git a/compiler/rustc_mir_build/src/builder/block.rs b/compiler/rustc_mir_build/src/builder/block.rs index a71196f79d78d..7e4a3cf872b9f 100644 --- a/compiler/rustc_mir_build/src/builder/block.rs +++ b/compiler/rustc_mir_build/src/builder/block.rs @@ -204,6 +204,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { block, node, span, + false, OutsideGuard, ScheduleDrops::Yes, ); @@ -296,6 +297,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { block, node, span, + false, OutsideGuard, ScheduleDrops::Yes, ); diff --git a/compiler/rustc_mir_build/src/builder/matches/match_pair.rs b/compiler/rustc_mir_build/src/builder/matches/match_pair.rs index 3a7854a5e118d..816dc06de42a1 100644 --- a/compiler/rustc_mir_build/src/builder/matches/match_pair.rs +++ b/compiler/rustc_mir_build/src/builder/matches/match_pair.rs @@ -160,7 +160,7 @@ impl<'tcx> MatchPairTree<'tcx> { None } - PatKind::Binding { mode, var, ref subpattern, .. } => { + PatKind::Binding { mode, var, is_shorthand, ref subpattern, .. } => { // In order to please the borrow checker, when lowering a pattern // like `x @ subpat` we must establish any bindings in `subpat` // before establishing the binding for `x`. @@ -199,6 +199,7 @@ impl<'tcx> MatchPairTree<'tcx> { source, var_id: var, binding_mode: mode, + is_shorthand, }); } diff --git a/compiler/rustc_mir_build/src/builder/matches/mod.rs b/compiler/rustc_mir_build/src/builder/matches/mod.rs index 977d4f3e931b5..fe27972ac0119 100644 --- a/compiler/rustc_mir_build/src/builder/matches/mod.rs +++ b/compiler/rustc_mir_build/src/builder/matches/mod.rs @@ -600,6 +600,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { block, var, irrefutable_pat.span, + false, OutsideGuard, ScheduleDrops::Yes, ); @@ -629,6 +630,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { block, var, irrefutable_pat.span, + false, OutsideGuard, ScheduleDrops::Yes, ); @@ -821,6 +823,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { block: BasicBlock, var: LocalVarId, span: Span, + is_shorthand: bool, for_guard: ForGuard, schedule_drop: ScheduleDrops, ) -> Place<'tcx> { @@ -834,6 +837,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { { self.schedule_drop(span, region_scope, local_id, DropKind::Storage); } + let local_info = self.local_decls[local_id].local_info.as_mut().unwrap_crate_local(); + if let LocalInfo::User(BindingForm::Var(var_info)) = &mut **local_info { + var_info.introductions.push((span, is_shorthand)); + } Place::from(local_id) } @@ -1230,6 +1237,7 @@ struct Binding<'tcx> { source: Place<'tcx>, var_id: LocalVarId, binding_mode: BindingMode, + is_shorthand: bool, } /// Indicates that the type of `source` must be a subtype of the @@ -2693,6 +2701,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { block, binding.var_id, binding.span, + binding.is_shorthand, RefWithinGuard, schedule_drops, ); @@ -2709,6 +2718,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { block, binding.var_id, binding.span, + binding.is_shorthand, OutsideGuard, schedule_drops, ); @@ -2748,6 +2758,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { block, binding.var_id, binding.span, + binding.is_shorthand, OutsideGuard, schedule_drops, ), @@ -2801,6 +2812,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { opt_ty_info: None, opt_match_place, pat_span, + introductions: Vec::new(), }, )))), }; diff --git a/compiler/rustc_mir_build/src/builder/mod.rs b/compiler/rustc_mir_build/src/builder/mod.rs index d14b8f505b3e6..5bbad152c578b 100644 --- a/compiler/rustc_mir_build/src/builder/mod.rs +++ b/compiler/rustc_mir_build/src/builder/mod.rs @@ -1045,6 +1045,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { opt_ty_info: param.ty_span, opt_match_place: Some((None, span)), pat_span: span, + introductions: vec![(span, false)], })) }; self.var_indices.insert(var, LocalsForNode::One(local)); diff --git a/compiler/rustc_mir_build/src/thir/pattern/mod.rs b/compiler/rustc_mir_build/src/thir/pattern/mod.rs index fcd106d78e253..3328a9e545608 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/mod.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/mod.rs @@ -368,6 +368,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { ty: var_ty, subpattern: self.lower_opt_pattern(sub), is_primary: id == pat.hir_id, + is_shorthand: false, } } @@ -385,9 +386,13 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { let res = self.typeck_results.qpath_res(qpath, pat.hir_id); let subpatterns = fields .iter() - .map(|field| FieldPat { - field: self.typeck_results.field_index(field.hir_id), - pattern: *self.lower_pattern(field.pat), + .map(|field| { + let mut pattern = *self.lower_pattern(field.pat); + if let PatKind::Binding { ref mut is_shorthand, .. } = pattern.kind { + *is_shorthand = field.is_shorthand; + } + let field = self.typeck_results.field_index(field.hir_id); + FieldPat { field, pattern } }) .collect(); diff --git a/compiler/rustc_mir_build/src/thir/print.rs b/compiler/rustc_mir_build/src/thir/print.rs index db9547a481fb4..0b831b25f4145 100644 --- a/compiler/rustc_mir_build/src/thir/print.rs +++ b/compiler/rustc_mir_build/src/thir/print.rs @@ -677,13 +677,14 @@ impl<'a, 'tcx> ThirPrinter<'a, 'tcx> { self.print_pat(subpattern, depth_lvl + 3); print_indented!(self, "}", depth_lvl + 1); } - PatKind::Binding { name, mode, var, ty, subpattern, is_primary } => { + PatKind::Binding { name, mode, var, ty, subpattern, is_primary, is_shorthand } => { print_indented!(self, "Binding {", depth_lvl + 1); print_indented!(self, format!("name: {:?}", name), depth_lvl + 2); print_indented!(self, format!("mode: {:?}", mode), depth_lvl + 2); print_indented!(self, format!("var: {:?}", var), depth_lvl + 2); print_indented!(self, format!("ty: {:?}", ty), depth_lvl + 2); print_indented!(self, format!("is_primary: {:?}", is_primary), depth_lvl + 2); + print_indented!(self, format!("is_shorthand: {:?}", is_shorthand), depth_lvl + 2); if let Some(subpattern) = subpattern { print_indented!(self, "subpattern: Some( ", depth_lvl + 2); diff --git a/tests/ui/thir-print/thir-tree-match.stdout b/tests/ui/thir-print/thir-tree-match.stdout index 910582ae4d9e9..72b9e97ea466f 100644 --- a/tests/ui/thir-print/thir-tree-match.stdout +++ b/tests/ui/thir-print/thir-tree-match.stdout @@ -16,6 +16,7 @@ params: [ var: LocalVarId(HirId(DefId(0:16 ~ thir_tree_match[fcf8]::has_match).2)) ty: Foo is_primary: true + is_shorthand: false subpattern: None } } From 05c7afae708d4273f5b0eb0655c04698bef4c188 Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Wed, 11 Jan 2023 17:29:32 +0000 Subject: [PATCH 3/7] Swallow unused assignment warning. --- src/tools/rust-analyzer/crates/test-fixture/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs b/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs index 8eb48f8d93e7c..893a7778698d5 100644 --- a/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs +++ b/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs @@ -371,6 +371,8 @@ impl ChangeFixture { } } + let _ = file_id; + let root = match current_source_root_kind { SourceRootKind::Local => SourceRoot::new_local(mem::take(&mut file_set)), SourceRootKind::Library => SourceRoot::new_library(mem::take(&mut file_set)), From aedc71c60d6a3fb559a7b140b28c0ac5dad9d95d Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Tue, 10 Jun 2025 18:17:32 +0000 Subject: [PATCH 4/7] Correct binding scope when building MIR. --- compiler/rustc_mir_build/src/builder/matches/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/rustc_mir_build/src/builder/matches/mod.rs b/compiler/rustc_mir_build/src/builder/matches/mod.rs index fe27972ac0119..9f811d560b7a0 100644 --- a/compiler/rustc_mir_build/src/builder/matches/mod.rs +++ b/compiler/rustc_mir_build/src/builder/matches/mod.rs @@ -763,6 +763,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { pattern, &ProjectedUserTypesNode::None, &mut |this, name, mode, var, span, ty, user_tys| { + let saved_scope = this.source_scope; + this.set_correct_source_scope_for_arg(var.0, saved_scope, span); let vis_scope = *visibility_scope .get_or_insert_with(|| this.new_source_scope(scope_span, LintLevel::Inherited)); let source_info = SourceInfo { span, scope: this.source_scope }; @@ -780,6 +782,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { opt_match_place.map(|(x, y)| (x.cloned(), y)), pattern.span, ); + this.source_scope = saved_scope; }, ); if let Some(guard_expr) = guard { From 4262b6cee9524e49b262bd9b18c079eaaf83a2d0 Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Tue, 10 Jun 2025 18:17:09 +0000 Subject: [PATCH 5/7] Diagnose liveness on MIR. --- compiler/rustc_interface/src/passes.rs | 1 + compiler/rustc_middle/src/mir/mod.rs | 6 +- compiler/rustc_middle/src/mir/statement.rs | 2 +- compiler/rustc_middle/src/query/mod.rs | 6 +- compiler/rustc_middle/src/ty/closure.rs | 5 + .../src/builder/matches/mod.rs | 2 +- compiler/rustc_mir_build/src/builder/mod.rs | 5 - compiler/rustc_mir_transform/messages.ftl | 27 + compiler/rustc_mir_transform/src/errors.rs | 110 +- compiler/rustc_mir_transform/src/lib.rs | 4 + compiler/rustc_mir_transform/src/liveness.rs | 1225 +++++++++++ compiler/rustc_passes/messages.ftl | 35 - compiler/rustc_passes/src/errors.rs | 134 +- compiler/rustc_passes/src/lib.rs | 2 - compiler/rustc_passes/src/liveness.rs | 1800 ----------------- .../rustc_passes/src/liveness/rwu_table.rs | 146 -- .../clippy/tests/ui/needless_match.fixed | 2 +- src/tools/clippy/tests/ui/needless_match.rs | 2 +- .../clippy/tests/ui/useless_conversion.fixed | 2 +- .../clippy/tests/ui/useless_conversion.rs | 2 +- src/tools/tidy/src/ui_tests.rs | 2 +- tests/ui/asm/x86_64/goto.rs | 1 + tests/ui/asm/x86_64/goto.stderr | 15 +- .../async-closures/precise-captures.rs | 1 + .../borrowck/borrowck-assign-to-subfield.rs | 1 + .../diagnostics/liveness.rs | 48 +- .../diagnostics/liveness.stderr | 115 +- .../liveness_unintentional_copy.rs | 24 +- .../liveness_unintentional_copy.stderr | 85 +- ...ture-pattern-closure-within-closure.stderr | 16 +- .../defaults/trait_object_lt_defaults.rs | 2 +- tests/ui/drop/or-pattern-drop-order.rs | 2 +- tests/ui/dropck/dropck-empty-array.rs | 3 +- ...-type-meant-to-be-arg-of-mut-borrow.stderr | 90 +- tests/ui/issues/issue-11958.rs | 5 +- tests/ui/issues/issue-11958.stderr | 23 +- tests/ui/issues/issue-19367.rs | 3 + tests/ui/issues/issue-24353.rs | 1 + tests/ui/issues/issue-24353.stderr | 10 + tests/ui/lint/dead-code/issue-85071-2.stderr | 24 +- tests/ui/lint/dead-code/issue-85071.stderr | 24 +- .../ui/lint/future-incompat-json-test.stderr | 2 +- ...orthand-field-patterns-in-pattern-macro.rs | 1 + ...and-field-patterns-in-pattern-macro.stderr | 11 + .../expect_lint_from_macro.stderr | 20 +- ...force_warn_expected_lints_fulfilled.stderr | 12 +- .../lint/unused/issue-117284-arg-in-macro.rs | 4 +- .../unused/issue-117284-arg-in-macro.stderr | 7 +- ...0-unused-variable-in-struct-pattern.stderr | 68 +- .../issue-54180-unused-ref-field.stderr | 12 +- .../lint/unused/lint-unused-variables.stderr | 22 +- tests/ui/liveness/liveness-asm.stderr | 8 +- tests/ui/liveness/liveness-consts.rs | 6 +- tests/ui/liveness/liveness-consts.stderr | 42 +- tests/ui/liveness/liveness-dead.stderr | 12 +- tests/ui/liveness/liveness-unused.rs | 113 +- tests/ui/liveness/liveness-unused.stderr | 199 +- tests/ui/liveness/liveness-upvars.rs | 30 +- tests/ui/liveness/liveness-upvars.stderr | 147 +- ...ject-lifetime-default-default-to-static.rs | 2 +- ...object-lifetime-default-from-ref-struct.rs | 2 +- .../object-lifetime-default-from-rptr-box.rs | 2 +- .../object-lifetime-default-from-rptr-mut.rs | 2 +- ...bject-lifetime-default-from-rptr-struct.rs | 2 +- .../object-lifetime-default-from-rptr.rs | 2 +- .../object-lifetime-default-inferred.rs | 2 +- tests/ui/packed/packed-struct-drop-aligned.rs | 2 + tests/ui/parser/intersection-patterns-1.fixed | 1 + tests/ui/parser/intersection-patterns-1.rs | 1 + .../ui/parser/intersection-patterns-1.stderr | 4 +- .../pattern/bindings-after-at/bind-by-copy.rs | 5 + .../bindings-after-at/bind-by-copy.stderr | 39 + .../rfc-2005-default-binding-mode/general.rs | 1 + .../general.stderr | 11 + .../std-panic-locations.default.stderr | 11 + .../std-panic-locations.mir-opt.stderr | 11 + .../std-panic-locations.rs | 1 + .../param-attrs-cfg.stderr | 24 +- .../ui/suggestions/try-removing-the-field.rs | 4 +- .../suggestions/try-removing-the-field.stderr | 8 +- .../unused-closure-argument.stderr | 12 +- tests/ui/type/issue-100584.stderr | 4 +- tests/ui/type/type-ascription.rs | 5 +- .../unboxed-closures-counter-not-moved.rs | 5 +- .../unboxed-closures-counter-not-moved.stderr | 24 +- .../unboxed-closures-move-mutable.rs | 10 +- .../unboxed-closures-move-mutable.stderr | 42 +- 87 files changed, 2427 insertions(+), 2541 deletions(-) create mode 100644 compiler/rustc_mir_transform/src/liveness.rs delete mode 100644 compiler/rustc_passes/src/liveness.rs delete mode 100644 compiler/rustc_passes/src/liveness/rwu_table.rs create mode 100644 tests/ui/issues/issue-24353.stderr create mode 100644 tests/ui/lint/issue-49588-non-shorthand-field-patterns-in-pattern-macro.stderr create mode 100644 tests/ui/pattern/bindings-after-at/bind-by-copy.stderr create mode 100644 tests/ui/rfcs/rfc-2005-default-binding-mode/general.stderr create mode 100644 tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.default.stderr create mode 100644 tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.mir-opt.stderr diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index 99520a3fea304..f8b0a75d0683a 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -990,6 +990,7 @@ fn run_required_analyses(tcx: TyCtxt<'_>) { tcx.ensure_ok().mir_borrowck(def_id) } tcx.ensure_ok().has_ffi_unwind_calls(def_id); + tcx.ensure_ok().check_liveness(def_id); // If we need to codegen, ensure that we emit all errors from // `mir_drops_elaborated_and_const_checked` now, to avoid discovering diff --git a/compiler/rustc_middle/src/mir/mod.rs b/compiler/rustc_middle/src/mir/mod.rs index 43799f83bb6d3..9ead48b5b4710 100644 --- a/compiler/rustc_middle/src/mir/mod.rs +++ b/compiler/rustc_middle/src/mir/mod.rs @@ -937,7 +937,7 @@ pub enum BindingForm<'tcx> { /// Binding for a `self`/`&self`/`&mut self` binding where the type is implicit. ImplicitSelf(ImplicitSelfKind), /// Reference used in a guard expression to ensure immutability. - RefForGuard, + RefForGuard(Local), } mod binding_form_impl { @@ -952,7 +952,7 @@ mod binding_form_impl { match self { Var(binding) => binding.hash_stable(hcx, hasher), ImplicitSelf(kind) => kind.hash_stable(hcx, hasher), - RefForGuard => (), + RefForGuard(local) => local.hash_stable(hcx, hasher), } } } @@ -1163,7 +1163,7 @@ impl<'tcx> LocalDecl<'tcx> { /// expression that is used to access said variable for the guard of the /// match arm. pub fn is_ref_for_guard(&self) -> bool { - matches!(self.local_info(), LocalInfo::User(BindingForm::RefForGuard)) + matches!(self.local_info(), LocalInfo::User(BindingForm::RefForGuard(_))) } /// Returns `Some` if this is a reference to a static item that is used to diff --git a/compiler/rustc_middle/src/mir/statement.rs b/compiler/rustc_middle/src/mir/statement.rs index d98b40f0fcf13..9320c3bde4235 100644 --- a/compiler/rustc_middle/src/mir/statement.rs +++ b/compiler/rustc_middle/src/mir/statement.rs @@ -228,7 +228,7 @@ impl<'tcx> PlaceTy<'tcx> { impl ProjectionElem { /// Returns `true` if the target of this projection may refer to a different region of memory /// than the base. - fn is_indirect(&self) -> bool { + pub fn is_indirect(&self) -> bool { match self { Self::Deref => true, diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index d8c927e00db4a..5673735753813 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -1119,8 +1119,10 @@ rustc_queries! { desc { |tcx| "checking privacy in {}", describe_as_module(key.to_local_def_id(), tcx) } } - query check_liveness(key: LocalDefId) { - desc { |tcx| "checking liveness of variables in `{}`", tcx.def_path_str(key) } + query check_liveness(key: LocalDefId) -> &'tcx rustc_index::bit_set::DenseBitSet { + arena_cache + desc { |tcx| "checking liveness of variables in `{}`", tcx.def_path_str(key.to_def_id()) } + cache_on_disk_if(tcx) { tcx.is_typeck_child(key.to_def_id()) } } /// Return the live symbols in the crate for dead code check. diff --git a/compiler/rustc_middle/src/ty/closure.rs b/compiler/rustc_middle/src/ty/closure.rs index df67bb505a689..640f4dbccc7c7 100644 --- a/compiler/rustc_middle/src/ty/closure.rs +++ b/compiler/rustc_middle/src/ty/closure.rs @@ -327,6 +327,11 @@ pub fn place_to_string_for_capture<'tcx>(tcx: TyCtxt<'tcx>, place: &HirPlace<'tc ) } }, + HirProjectionKind::UnwrapUnsafeBinder => { + curr_string = format!("unwrap_binder!({curr_string})"); + } + // Just change the type to the hidden type, so we can actually project. + HirProjectionKind::OpaqueCast => {} proj => bug!("{:?} unexpected because it isn't captured", proj), } } diff --git a/compiler/rustc_mir_build/src/builder/matches/mod.rs b/compiler/rustc_mir_build/src/builder/matches/mod.rs index 9f811d560b7a0..d8ccd4f458566 100644 --- a/compiler/rustc_mir_build/src/builder/matches/mod.rs +++ b/compiler/rustc_mir_build/src/builder/matches/mod.rs @@ -2838,7 +2838,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { user_ty: None, source_info, local_info: ClearCrossCrate::Set(Box::new(LocalInfo::User( - BindingForm::RefForGuard, + BindingForm::RefForGuard(for_arm_body), ))), }); if self.should_emit_debug_info_for_binding(name, var_id) { diff --git a/compiler/rustc_mir_build/src/builder/mod.rs b/compiler/rustc_mir_build/src/builder/mod.rs index 5bbad152c578b..1e1263c19abc5 100644 --- a/compiler/rustc_mir_build/src/builder/mod.rs +++ b/compiler/rustc_mir_build/src/builder/mod.rs @@ -68,11 +68,6 @@ pub fn build_mir<'tcx>(tcx: TyCtxt<'tcx>, def: LocalDefId) -> Body<'tcx> { } }; - // Checking liveness after building the THIR ensures there were no typeck errors. - // - // maybe move the check to a MIR pass? - tcx.ensure_ok().check_liveness(def); - // Don't steal here, instead steal in unsafeck. This is so that // pattern inline constants can be evaluated as part of building the // THIR of the parent function without a cycle. diff --git a/compiler/rustc_mir_transform/messages.ftl b/compiler/rustc_mir_transform/messages.ftl index ae3062f07de9c..007e8115fdbe0 100644 --- a/compiler/rustc_mir_transform/messages.ftl +++ b/compiler/rustc_mir_transform/messages.ftl @@ -36,12 +36,16 @@ mir_transform_force_inline_attr = mir_transform_force_inline_justification = `{$callee}` is required to be inlined to: {$sym} +mir_transform_maybe_string_interpolation = you might have meant to use string interpolation in this string literal + mir_transform_must_not_suspend = {$pre}`{$def_path}`{$post} held across a suspend point, but should not be .label = the value is held across this suspend point .note = {$reason} .help = consider using a block (`{"{ ... }"}`) to shrink the value's scope, ending before the suspend point mir_transform_operation_will_panic = this operation will panic at runtime +mir_transform_string_interpolation_only_works = string interpolation only works in `format!` invocations + mir_transform_tail_expr_drop_order = relative drop order changing in Rust 2024 .temporaries = in Rust 2024, this temporary value will be dropped first .observers = in Rust 2024, this local variable or temporary value will be dropped second @@ -79,3 +83,26 @@ mir_transform_unconditional_recursion = function cannot return without recursing mir_transform_unconditional_recursion_call_site_label = recursive call site mir_transform_unknown_pass_name = MIR pass `{$name}` is unknown and will be ignored + +mir_transform_unused_assign = value assigned to `{$name}` is never read + .help = maybe it is overwritten before being read? + +mir_transform_unused_assign_passed = value passed to `{$name}` is never read + .help = maybe it is overwritten before being read? + +mir_transform_unused_assign_suggestion = + you might have meant to mutate the pointed at value being passed in, instead of changing the reference in the local binding + +mir_transform_unused_capture_maybe_capture_ref = value captured by `{$name}` is never read + .help = did you mean to capture by reference instead? + +mir_transform_unused_var_assigned_only = variable `{$name}` is assigned to, but never used + .note = consider using `_{$name}` instead + +mir_transform_unused_var_underscore = if this is intentional, prefix it with an underscore + +mir_transform_unused_variable = unused variable: `{$name}` + +mir_transform_unused_variable_args_in_macro = `{$name}` is captured in macro and introduced a unused variable + +mir_transform_unused_variable_try_ignore = try ignoring the field diff --git a/compiler/rustc_mir_transform/src/errors.rs b/compiler/rustc_mir_transform/src/errors.rs index cffa0183fa7a5..4303bbc75396e 100644 --- a/compiler/rustc_mir_transform/src/errors.rs +++ b/compiler/rustc_mir_transform/src/errors.rs @@ -1,5 +1,5 @@ use rustc_errors::codes::*; -use rustc_errors::{Diag, LintDiagnostic}; +use rustc_errors::{Applicability, Diag, EmissionGuarantee, LintDiagnostic, Subdiagnostic}; use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic}; use rustc_middle::mir::AssertKind; use rustc_middle::ty::TyCtxt; @@ -125,6 +125,114 @@ pub(crate) struct MCDCExceedsTestVectorLimit { pub(crate) max_num_test_vectors: usize, } +#[derive(LintDiagnostic)] +#[diag(mir_transform_unused_capture_maybe_capture_ref)] +#[help] +pub(crate) struct UnusedCaptureMaybeCaptureRef { + pub name: String, +} + +#[derive(LintDiagnostic)] +#[diag(mir_transform_unused_var_assigned_only)] +#[note] +pub(crate) struct UnusedVarAssignedOnly { + pub name: String, +} + +#[derive(LintDiagnostic)] +#[diag(mir_transform_unused_assign)] +pub(crate) struct UnusedAssign { + pub name: String, + #[subdiagnostic] + pub suggestion: Option, + #[help] + pub help: bool, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion(mir_transform_unused_assign_suggestion, applicability = "maybe-incorrect")] +pub(crate) struct UnusedAssignSuggestion { + pub pre: &'static str, + #[suggestion_part(code = "{pre}mut ")] + pub ty_span: Option, + #[suggestion_part(code = "")] + pub ty_ref_span: Span, + #[suggestion_part(code = "*")] + pub pre_lhs_span: Span, + #[suggestion_part(code = "")] + pub rhs_borrow_span: Span, +} + +#[derive(LintDiagnostic)] +#[diag(mir_transform_unused_assign_passed)] +#[help] +pub(crate) struct UnusedAssignPassed { + pub name: String, +} + +#[derive(LintDiagnostic)] +#[diag(mir_transform_unused_variable)] +pub(crate) struct UnusedVariable { + pub name: String, + #[subdiagnostic] + pub string_interp: Vec, + #[subdiagnostic] + pub sugg: UnusedVariableSugg, +} + +#[derive(Subdiagnostic)] +pub(crate) enum UnusedVariableSugg { + #[multipart_suggestion( + mir_transform_unused_variable_try_ignore, + applicability = "machine-applicable" + )] + TryIgnore { + #[suggestion_part(code = "{name}: _")] + shorthands: Vec, + #[suggestion_part(code = "_")] + non_shorthands: Vec, + name: String, + }, + + #[multipart_suggestion( + mir_transform_unused_var_underscore, + applicability = "machine-applicable" + )] + TryPrefix { + #[suggestion_part(code = "_{name}")] + spans: Vec, + name: String, + }, + + #[help(mir_transform_unused_variable_args_in_macro)] + NoSugg { + #[primary_span] + span: Span, + name: String, + }, +} + +pub(crate) struct UnusedVariableStringInterp { + pub lit: Span, +} + +impl Subdiagnostic for UnusedVariableStringInterp { + fn add_to_diag(self, diag: &mut Diag<'_, G>) { + diag.span_label( + self.lit, + crate::fluent_generated::mir_transform_maybe_string_interpolation, + ); + diag.multipart_suggestion( + crate::fluent_generated::mir_transform_string_interpolation_only_works, + vec![ + (self.lit.shrink_to_lo(), String::from("format!(")), + (self.lit.shrink_to_hi(), String::from(")")), + ], + Applicability::MachineApplicable, + ); + } +} + pub(crate) struct MustNotSupend<'a, 'tcx> { pub tcx: TyCtxt<'tcx>, pub yield_sp: Span, diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs index 572ad585c8c87..3447b16b100c1 100644 --- a/compiler/rustc_mir_transform/src/lib.rs +++ b/compiler/rustc_mir_transform/src/lib.rs @@ -50,6 +50,7 @@ mod errors; mod ffi_unwind_calls; mod lint; mod lint_tail_expr_drop_order; +mod liveness; mod patch; mod shim; mod ssa; @@ -213,6 +214,7 @@ pub fn provide(providers: &mut Providers) { mir_for_ctfe, mir_coroutine_witnesses: coroutine::mir_coroutine_witnesses, optimized_mir, + check_liveness: liveness::check_liveness, is_mir_available, is_ctfe_mir_available: is_mir_available, mir_callgraph_reachable: inline::cycle::mir_callgraph_reachable, @@ -507,6 +509,8 @@ fn mir_drops_elaborated_and_const_checked(tcx: TyCtxt<'_>, def: LocalDefId) -> & } } + tcx.ensure_done().check_liveness(def); + let (body, _) = tcx.mir_promoted(def); let mut body = body.steal(); diff --git a/compiler/rustc_mir_transform/src/liveness.rs b/compiler/rustc_mir_transform/src/liveness.rs new file mode 100644 index 0000000000000..9384d6dc58e35 --- /dev/null +++ b/compiler/rustc_mir_transform/src/liveness.rs @@ -0,0 +1,1225 @@ +use rustc_abi::FieldIdx; +use rustc_data_structures::fx::{FxHashSet, FxIndexMap, IndexEntry}; +use rustc_hir::def::DefKind; +use rustc_hir::def_id::LocalDefId; +use rustc_index::IndexVec; +use rustc_index::bit_set::DenseBitSet; +use rustc_middle::bug; +use rustc_middle::mir::visit::{ + MutatingUseContext, NonMutatingUseContext, NonUseContext, PlaceContext, Visitor, +}; +use rustc_middle::mir::*; +use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_mir_dataflow::fmt::DebugWithContext; +use rustc_mir_dataflow::{Analysis, Backward, ResultsCursor}; +use rustc_session::lint; +use rustc_span::Span; +use rustc_span::symbol::sym; + +use crate::errors; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +enum AccessKind { + Param, + Assign, + Capture, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +enum CaptureKind { + Closure(ty::ClosureKind), + Coroutine, + CoroutineClosure, + None, +} + +struct AssignmentResult { + /// Set of locals that are live at least once. This is used to report fully unused locals. + ever_live: DenseBitSet, + /// Set of locals that have a non-trivial drop. This is used to skip reporting unused + /// assignment if it would be used by the `Drop` impl. + ever_dropped: DenseBitSet, + /// Set of assignments for each local. Here, assignment is understood in the AST sense. Any + /// MIR that may look like an assignment (Assign, DropAndReplace, Yield, Call) are considered. + /// + /// For each local, we return a map: for each source position, whether the statement is live + /// and which kind of access it performs. When we encounter multiple statements at the same + /// location, we only increase the liveness, in order to avoid false positives. + assignments: IndexVec>, +} + +#[tracing::instrument(level = "debug", skip(tcx), ret)] +pub fn check_liveness<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> DenseBitSet { + // Don't run on synthetic MIR, as that will ICE trying to access HIR. + if tcx.is_synthetic_mir(def_id) { + return DenseBitSet::new_empty(0); + } + + // Don't run unused pass for #[naked] + if tcx.has_attr(def_id.to_def_id(), sym::naked) { + return DenseBitSet::new_empty(0); + } + + // Don't run unused pass for intrinsics + if tcx.intrinsic(def_id.to_def_id()).is_some() { + return DenseBitSet::new_empty(0); + } + + // Don't run unused pass for #[derive] + let parent = tcx.parent(tcx.typeck_root_def_id(def_id.to_def_id())); + if let DefKind::Impl { of_trait: true } = tcx.def_kind(parent) + && tcx.has_attr(parent, sym::automatically_derived) + { + return DenseBitSet::new_empty(0); + } + + let mut body = &*tcx.mir_promoted(def_id).0.borrow(); + let mut body_mem; + + // Don't run if there are errors. + if body.tainted_by_errors.is_some() { + return DenseBitSet::new_empty(0); + } + + let mut checked_places = PlaceSet::default(); + checked_places.insert_locals(&body.local_decls); + + // The body is the one of a closure or generator, so we also want to analyse captures. + let (capture_kind, num_captures) = if tcx.is_closure_like(def_id.to_def_id()) { + let mut self_ty = body.local_decls[ty::CAPTURE_STRUCT_LOCAL].ty; + let mut self_is_ref = false; + if let ty::Ref(_, ty, _) = self_ty.kind() { + self_ty = *ty; + self_is_ref = true; + } + + let (capture_kind, args) = match self_ty.kind() { + ty::Closure(_, args) => { + (CaptureKind::Closure(args.as_closure().kind()), ty::UpvarArgs::Closure(args)) + } + &ty::Coroutine(_, args) => (CaptureKind::Coroutine, ty::UpvarArgs::Coroutine(args)), + &ty::CoroutineClosure(_, args) => { + (CaptureKind::CoroutineClosure, ty::UpvarArgs::CoroutineClosure(args)) + } + _ => bug!("expected closure or generator, found {:?}", self_ty), + }; + + let captures = tcx.closure_captures(def_id); + checked_places.insert_captures(tcx, self_is_ref, captures, args.upvar_tys()); + + // `FnMut` closures can modify captured values and carry those + // modified values with them in subsequent calls. To model this behaviour, + // we consider the `FnMut` closure as jumping to `bb0` upon return. + if let CaptureKind::Closure(ty::ClosureKind::FnMut) = capture_kind + && checked_places.captures.iter().any(|(_, by_ref)| !by_ref) + { + // FIXME: stop cloning the body. + body_mem = body.clone(); + for bbdata in body_mem.basic_blocks_mut() { + if let TerminatorKind::Return = bbdata.terminator().kind { + bbdata.terminator_mut().kind = TerminatorKind::Goto { target: START_BLOCK }; + } + } + body = &body_mem; + } + + (capture_kind, args.upvar_tys().len()) + } else { + (CaptureKind::None, 0) + }; + + // Get the remaining variables' names from debuginfo. + checked_places.record_debuginfo(&body.var_debug_info); + + let self_assignment = find_self_assignments(&checked_places, body); + + let mut live = + MaybeLivePlaces { tcx, capture_kind, checked_places: &checked_places, self_assignment } + .iterate_to_fixpoint(tcx, body, None) + .into_results_cursor(body); + + let AssignmentResult { mut ever_live, ever_dropped, mut assignments } = + find_dead_assignments(tcx, &checked_places, &mut live, body); + + // Match guards introduce a different local to freeze the guarded value as immutable. + // Having two locals, we need to make sure that we do not report an unused_variable + // when the guard local is used but not the arm local, or vice versa, like in this example. + // + // match 5 { + // x if x > 2 => {} + // ^ ^- This is `local` + // +------ This is `arm_local` + // _ => {} + // } + // + for (index, place) in checked_places.iter() { + let local = place.local; + if let &LocalInfo::User(BindingForm::RefForGuard(arm_local)) = + body.local_decls[local].local_info() + { + debug_assert!(place.projection.is_empty()); + + // Local to use in the arm. + let Some((arm_index, _proj)) = checked_places.get(arm_local.into()) else { continue }; + debug_assert_ne!(index, arm_index); + debug_assert_eq!(_proj, &[]); + + // Mark the arm local as used if the guard local is used. + if ever_live.contains(index) { + ever_live.insert(arm_index); + } + + // Some assignments are common to both locals in the source code. + // Sadly, we can only detect this using the `source_info`. + // Therefore, we loop over all the assignments we have for the guard local: + // - if they already appeared for the arm local, the assignment is live if one of the + // two versions is live; + // - if it does not appear for the arm local, it happened inside the guard, so we add + // it as-is. + let guard_assignments = std::mem::take(&mut assignments[index]); + let arm_assignments = &mut assignments[arm_index]; + for (source_info, (live, kind)) in guard_assignments { + match arm_assignments.entry(source_info) { + IndexEntry::Vacant(v) => { + v.insert((live, kind)); + } + IndexEntry::Occupied(mut o) => { + o.get_mut().0 |= live; + } + } + } + } + } + + // Report to caller the set of dead captures. + let mut dead_captures = DenseBitSet::new_empty(num_captures); + + // First, report fully unused locals. + for (index, place) in checked_places.iter() { + if ever_live.contains(index) { + continue; + } + + // This is a capture: let the enclosing function report the unused variable. + if is_capture(*place) { + debug_assert_eq!(place.local, ty::CAPTURE_STRUCT_LOCAL); + for p in place.projection { + if let PlaceElem::Field(f, _) = p { + dead_captures.insert(*f); + break; + } + } + continue; + } + + let Some((ref name, def_span)) = checked_places.names[index] else { continue }; + if name.is_empty() || name.starts_with('_') || name == "self" { + continue; + } + + let local = place.local; + let decl = &body.local_decls[local]; + + if decl.from_compiler_desugaring() { + continue; + } + + // Only report actual user-defined binding from now on. + let LocalInfo::User(BindingForm::Var(binding)) = decl.local_info() else { continue }; + let Some(hir_id) = decl.source_info.scope.lint_root(&body.source_scopes) else { continue }; + + let introductions = &binding.introductions; + + // #117284, when `ident_span` and `def_span` have different contexts + // we can't provide a good suggestion, instead we pointed out the spans from macro + let from_macro = def_span.from_expansion() + && introductions.iter().any(|(ident_span, _)| ident_span.eq_ctxt(def_span)); + + let statements = &mut assignments[index]; + if statements.is_empty() { + let sugg = if from_macro { + errors::UnusedVariableSugg::NoSugg { span: def_span, name: name.clone() } + } else { + errors::UnusedVariableSugg::TryPrefix { spans: vec![def_span], name: name.clone() } + }; + tcx.emit_node_span_lint( + lint::builtin::UNUSED_VARIABLES, + hir_id, + def_span, + errors::UnusedVariable { + name: name.clone(), + string_interp: maybe_suggest_literal_matching_name(body, name), + sugg, + }, + ); + continue; + } + + // Idiomatic rust assigns a value to a local upon definition. However, we do not want to + // warn twice, for the unused local and for the unused assignment. Therefore, we remove + // from the list of assignments the ones that happen at the definition site. + statements.retain(|source_info, _| { + source_info.span.find_ancestor_inside(binding.pat_span).is_none() + }); + + // Extra assignments that we recognize thanks to the initialization span. We need to + // take care of macro contexts here to be accurate. + if let Some((_, initializer_span)) = binding.opt_match_place { + statements.retain(|source_info, _| { + let within = source_info.span.find_ancestor_inside(initializer_span); + let outer_initializer_span = + initializer_span.find_ancestor_in_same_ctxt(source_info.span); + within.is_none() + && outer_initializer_span.map_or(true, |s| !s.contains(source_info.span)) + }); + } + + if !statements.is_empty() { + // We have a dead local with outstanding assignments and with non-trivial drop. + // This is probably a drop-guard, so we do not issue a warning there. + if ever_dropped.contains(index) { + continue; + } + + tcx.emit_node_span_lint( + lint::builtin::UNUSED_VARIABLES, + hir_id, + def_span, + errors::UnusedVarAssignedOnly { name: name.clone() }, + ); + continue; + } + + // We do not have outstanding assignments, suggest renaming the binding. + let spans = introductions.iter().map(|(span, _)| *span).collect::>(); + + let any_shorthand = introductions.iter().any(|(_, is_shorthand)| *is_shorthand); + + let sugg = if any_shorthand { + errors::UnusedVariableSugg::TryIgnore { + name: name.clone(), + shorthands: introductions + .iter() + .filter_map( + |&(span, is_shorthand)| { + if is_shorthand { Some(span) } else { None } + }, + ) + .collect(), + non_shorthands: introductions + .iter() + .filter_map( + |&(span, is_shorthand)| { + if !is_shorthand { Some(span) } else { None } + }, + ) + .collect(), + } + } else if from_macro { + errors::UnusedVariableSugg::NoSugg { span: def_span, name: name.clone() } + } else if !introductions.is_empty() { + errors::UnusedVariableSugg::TryPrefix { + name: name.clone(), + spans: introductions.iter().map(|&(span, _)| span).collect(), + } + } else { + errors::UnusedVariableSugg::TryPrefix { name: name.clone(), spans: vec![def_span] } + }; + + tcx.emit_node_span_lint( + lint::builtin::UNUSED_VARIABLES, + hir_id, + spans, + errors::UnusedVariable { + name: name.clone(), + string_interp: maybe_suggest_literal_matching_name(body, name), + sugg, + }, + ); + } + + // Second, report unused assignments that do not correspond to initialization. + // Initializations have been removed in the previous loop reporting unused variables. + for (index, statements) in assignments.into_iter_enumerated() { + if statements.is_empty() { + continue; + } + + let Some((ref name, decl_span)) = checked_places.names[index] else { continue }; + if name.is_empty() || name.starts_with('_') || name == "self" { + continue; + } + + // We have outstanding assignments and with non-trivial drop. + // This is probably a drop-guard, so we do not issue a warning there. + if ever_dropped.contains(index) { + continue; + } + + // We probed MIR in reverse order for dataflow. + // We revert the vector to give a consistent order to the user. + for (source_info, (live, kind)) in statements.into_iter().rev() { + if live { + continue; + } + + // Report the dead assignment. + let Some(hir_id) = source_info.scope.lint_root(&body.source_scopes) else { continue }; + + match kind { + AccessKind::Assign => { + let suggestion = annotate_mut_binding_to_immutable_binding( + tcx, + checked_places.places[index], + def_id, + source_info.span, + body, + ); + tcx.emit_node_span_lint( + lint::builtin::UNUSED_ASSIGNMENTS, + hir_id, + source_info.span, + errors::UnusedAssign { + name: name.clone(), + help: suggestion.is_none(), + suggestion, + }, + ) + } + AccessKind::Param => tcx.emit_node_span_lint( + lint::builtin::UNUSED_ASSIGNMENTS, + hir_id, + source_info.span, + errors::UnusedAssignPassed { name: name.clone() }, + ), + AccessKind::Capture => tcx.emit_node_span_lint( + lint::builtin::UNUSED_ASSIGNMENTS, + hir_id, + decl_span, + errors::UnusedCaptureMaybeCaptureRef { name: name.clone() }, + ), + } + } + } + + dead_captures +} + +/// Small helper to make semantics easier to read. +#[inline] +fn is_capture(place: PlaceRef<'_>) -> bool { + if !place.projection.is_empty() { + debug_assert_eq!(place.local, ty::CAPTURE_STRUCT_LOCAL); + true + } else { + false + } +} + +/// Give a diagnostic when any of the string constants look like a naked format string that would +/// interpolate our dead local. +fn maybe_suggest_literal_matching_name( + body: &Body<'_>, + name: &str, +) -> Vec { + struct LiteralFinder<'body, 'tcx> { + body: &'body Body<'tcx>, + name: String, + name_colon: String, + found: Vec, + } + + impl<'tcx> Visitor<'tcx> for LiteralFinder<'_, 'tcx> { + fn visit_const_operand(&mut self, constant: &ConstOperand<'tcx>, loc: Location) { + if let ty::Ref(_, ref_ty, _) = constant.ty().kind() + && ref_ty.kind() == &ty::Str + { + let rendered_constant = constant.const_.to_string(); + if rendered_constant.contains(&self.name) + || rendered_constant.contains(&self.name_colon) + { + let lit = self.body.source_info(loc).span; + self.found.push(errors::UnusedVariableStringInterp { lit }); + } + } + } + } + + let mut finder = LiteralFinder { + body, + name: format!("{{{name}}}"), + name_colon: format!("{{{name}:"), + found: vec![], + }; + finder.visit_body(body); + finder.found +} + +/// Detect the following case +/// +/// ```text +/// fn change_object(mut a: &Ty) { +/// let a = Ty::new(); +/// b = &a; +/// } +/// ``` +/// +/// where the user likely meant to modify the value behind there reference, use `a` as an out +/// parameter, instead of mutating the local binding. When encountering this we suggest: +/// +/// ```text +/// fn change_object(a: &'_ mut Ty) { +/// let a = Ty::new(); +/// *b = a; +/// } +/// ``` +fn annotate_mut_binding_to_immutable_binding<'tcx>( + tcx: TyCtxt<'tcx>, + place: PlaceRef<'tcx>, + body_def_id: LocalDefId, + assignment_span: Span, + body: &Body<'tcx>, +) -> Option { + use rustc_hir as hir; + use rustc_hir::intravisit::{self, Visitor}; + + // Verify we have a mutable argument... + let local = place.as_local()?; + let LocalKind::Arg = body.local_kind(local) else { return None }; + let Mutability::Mut = body.local_decls[local].mutability else { return None }; + + // ... with reference type... + let hir_param_index = + local.as_usize() - if tcx.is_closure_like(body_def_id.to_def_id()) { 2 } else { 1 }; + let fn_decl = tcx.hir_node_by_def_id(body_def_id).fn_decl()?; + let ty = fn_decl.inputs[hir_param_index]; + let hir::TyKind::Ref(lt, mut_ty) = ty.kind else { return None }; + + // ... as a binding pattern. + let hir_body = tcx.hir_maybe_body_owned_by(body_def_id)?; + let param = hir_body.params[hir_param_index]; + let hir::PatKind::Binding(hir::BindingMode::MUT, _hir_id, ident, _) = param.pat.kind else { + return None; + }; + + // Find the assignment to modify. + let mut finder = ExprFinder { assignment_span, lhs: None, rhs: None }; + finder.visit_body(hir_body); + let lhs = finder.lhs?; + let rhs = finder.rhs?; + + let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _mut, inner) = rhs.kind else { return None }; + + // Changes to the parameter's type. + let pre = if lt.ident.span.is_empty() { "" } else { " " }; + let ty_span = if mut_ty.mutbl.is_mut() { + // Leave `&'name mut Ty` and `&mut Ty` as they are (#136028). + None + } else { + // `&'name Ty` -> `&'name mut Ty` or `&Ty` -> `&mut Ty` + Some(mut_ty.ty.span.shrink_to_lo()) + }; + + return Some(errors::UnusedAssignSuggestion { + ty_span, + pre, + // Span of the `mut` before the binding. + ty_ref_span: param.pat.span.until(ident.span), + // Where to add a `*`. + pre_lhs_span: lhs.span.shrink_to_lo(), + // Where to remove the borrow. + rhs_borrow_span: rhs.span.until(inner.span), + }); + + #[derive(Debug)] + struct ExprFinder<'hir> { + assignment_span: Span, + lhs: Option<&'hir hir::Expr<'hir>>, + rhs: Option<&'hir hir::Expr<'hir>>, + } + impl<'hir> Visitor<'hir> for ExprFinder<'hir> { + fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) { + if expr.span == self.assignment_span + && let hir::ExprKind::Assign(lhs, rhs, _) = expr.kind + { + self.lhs = Some(lhs); + self.rhs = Some(rhs); + } else { + intravisit::walk_expr(self, expr) + } + } + } +} + +/// Compute self-assignments of the form `a += b`. +/// +/// MIR building generates 2 statements and 1 terminator for such assignments: +/// - _temp = CheckedBinaryOp(a, b) +/// - assert(!_temp.1) +/// - a = _temp.0 +/// +/// This function tries to detect this pattern in order to avoid marking statement as a definition +/// and use. This will let the analysis be dictated by the next use of `a`. +/// +/// Note that we will still need to account for the use of `b`. +fn find_self_assignments<'tcx>( + checked_places: &PlaceSet<'tcx>, + body: &Body<'tcx>, +) -> FxHashSet { + let mut self_assign = FxHashSet::default(); + + const FIELD_0: FieldIdx = FieldIdx::from_u32(0); + const FIELD_1: FieldIdx = FieldIdx::from_u32(1); + + for (bb, bb_data) in body.basic_blocks.iter_enumerated() { + for (statement_index, stmt) in bb_data.statements.iter().enumerate() { + let StatementKind::Assign(box (first_place, rvalue)) = &stmt.kind else { continue }; + match rvalue { + // For checked binary ops, the MIR builder inserts an assertion in between. + Rvalue::BinaryOp( + BinOp::AddWithOverflow | BinOp::SubWithOverflow | BinOp::MulWithOverflow, + box (Operand::Copy(lhs), _), + ) => { + // Checked binary ops only appear at the end of the block, before the assertion. + if statement_index + 1 != bb_data.statements.len() { + continue; + } + + let TerminatorKind::Assert { + cond, + target, + msg: box AssertKind::Overflow(..), + .. + } = &bb_data.terminator().kind + else { + continue; + }; + let Some(assign) = body.basic_blocks[*target].statements.first() else { + continue; + }; + let StatementKind::Assign(box (dest, Rvalue::Use(Operand::Move(temp)))) = + assign.kind + else { + continue; + }; + + if dest != *lhs { + continue; + } + + let Operand::Move(cond) = cond else { continue }; + let [PlaceElem::Field(FIELD_0, _)] = &temp.projection.as_slice() else { + continue; + }; + let [PlaceElem::Field(FIELD_1, _)] = &cond.projection.as_slice() else { + continue; + }; + + // We ignore indirect self-assignment, because both occurences of `dest` are uses. + let is_indirect = checked_places + .get(dest.as_ref()) + .map_or(false, |(_, projections)| is_indirect(projections)); + if is_indirect { + continue; + } + + if first_place.local == temp.local + && first_place.local == cond.local + && first_place.projection.is_empty() + { + // Original block + self_assign.insert(Location { + block: bb, + statement_index: bb_data.statements.len() - 1, + }); + self_assign.insert(Location { + block: bb, + statement_index: bb_data.statements.len(), + }); + // Target block + self_assign.insert(Location { block: *target, statement_index: 0 }); + } + } + // Straight self-assignment. + Rvalue::BinaryOp(op, box (Operand::Copy(lhs), _)) => { + if lhs != first_place { + continue; + } + + // We ignore indirect self-assignment, because both occurences of `dest` are uses. + let is_indirect = checked_places + .get(first_place.as_ref()) + .map_or(false, |(_, projections)| is_indirect(projections)); + if is_indirect { + continue; + } + + self_assign.insert(Location { block: bb, statement_index }); + + // Checked division verifies overflow before performing the division, so we + // need to go and ignore this check in the predecessor block. + if let BinOp::Div | BinOp::Rem = op + && statement_index == 0 + && let &[pred] = body.basic_blocks.predecessors()[bb].as_slice() + && let TerminatorKind::Assert { msg, .. } = + &body.basic_blocks[pred].terminator().kind + && let AssertKind::Overflow(..) = **msg + && let len = body.basic_blocks[pred].statements.len() + && len >= 2 + { + // BitAnd of two checks. + self_assign.insert(Location { block: pred, statement_index: len - 1 }); + // `lhs == MIN`. + self_assign.insert(Location { block: pred, statement_index: len - 2 }); + } + } + _ => {} + } + } + } + + self_assign +} + +#[derive(Default, Debug)] +struct PlaceSet<'tcx> { + places: IndexVec>, + names: IndexVec>, + + /// Places corresponding to locals, common case. + locals: IndexVec>, + + // Handling of captures. + /// If `_1` is a reference, we need to add a `Deref` to the matched place. + capture_field_pos: usize, + /// Captured fields. + captures: IndexVec, +} + +impl<'tcx> PlaceSet<'tcx> { + fn insert_locals(&mut self, decls: &IndexVec>) { + self.locals = IndexVec::from_elem(None, &decls); + for (local, decl) in decls.iter_enumerated() { + // Record all user-written locals for the analysis. + // We also keep the `RefForGuard` locals (more on that below). + if let LocalInfo::User(BindingForm::Var(_) | BindingForm::RefForGuard(_)) = + decl.local_info() + { + let index = self.places.push(local.into()); + self.locals[local] = Some(index); + let _index = self.names.push(None); + debug_assert_eq!(index, _index); + } + } + } + + fn insert_captures( + &mut self, + tcx: TyCtxt<'tcx>, + self_is_ref: bool, + captures: &[&'tcx ty::CapturedPlace<'tcx>], + upvars: &ty::List>, + ) { + // We should not track the environment local separately. + debug_assert_eq!(self.locals[ty::CAPTURE_STRUCT_LOCAL], None); + + let self_place = Place { + local: ty::CAPTURE_STRUCT_LOCAL, + projection: tcx.mk_place_elems(if self_is_ref { &[PlaceElem::Deref] } else { &[] }), + }; + if self_is_ref { + self.capture_field_pos = 1; + } + + for (f, (capture, ty)) in std::iter::zip(captures, upvars).enumerate() { + let f = FieldIdx::from_usize(f); + let elem = PlaceElem::Field(f, ty); + let by_ref = matches!(capture.info.capture_kind, ty::UpvarCapture::ByRef(..)); + let place = if by_ref { + self_place.project_deeper(&[elem, PlaceElem::Deref], tcx) + } else { + self_place.project_deeper(&[elem], tcx) + }; + let index = self.places.push(place.as_ref()); + let _f = self.captures.push((index, by_ref)); + debug_assert_eq!(_f, f); + + // Record a variable name from the capture, because it is much friendlier than the + // debuginfo name. + self.names.insert(index, (capture.to_string(tcx), capture.get_path_span(tcx))); + } + } + + fn record_debuginfo(&mut self, var_debug_info: &Vec>) { + for var_debug_info in var_debug_info { + if let VarDebugInfoContents::Place(place) = var_debug_info.value + && let Some(index) = self.locals[place.local] + { + self.names.get_or_insert_with(index, || { + (var_debug_info.name.to_string(), var_debug_info.source_info.span) + }); + } + } + } + + fn get(&self, place: PlaceRef<'tcx>) -> Option<(PlaceIndex, &'tcx [PlaceElem<'tcx>])> { + if let Some(index) = self.locals[place.local] { + return Some((index, place.projection)); + } + if place.local == ty::CAPTURE_STRUCT_LOCAL + && !self.captures.is_empty() + && self.capture_field_pos < place.projection.len() + && let PlaceElem::Field(f, _) = place.projection[self.capture_field_pos] + && let Some((index, by_ref)) = self.captures.get(f) + { + let mut start = self.capture_field_pos + 1; + if *by_ref { + // Account for an extra Deref. + start += 1; + } + // We may have an attempt to access `_1.f` as a shallow reborrow. Just ignore it. + if start <= place.projection.len() { + let projection = &place.projection[start..]; + return Some((*index, projection)); + } + } + None + } + + fn iter(&self) -> impl Iterator)> { + self.places.iter_enumerated() + } + + fn len(&self) -> usize { + self.places.len() + } +} + +/// Collect all assignments to checked locals. +/// +/// Assignments are collected, even if they are live. Dead assignments are reported, and live +/// assignments are used to make diagnostics correct for match guards. +fn find_dead_assignments<'tcx>( + tcx: TyCtxt<'tcx>, + checked_places: &PlaceSet<'tcx>, + cursor: &mut ResultsCursor<'_, 'tcx, MaybeLivePlaces<'_, 'tcx>>, + body: &Body<'tcx>, +) -> AssignmentResult { + let mut ever_live = DenseBitSet::new_empty(checked_places.len()); + let mut ever_dropped = DenseBitSet::new_empty(checked_places.len()); + let mut assignments = IndexVec::>::from_elem( + Default::default(), + &checked_places.places, + ); + + let mut check_place = + |place: Place<'tcx>, kind, source_info: SourceInfo, live: &DenseBitSet| { + if let Some((index, extra_projections)) = checked_places.get(place.as_ref()) { + if !is_indirect(extra_projections) { + match assignments[index].entry(source_info) { + IndexEntry::Vacant(v) => { + v.insert((live.contains(index), kind)); + } + IndexEntry::Occupied(mut o) => { + // There were already a sighting. Mark this statement as live if it was, + // to avoid false positives. + o.get_mut().0 |= live.contains(index); + } + } + } + } + }; + + let typing_env = ty::TypingEnv::post_analysis(tcx, body.source.def_id()); + let mut record_drop = |place: Place<'tcx>| { + if let Some((index, &[])) = checked_places.get(place.as_ref()) { + let ty = place.ty(&body.local_decls, tcx).ty; + let needs_drop = matches!( + ty.kind(), + ty::Closure(..) + | ty::Coroutine(..) + | ty::Tuple(..) + | ty::Adt(..) + | ty::Dynamic(..) + | ty::Array(..) + | ty::Slice(..) + | ty::Alias(ty::Opaque, ..) + ) && ty.needs_drop(tcx, typing_env); + if needs_drop { + ever_dropped.insert(index); + } + } + }; + + for (bb, bb_data) in traversal::postorder(body) { + cursor.seek_to_block_end(bb); + let live = cursor.get(); + ever_live.union(live); + + let terminator = bb_data.terminator(); + match &terminator.kind { + TerminatorKind::Call { destination: place, .. } + | TerminatorKind::Yield { resume_arg: place, .. } => { + check_place(*place, AccessKind::Assign, terminator.source_info, live); + record_drop(*place) + } + TerminatorKind::Drop { place, .. } => record_drop(*place), + TerminatorKind::InlineAsm { operands, .. } => { + for operand in operands { + if let InlineAsmOperand::Out { place: Some(place), .. } + | InlineAsmOperand::InOut { out_place: Some(place), .. } = operand + { + check_place(*place, AccessKind::Assign, terminator.source_info, live); + } + } + } + _ => {} + } + + for (statement_index, statement) in bb_data.statements.iter().enumerate().rev() { + cursor.seek_before_primary_effect(Location { block: bb, statement_index }); + let live = cursor.get(); + ever_live.union(live); + match &statement.kind { + StatementKind::Assign(box (place, _)) + | StatementKind::Deinit(box place) + | StatementKind::SetDiscriminant { box place, .. } => { + check_place(*place, AccessKind::Assign, statement.source_info, live); + } + StatementKind::Retag(_, _) + | StatementKind::StorageLive(_) + | StatementKind::StorageDead(_) + | StatementKind::Coverage(_) + | StatementKind::Intrinsic(_) + | StatementKind::Nop + | StatementKind::FakeRead(_) + | StatementKind::PlaceMention(_) + | StatementKind::ConstEvalCounter + | StatementKind::BackwardIncompatibleDropHint { .. } + | StatementKind::AscribeUserType(_, _) => (), + } + } + } + + // Check liveness of function arguments on entry. + { + cursor.seek_to_block_start(START_BLOCK); + let live = cursor.get(); + ever_live.union(live); + + // Verify that arguments and captured values are useful. + for (index, place) in checked_places.iter() { + let kind = if is_capture(*place) { + // This is a by-ref capture, an assignment to it will modify surrounding + // environment, so we do not report it. + if place.projection.last() == Some(&PlaceElem::Deref) { + continue; + } + + AccessKind::Capture + } else if body.local_kind(place.local) == LocalKind::Arg { + AccessKind::Param + } else { + continue; + }; + let source_info = body.local_decls[place.local].source_info; + assignments[index].insert(source_info, (live.contains(index), kind)); + } + } + + AssignmentResult { ever_live, ever_dropped, assignments } +} + +rustc_index::newtype_index! { + pub struct PlaceIndex {} +} + +impl DebugWithContext> for PlaceIndex { + fn fmt_with( + &self, + ctxt: &MaybeLivePlaces<'_, '_>, + f: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + std::fmt::Debug::fmt(&ctxt.checked_places.places[*self], f) + } +} + +pub struct MaybeLivePlaces<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + checked_places: &'a PlaceSet<'tcx>, + capture_kind: CaptureKind, + self_assignment: FxHashSet, +} + +impl<'tcx> MaybeLivePlaces<'_, 'tcx> { + fn transfer_function<'a>( + &'a self, + trans: &'a mut DenseBitSet, + ) -> TransferFunction<'a, 'tcx> { + TransferFunction { + tcx: self.tcx, + checked_places: &self.checked_places, + capture_kind: self.capture_kind, + trans, + self_assignment: &self.self_assignment, + } + } +} + +impl<'tcx> Analysis<'tcx> for MaybeLivePlaces<'_, 'tcx> { + type Domain = DenseBitSet; + type Direction = Backward; + + const NAME: &'static str = "liveness-lint"; + + fn bottom_value(&self, _: &Body<'tcx>) -> Self::Domain { + // bottom = not live + DenseBitSet::new_empty(self.checked_places.len()) + } + + fn initialize_start_block(&self, _: &Body<'tcx>, _: &mut Self::Domain) { + // No variables are live until we observe a use + } + + fn apply_primary_statement_effect( + &mut self, + trans: &mut Self::Domain, + statement: &Statement<'tcx>, + location: Location, + ) { + self.transfer_function(trans).visit_statement(statement, location); + } + + fn apply_primary_terminator_effect<'mir>( + &mut self, + trans: &mut Self::Domain, + terminator: &'mir Terminator<'tcx>, + location: Location, + ) -> TerminatorEdges<'mir, 'tcx> { + self.transfer_function(trans).visit_terminator(terminator, location); + terminator.edges() + } + + fn apply_call_return_effect( + &mut self, + _trans: &mut Self::Domain, + _block: BasicBlock, + _return_places: CallReturnPlaces<'_, 'tcx>, + ) { + // FIXME: what should happen here? + } +} + +struct TransferFunction<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + checked_places: &'a PlaceSet<'tcx>, + trans: &'a mut DenseBitSet, + capture_kind: CaptureKind, + self_assignment: &'a FxHashSet, +} + +impl<'tcx> Visitor<'tcx> for TransferFunction<'_, 'tcx> { + fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) { + match statement.kind { + // `ForLet(None)` fake read erroneously marks the just-assigned local as live. + // This defeats the purpose of the analysis for `let` bindings. + StatementKind::FakeRead(box (FakeReadCause::ForLet(None), _)) => return, + // Handle self-assignment by restricting the read/write they do. + StatementKind::Assign(box (ref dest, ref rvalue)) + if self.self_assignment.contains(&location) => + { + if let Rvalue::BinaryOp( + BinOp::AddWithOverflow | BinOp::SubWithOverflow | BinOp::MulWithOverflow, + box (_, rhs), + ) = rvalue + { + // We are computing the binary operation: + // - the LHS will be assigned, so we don't read it; + // - the RHS still needs to be read. + self.visit_operand(rhs, location); + self.visit_place( + dest, + PlaceContext::MutatingUse(MutatingUseContext::Store), + location, + ); + } else if let Rvalue::BinaryOp(_, box (_, rhs)) = rvalue { + // We are computing the binary operation: + // - the LHS is being updated, so we don't read it; + // - the RHS still needs to be read. + self.visit_operand(rhs, location); + } else { + // This is the second part of a checked self-assignment, + // we are assigning the result. + // We do not consider the write to the destination as a `def`. + // `self_assignment` must be false if the assignment is indirect. + self.visit_rvalue(rvalue, location); + } + } + _ => self.super_statement(statement, location), + } + } + + fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) { + // By-ref captures could be read by the surrounding environement, so we mark + // them as live upon yield and return. + match terminator.kind { + TerminatorKind::Return + | TerminatorKind::Yield { .. } + | TerminatorKind::Goto { target: START_BLOCK } // Inserted for the `FnMut` case. + if self.capture_kind != CaptureKind::None => + { + // All indirect captures have an effect on the environment, so we mark them as live. + for (index, place) in self.checked_places.iter() { + if place.local == ty::CAPTURE_STRUCT_LOCAL + && place.projection.last() == Some(&PlaceElem::Deref) + { + self.trans.insert(index); + } + } + } + // Do not consider a drop to be a use. We whitelist interesting drops elsewhere. + TerminatorKind::Drop { .. } => {} + // Ignore assertions since they must be triggered by actual code. + TerminatorKind::Assert { .. } => {} + _ => self.super_terminator(terminator, location), + } + } + + fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { + match rvalue { + // When a closure/generator does not use some of its captures, do not consider these + // captures as live in the surrounding function. This allows to report unused variables, + // even if they have been (uselessly) captured. + Rvalue::Aggregate( + box AggregateKind::Closure(def_id, _) | box AggregateKind::Coroutine(def_id, _), + operands, + ) => { + if let Some(def_id) = def_id.as_local() { + let dead_captures = self.tcx.check_liveness(def_id); + for (field, operand) in + operands.iter_enumerated().take(dead_captures.domain_size()) + { + if !dead_captures.contains(field) { + self.visit_operand(operand, location); + } + } + } + } + _ => self.super_rvalue(rvalue, location), + } + } + + fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, location: Location) { + if let Some((index, extra_projections)) = self.checked_places.get(place.as_ref()) { + for i in (extra_projections.len()..=place.projection.len()).rev() { + let place_part = + PlaceRef { local: place.local, projection: &place.projection[..i] }; + let extra_projections = &place.projection[i..]; + + if let Some(&elem) = extra_projections.get(0) { + self.visit_projection_elem(place_part, elem, context, location); + } + } + + match DefUse::for_place(extra_projections, context) { + Some(DefUse::Def) => { + self.trans.remove(index); + } + Some(DefUse::Use) => { + self.trans.insert(index); + } + None => {} + } + } else { + self.super_place(place, context, location) + } + } + + fn visit_local(&mut self, local: Local, context: PlaceContext, _: Location) { + if let Some((index, _proj)) = self.checked_places.get(local.into()) { + debug_assert_eq!(_proj, &[]); + match DefUse::for_place(&[], context) { + Some(DefUse::Def) => { + self.trans.remove(index); + } + Some(DefUse::Use) => { + self.trans.insert(index); + } + _ => {} + } + } + } +} + +#[derive(Eq, PartialEq, Debug, Clone)] +enum DefUse { + Def, + Use, +} + +fn is_indirect(proj: &[PlaceElem<'_>]) -> bool { + proj.iter().any(|p| p.is_indirect()) +} + +impl DefUse { + fn for_place<'tcx>(projection: &[PlaceElem<'tcx>], context: PlaceContext) -> Option { + let is_indirect = is_indirect(projection); + match context { + PlaceContext::MutatingUse( + MutatingUseContext::Store + | MutatingUseContext::Deinit + | MutatingUseContext::SetDiscriminant, + ) => { + if is_indirect { + // Treat derefs as a use of the base local. `*p = 4` is not a def of `p` but a + // use. + Some(DefUse::Use) + } else if projection.is_empty() { + Some(DefUse::Def) + } else { + None + } + } + + // For the associated terminators, this is only a `Def` when the terminator returns + // "successfully." As such, we handle this case separately in `call_return_effect` + // above. However, if the place looks like `*_5`, this is still unconditionally a use of + // `_5`. + PlaceContext::MutatingUse( + MutatingUseContext::Call + | MutatingUseContext::Yield + | MutatingUseContext::AsmOutput, + ) => is_indirect.then_some(DefUse::Use), + + // All other contexts are uses... + PlaceContext::MutatingUse( + MutatingUseContext::RawBorrow + | MutatingUseContext::Borrow + | MutatingUseContext::Drop + | MutatingUseContext::Retag, + ) + | PlaceContext::NonMutatingUse( + NonMutatingUseContext::RawBorrow + | NonMutatingUseContext::Copy + | NonMutatingUseContext::Inspect + | NonMutatingUseContext::Move + | NonMutatingUseContext::FakeBorrow + | NonMutatingUseContext::SharedBorrow + | NonMutatingUseContext::PlaceMention, + ) => Some(DefUse::Use), + + PlaceContext::NonUse( + NonUseContext::StorageLive + | NonUseContext::StorageDead + | NonUseContext::AscribeUserTy(_) + | NonUseContext::BackwardIncompatibleDropHint + | NonUseContext::VarDebugInfo, + ) => None, + + PlaceContext::MutatingUse(MutatingUseContext::Projection) + | PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection) => { + unreachable!("A projection could be a def or a use and must be handled separately") + } + } + } +} diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index 7eb4d9c358612..1f50f2bd2f8ca 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -456,8 +456,6 @@ passes_macro_use = passes_may_dangle = `#[may_dangle]` must be applied to a lifetime or type generic parameter in `Drop` impl -passes_maybe_string_interpolation = you might have meant to use string interpolation in this string literal - passes_missing_const_err = attributes `#[rustc_const_unstable]`, `#[rustc_const_stable]` and `#[rustc_const_stable_indirect]` require the function or method to be `const` .help = make the function or method const @@ -673,8 +671,6 @@ passes_should_be_applied_to_trait = passes_stability_promotable = attribute cannot be applied to an expression -passes_string_interpolation_only_works = string interpolation only works in `format!` invocations - passes_target_feature_on_statement = {passes_should_be_applied_to_fn} .warn = {-passes_previously_accepted} @@ -743,18 +739,6 @@ passes_unused = unused attribute .suggestion = remove this attribute -passes_unused_assign = value assigned to `{$name}` is never read - .help = maybe it is overwritten before being read? - -passes_unused_assign_passed = value passed to `{$name}` is never read - .help = maybe it is overwritten before being read? - -passes_unused_assign_suggestion = - you might have meant to mutate the pointed at value being passed in, instead of changing the reference in the local binding - -passes_unused_capture_maybe_capture_ref = value captured by `{$name}` is never read - .help = did you mean to capture by reference instead? - passes_unused_default_method_body_const_note = `default_method_body_is_const` has been replaced with `#[const_trait]` on traits @@ -778,25 +762,6 @@ passes_unused_multiple = passes_unused_no_lints_note = attribute `{$name}` without any lints has no effect -passes_unused_var_assigned_only = variable `{$name}` is assigned to, but never used - .note = consider using `_{$name}` instead - -passes_unused_var_maybe_capture_ref = unused variable: `{$name}` - .help = did you mean to capture by reference instead? - -passes_unused_var_remove_field = unused variable: `{$name}` -passes_unused_var_remove_field_suggestion = try removing the field - -passes_unused_variable_args_in_macro = `{$name}` is captured in macro and introduced a unused variable - -passes_unused_variable_try_ignore = unused variable: `{$name}` - .suggestion = try ignoring the field - -passes_unused_variable_try_prefix = unused variable: `{$name}` - .label = unused variable - .suggestion = if this is intentional, prefix it with an underscore - - passes_used_compiler_linker = `used(compiler)` and `used(linker)` can't be used together diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 57e92a6dbdb2e..ef6c6140f3325 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -4,7 +4,7 @@ use std::path::{Path, PathBuf}; use rustc_errors::codes::*; use rustc_errors::{ Applicability, Diag, DiagCtxtHandle, DiagSymbolList, Diagnostic, EmissionGuarantee, Level, - MultiSpan, Subdiagnostic, + MultiSpan, }; use rustc_hir::Target; use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic}; @@ -1539,45 +1539,6 @@ pub(crate) struct ProcMacroBadSig { pub kind: ProcMacroKind, } -#[derive(LintDiagnostic)] -#[diag(passes_unused_var_maybe_capture_ref)] -#[help] -pub(crate) struct UnusedVarMaybeCaptureRef { - pub name: String, -} - -#[derive(LintDiagnostic)] -#[diag(passes_unused_capture_maybe_capture_ref)] -#[help] -pub(crate) struct UnusedCaptureMaybeCaptureRef { - pub name: String, -} - -#[derive(LintDiagnostic)] -#[diag(passes_unused_var_remove_field)] -pub(crate) struct UnusedVarRemoveField { - pub name: String, - #[subdiagnostic] - pub sugg: UnusedVarRemoveFieldSugg, -} - -#[derive(Subdiagnostic)] -#[multipart_suggestion( - passes_unused_var_remove_field_suggestion, - applicability = "machine-applicable" -)] -pub(crate) struct UnusedVarRemoveFieldSugg { - #[suggestion_part(code = "")] - pub spans: Vec, -} - -#[derive(LintDiagnostic)] -#[diag(passes_unused_var_assigned_only)] -#[note] -pub(crate) struct UnusedVarAssignedOnly { - pub name: String, -} - #[derive(LintDiagnostic)] #[diag(passes_unnecessary_stable_feature)] pub(crate) struct UnnecessaryStableFeature { @@ -1602,99 +1563,6 @@ pub(crate) struct UnnecessaryPartialStableFeature { #[note] pub(crate) struct IneffectiveUnstableImpl; -#[derive(LintDiagnostic)] -#[diag(passes_unused_assign)] -pub(crate) struct UnusedAssign { - pub name: String, - #[subdiagnostic] - pub suggestion: Option, - #[help] - pub help: bool, -} - -#[derive(Subdiagnostic)] -#[multipart_suggestion(passes_unused_assign_suggestion, applicability = "maybe-incorrect")] -pub(crate) struct UnusedAssignSuggestion { - pub pre: &'static str, - #[suggestion_part(code = "{pre}mut ")] - pub ty_span: Option, - #[suggestion_part(code = "")] - pub ty_ref_span: Span, - #[suggestion_part(code = "*")] - pub ident_span: Span, - #[suggestion_part(code = "")] - pub expr_ref_span: Span, -} - -#[derive(LintDiagnostic)] -#[diag(passes_unused_assign_passed)] -#[help] -pub(crate) struct UnusedAssignPassed { - pub name: String, -} - -#[derive(LintDiagnostic)] -#[diag(passes_unused_variable_try_prefix)] -pub(crate) struct UnusedVariableTryPrefix { - #[label] - pub label: Option, - #[subdiagnostic] - pub string_interp: Vec, - #[subdiagnostic] - pub sugg: UnusedVariableSugg, - pub name: String, -} - -#[derive(Subdiagnostic)] -pub(crate) enum UnusedVariableSugg { - #[multipart_suggestion(passes_suggestion, applicability = "maybe-incorrect")] - TryPrefixSugg { - #[suggestion_part(code = "_{name}")] - spans: Vec, - name: String, - }, - #[help(passes_unused_variable_args_in_macro)] - NoSugg { - #[primary_span] - span: Span, - name: String, - }, -} - -pub(crate) struct UnusedVariableStringInterp { - pub lit: Span, - pub lo: Span, - pub hi: Span, -} - -impl Subdiagnostic for UnusedVariableStringInterp { - fn add_to_diag(self, diag: &mut Diag<'_, G>) { - diag.span_label(self.lit, crate::fluent_generated::passes_maybe_string_interpolation); - diag.multipart_suggestion( - crate::fluent_generated::passes_string_interpolation_only_works, - vec![(self.lo, String::from("format!(")), (self.hi, String::from(")"))], - Applicability::MachineApplicable, - ); - } -} - -#[derive(LintDiagnostic)] -#[diag(passes_unused_variable_try_ignore)] -pub(crate) struct UnusedVarTryIgnore { - #[subdiagnostic] - pub sugg: UnusedVarTryIgnoreSugg, -} - -#[derive(Subdiagnostic)] -#[multipart_suggestion(passes_suggestion, applicability = "maybe-incorrect")] -pub(crate) struct UnusedVarTryIgnoreSugg { - #[suggestion_part(code = "{name}: _")] - pub shorthands: Vec, - #[suggestion_part(code = "_")] - pub non_shorthands: Vec, - pub name: String, -} - #[derive(LintDiagnostic)] #[diag(passes_attr_crate_level)] #[note] diff --git a/compiler/rustc_passes/src/lib.rs b/compiler/rustc_passes/src/lib.rs index af7ecf0830c22..4796c688b737a 100644 --- a/compiler/rustc_passes/src/lib.rs +++ b/compiler/rustc_passes/src/lib.rs @@ -28,7 +28,6 @@ pub mod input_stats; mod lang_items; pub mod layout_test; mod lib_features; -mod liveness; mod reachable; pub mod stability; mod upvars; @@ -44,7 +43,6 @@ pub fn provide(providers: &mut Providers) { entry::provide(providers); lang_items::provide(providers); lib_features::provide(providers); - liveness::provide(providers); reachable::provide(providers); stability::provide(providers); upvars::provide(providers); diff --git a/compiler/rustc_passes/src/liveness.rs b/compiler/rustc_passes/src/liveness.rs deleted file mode 100644 index c8bc3338a29ea..0000000000000 --- a/compiler/rustc_passes/src/liveness.rs +++ /dev/null @@ -1,1800 +0,0 @@ -//! A classic liveness analysis based on dataflow over the AST. Computes, -//! for each local variable in a function, whether that variable is live -//! at a given point. Program execution points are identified by their -//! IDs. -//! -//! # Basic idea -//! -//! The basic model is that each local variable is assigned an index. We -//! represent sets of local variables using a vector indexed by this -//! index. The value in the vector is either 0, indicating the variable -//! is dead, or the ID of an expression that uses the variable. -//! -//! We conceptually walk over the AST in reverse execution order. If we -//! find a use of a variable, we add it to the set of live variables. If -//! we find an assignment to a variable, we remove it from the set of live -//! variables. When we have to merge two flows, we take the union of -//! those two flows -- if the variable is live on both paths, we simply -//! pick one ID. In the event of loops, we continue doing this until a -//! fixed point is reached. -//! -//! ## Checking initialization -//! -//! At the function entry point, all variables must be dead. If this is -//! not the case, we can report an error using the ID found in the set of -//! live variables, which identifies a use of the variable which is not -//! dominated by an assignment. -//! -//! ## Checking moves -//! -//! After each explicit move, the variable must be dead. -//! -//! ## Computing last uses -//! -//! Any use of the variable where the variable is dead afterwards is a -//! last use. -//! -//! # Implementation details -//! -//! The actual implementation contains two (nested) walks over the AST. -//! The outer walk has the job of building up the ir_maps instance for the -//! enclosing function. On the way down the tree, it identifies those AST -//! nodes and variable IDs that will be needed for the liveness analysis -//! and assigns them contiguous IDs. The liveness ID for an AST node is -//! called a `live_node` (it's a newtype'd `u32`) and the ID for a variable -//! is called a `variable` (another newtype'd `u32`). -//! -//! On the way back up the tree, as we are about to exit from a function -//! declaration we allocate a `liveness` instance. Now that we know -//! precisely how many nodes and variables we need, we can allocate all -//! the various arrays that we will need to precisely the right size. We then -//! perform the actual propagation on the `liveness` instance. -//! -//! This propagation is encoded in the various `propagate_through_*()` -//! methods. It effectively does a reverse walk of the AST; whenever we -//! reach a loop node, we iterate until a fixed point is reached. -//! -//! ## The `RWU` struct -//! -//! At each live node `N`, we track three pieces of information for each -//! variable `V` (these are encapsulated in the `RWU` struct): -//! -//! - `reader`: the `LiveNode` ID of some node which will read the value -//! that `V` holds on entry to `N`. Formally: a node `M` such -//! that there exists a path `P` from `N` to `M` where `P` does not -//! write `V`. If the `reader` is `None`, then the current -//! value will never be read (the variable is dead, essentially). -//! -//! - `writer`: the `LiveNode` ID of some node which will write the -//! variable `V` and which is reachable from `N`. Formally: a node `M` -//! such that there exists a path `P` from `N` to `M` and `M` writes -//! `V`. If the `writer` is `None`, then there is no writer -//! of `V` that follows `N`. -//! -//! - `used`: a boolean value indicating whether `V` is *used*. We -//! distinguish a *read* from a *use* in that a *use* is some read that -//! is not just used to generate a new value. For example, `x += 1` is -//! a read but not a use. This is used to generate better warnings. -//! -//! ## Special nodes and variables -//! -//! We generate various special nodes for various, well, special purposes. -//! These are described in the `Liveness` struct. - -use std::io; -use std::io::prelude::*; -use std::rc::Rc; - -use rustc_data_structures::fx::FxIndexMap; -use rustc_hir as hir; -use rustc_hir::def::*; -use rustc_hir::def_id::LocalDefId; -use rustc_hir::intravisit::{self, Visitor}; -use rustc_hir::{Expr, HirId, HirIdMap, HirIdSet}; -use rustc_index::IndexVec; -use rustc_middle::query::Providers; -use rustc_middle::span_bug; -use rustc_middle::ty::{self, RootVariableMinCaptureList, TyCtxt}; -use rustc_session::lint; -use rustc_span::{BytePos, Span, Symbol, sym}; -use tracing::{debug, instrument}; - -use self::LiveNodeKind::*; -use self::VarKind::*; -use crate::errors; - -mod rwu_table; - -rustc_index::newtype_index! { - #[debug_format = "v({})"] - pub struct Variable {} -} - -rustc_index::newtype_index! { - #[debug_format = "ln({})"] - pub struct LiveNode {} -} - -#[derive(Copy, Clone, PartialEq, Debug)] -enum LiveNodeKind { - UpvarNode(Span), - ExprNode(Span, HirId), - VarDefNode(Span, HirId), - ClosureNode, - ExitNode, -} - -fn live_node_kind_to_string(lnk: LiveNodeKind, tcx: TyCtxt<'_>) -> String { - let sm = tcx.sess.source_map(); - match lnk { - UpvarNode(s) => format!("Upvar node [{}]", sm.span_to_diagnostic_string(s)), - ExprNode(s, _) => format!("Expr node [{}]", sm.span_to_diagnostic_string(s)), - VarDefNode(s, _) => format!("Var def node [{}]", sm.span_to_diagnostic_string(s)), - ClosureNode => "Closure node".to_owned(), - ExitNode => "Exit node".to_owned(), - } -} - -fn check_liveness(tcx: TyCtxt<'_>, def_id: LocalDefId) { - // Don't run unused pass for #[derive()] - let parent = tcx.local_parent(def_id); - if let DefKind::Impl { .. } = tcx.def_kind(parent) - && tcx.has_attr(parent, sym::automatically_derived) - { - return; - } - - // Don't run unused pass for #[naked] - if tcx.has_attr(def_id.to_def_id(), sym::naked) { - return; - } - - let mut maps = IrMaps::new(tcx); - let body = tcx.hir_body_owned_by(def_id); - let hir_id = tcx.hir_body_owner(body.id()); - - if let Some(upvars) = tcx.upvars_mentioned(def_id) { - for &var_hir_id in upvars.keys() { - let var_name = tcx.hir_name(var_hir_id); - maps.add_variable(Upvar(var_hir_id, var_name)); - } - } - - // gather up the various local variables, significant expressions, - // and so forth: - maps.visit_body(&body); - - // compute liveness - let mut lsets = Liveness::new(&mut maps, def_id); - let entry_ln = lsets.compute(&body, hir_id); - lsets.log_liveness(entry_ln, body.id().hir_id); - - // check for various error conditions - lsets.visit_body(&body); - lsets.warn_about_unused_upvars(entry_ln); - lsets.warn_about_unused_args(&body, entry_ln); -} - -pub(crate) fn provide(providers: &mut Providers) { - *providers = Providers { check_liveness, ..*providers }; -} - -// ______________________________________________________________________ -// Creating ir_maps -// -// This is the first pass and the one that drives the main -// computation. It walks up and down the IR once. On the way down, -// we count for each function the number of variables as well as -// liveness nodes. A liveness node is basically an expression or -// capture clause that does something of interest: either it has -// interesting control flow or it uses/defines a local variable. -// -// On the way back up, at each function node we create liveness sets -// (we now know precisely how big to make our various vectors and so -// forth) and then do the data-flow propagation to compute the set -// of live variables at each program point. -// -// Finally, we run back over the IR one last time and, using the -// computed liveness, check various safety conditions. For example, -// there must be no live nodes at the definition site for a variable -// unless it has an initializer. Similarly, each non-mutable local -// variable must not be assigned if there is some successor -// assignment. And so forth. - -struct CaptureInfo { - ln: LiveNode, - var_hid: HirId, -} - -#[derive(Copy, Clone, Debug)] -struct LocalInfo { - id: HirId, - name: Symbol, - is_shorthand: bool, -} - -#[derive(Copy, Clone, Debug)] -enum VarKind { - Param(HirId, Symbol), - Local(LocalInfo), - Upvar(HirId, Symbol), -} - -struct CollectLitsVisitor<'tcx> { - lit_exprs: Vec<&'tcx hir::Expr<'tcx>>, -} - -impl<'tcx> Visitor<'tcx> for CollectLitsVisitor<'tcx> { - fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { - if let hir::ExprKind::Lit(_) = expr.kind { - self.lit_exprs.push(expr); - } - intravisit::walk_expr(self, expr); - } -} - -struct IrMaps<'tcx> { - tcx: TyCtxt<'tcx>, - live_node_map: HirIdMap, - variable_map: HirIdMap, - capture_info_map: HirIdMap>>, - var_kinds: IndexVec, - lnks: IndexVec, -} - -impl<'tcx> IrMaps<'tcx> { - fn new(tcx: TyCtxt<'tcx>) -> IrMaps<'tcx> { - IrMaps { - tcx, - live_node_map: HirIdMap::default(), - variable_map: HirIdMap::default(), - capture_info_map: Default::default(), - var_kinds: IndexVec::new(), - lnks: IndexVec::new(), - } - } - - fn add_live_node(&mut self, lnk: LiveNodeKind) -> LiveNode { - let ln = self.lnks.push(lnk); - - debug!("{:?} is of kind {}", ln, live_node_kind_to_string(lnk, self.tcx)); - - ln - } - - fn add_live_node_for_node(&mut self, hir_id: HirId, lnk: LiveNodeKind) { - let ln = self.add_live_node(lnk); - self.live_node_map.insert(hir_id, ln); - - debug!("{:?} is node {:?}", ln, hir_id); - } - - fn add_variable(&mut self, vk: VarKind) -> Variable { - let v = self.var_kinds.push(vk); - - match vk { - Local(LocalInfo { id: node_id, .. }) | Param(node_id, _) | Upvar(node_id, _) => { - self.variable_map.insert(node_id, v); - } - } - - debug!("{:?} is {:?}", v, vk); - - v - } - - fn variable(&self, hir_id: HirId, span: Span) -> Variable { - match self.variable_map.get(&hir_id) { - Some(&var) => var, - None => { - span_bug!(span, "no variable registered for id {:?}", hir_id); - } - } - } - - fn variable_name(&self, var: Variable) -> Symbol { - match self.var_kinds[var] { - Local(LocalInfo { name, .. }) | Param(_, name) | Upvar(_, name) => name, - } - } - - fn variable_is_shorthand(&self, var: Variable) -> bool { - match self.var_kinds[var] { - Local(LocalInfo { is_shorthand, .. }) => is_shorthand, - Param(..) | Upvar(..) => false, - } - } - - fn set_captures(&mut self, hir_id: HirId, cs: Vec) { - self.capture_info_map.insert(hir_id, Rc::new(cs)); - } - - fn collect_shorthand_field_ids(&self, pat: &hir::Pat<'tcx>) -> HirIdSet { - // For struct patterns, take note of which fields used shorthand - // (`x` rather than `x: x`). - let mut shorthand_field_ids = HirIdSet::default(); - - pat.walk_always(|pat| { - if let hir::PatKind::Struct(_, fields, _) = pat.kind { - let short = fields.iter().filter(|f| f.is_shorthand); - shorthand_field_ids.extend(short.map(|f| f.pat.hir_id)); - } - }); - - shorthand_field_ids - } - - fn add_from_pat(&mut self, pat: &hir::Pat<'tcx>) { - let shorthand_field_ids = self.collect_shorthand_field_ids(pat); - - pat.each_binding(|_, hir_id, _, ident| { - self.add_live_node_for_node(hir_id, VarDefNode(ident.span, hir_id)); - self.add_variable(Local(LocalInfo { - id: hir_id, - name: ident.name, - is_shorthand: shorthand_field_ids.contains(&hir_id), - })); - }); - } -} - -impl<'tcx> Visitor<'tcx> for IrMaps<'tcx> { - fn visit_local(&mut self, local: &'tcx hir::LetStmt<'tcx>) { - self.add_from_pat(local.pat); - if local.els.is_some() { - self.add_live_node_for_node(local.hir_id, ExprNode(local.span, local.hir_id)); - } - intravisit::walk_local(self, local); - } - - fn visit_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) { - self.add_from_pat(&arm.pat); - intravisit::walk_arm(self, arm); - } - - fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) { - let shorthand_field_ids = self.collect_shorthand_field_ids(param.pat); - param.pat.each_binding(|_bm, hir_id, _x, ident| { - let var = match param.pat.kind { - rustc_hir::PatKind::Struct(..) => Local(LocalInfo { - id: hir_id, - name: ident.name, - is_shorthand: shorthand_field_ids.contains(&hir_id), - }), - _ => Param(hir_id, ident.name), - }; - self.add_variable(var); - }); - intravisit::walk_param(self, param); - } - - fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { - match expr.kind { - // live nodes required for uses or definitions of variables: - hir::ExprKind::Path(hir::QPath::Resolved(_, path)) => { - debug!("expr {}: path that leads to {:?}", expr.hir_id, path.res); - if let Res::Local(_var_hir_id) = path.res { - self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id)); - } - } - hir::ExprKind::Closure(closure) => { - // Interesting control flow (for loops can contain labeled - // breaks or continues) - self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id)); - - // Make a live_node for each mentioned variable, with the span - // being the location that the variable is used. This results - // in better error messages than just pointing at the closure - // construction site. - let mut call_caps = Vec::new(); - if let Some(upvars) = self.tcx.upvars_mentioned(closure.def_id) { - call_caps.extend(upvars.keys().map(|var_id| { - let upvar = upvars[var_id]; - let upvar_ln = self.add_live_node(UpvarNode(upvar.span)); - CaptureInfo { ln: upvar_ln, var_hid: *var_id } - })); - } - self.set_captures(expr.hir_id, call_caps); - } - - hir::ExprKind::Let(let_expr) => { - self.add_from_pat(let_expr.pat); - } - - // live nodes required for interesting control flow: - hir::ExprKind::If(..) - | hir::ExprKind::Match(..) - | hir::ExprKind::Loop(..) - | hir::ExprKind::Yield(..) => { - self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id)); - } - hir::ExprKind::Binary(op, ..) if op.node.is_lazy() => { - self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id)); - } - - // Inline assembly may contain labels. - hir::ExprKind::InlineAsm(asm) if asm.contains_label() => { - self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id)); - intravisit::walk_expr(self, expr); - } - - // otherwise, live nodes are not required: - hir::ExprKind::Index(..) - | hir::ExprKind::Field(..) - | hir::ExprKind::Array(..) - | hir::ExprKind::Call(..) - | hir::ExprKind::MethodCall(..) - | hir::ExprKind::Use(..) - | hir::ExprKind::Tup(..) - | hir::ExprKind::Binary(..) - | hir::ExprKind::AddrOf(..) - | hir::ExprKind::Cast(..) - | hir::ExprKind::DropTemps(..) - | hir::ExprKind::Unary(..) - | hir::ExprKind::Break(..) - | hir::ExprKind::Continue(_) - | hir::ExprKind::Lit(_) - | hir::ExprKind::ConstBlock(..) - | hir::ExprKind::Ret(..) - | hir::ExprKind::Become(..) - | hir::ExprKind::Block(..) - | hir::ExprKind::Assign(..) - | hir::ExprKind::AssignOp(..) - | hir::ExprKind::Struct(..) - | hir::ExprKind::Repeat(..) - | hir::ExprKind::InlineAsm(..) - | hir::ExprKind::OffsetOf(..) - | hir::ExprKind::Type(..) - | hir::ExprKind::UnsafeBinderCast(..) - | hir::ExprKind::Err(_) - | hir::ExprKind::Path(hir::QPath::TypeRelative(..)) - | hir::ExprKind::Path(hir::QPath::LangItem(..)) => {} - } - intravisit::walk_expr(self, expr); - } -} - -// ______________________________________________________________________ -// Computing liveness sets -// -// Actually we compute just a bit more than just liveness, but we use -// the same basic propagation framework in all cases. - -const ACC_READ: u32 = 1; -const ACC_WRITE: u32 = 2; -const ACC_USE: u32 = 4; - -struct Liveness<'a, 'tcx> { - ir: &'a mut IrMaps<'tcx>, - typeck_results: &'a ty::TypeckResults<'tcx>, - typing_env: ty::TypingEnv<'tcx>, - closure_min_captures: Option<&'tcx RootVariableMinCaptureList<'tcx>>, - successors: IndexVec>, - rwu_table: rwu_table::RWUTable, - - /// A live node representing a point of execution before closure entry & - /// after closure exit. Used to calculate liveness of captured variables - /// through calls to the same closure. Used for Fn & FnMut closures only. - closure_ln: LiveNode, - /// A live node representing every 'exit' from the function, whether it be - /// by explicit return, panic, or other means. - exit_ln: LiveNode, - - // mappings from loop node ID to LiveNode - // ("break" label should map to loop node ID, - // it probably doesn't now) - break_ln: HirIdMap, - cont_ln: HirIdMap, -} - -impl<'a, 'tcx> Liveness<'a, 'tcx> { - fn new(ir: &'a mut IrMaps<'tcx>, body_owner: LocalDefId) -> Liveness<'a, 'tcx> { - let typeck_results = ir.tcx.typeck(body_owner); - // Liveness linting runs after building the THIR. We make several assumptions based on - // typeck succeeding, e.g. that breaks and continues are well-formed. - assert!(typeck_results.tainted_by_errors.is_none()); - // FIXME(#132279): we're in a body here. - let typing_env = ty::TypingEnv::non_body_analysis(ir.tcx, body_owner); - let closure_min_captures = typeck_results.closure_min_captures.get(&body_owner); - let closure_ln = ir.add_live_node(ClosureNode); - let exit_ln = ir.add_live_node(ExitNode); - - let num_live_nodes = ir.lnks.len(); - let num_vars = ir.var_kinds.len(); - - Liveness { - ir, - typeck_results, - typing_env, - closure_min_captures, - successors: IndexVec::from_elem_n(None, num_live_nodes), - rwu_table: rwu_table::RWUTable::new(num_live_nodes, num_vars), - closure_ln, - exit_ln, - break_ln: Default::default(), - cont_ln: Default::default(), - } - } - - fn live_node(&self, hir_id: HirId, span: Span) -> LiveNode { - match self.ir.live_node_map.get(&hir_id) { - Some(&ln) => ln, - None => { - // This must be a mismatch between the ir_map construction - // above and the propagation code below; the two sets of - // code have to agree about which AST nodes are worth - // creating liveness nodes for. - span_bug!(span, "no live node registered for node {:?}", hir_id); - } - } - } - - fn variable(&self, hir_id: HirId, span: Span) -> Variable { - self.ir.variable(hir_id, span) - } - - fn define_bindings_in_pat(&mut self, pat: &hir::Pat<'_>, mut succ: LiveNode) -> LiveNode { - // In an or-pattern, only consider the first non-never pattern; any later patterns - // must have the same bindings, and we also consider that pattern - // to be the "authoritative" set of ids. - pat.each_binding_or_first(&mut |_, hir_id, pat_sp, ident| { - let ln = self.live_node(hir_id, pat_sp); - let var = self.variable(hir_id, ident.span); - self.init_from_succ(ln, succ); - self.define(ln, var); - succ = ln; - }); - succ - } - - fn live_on_entry(&self, ln: LiveNode, var: Variable) -> bool { - self.rwu_table.get_reader(ln, var) - } - - // Is this variable live on entry to any of its successor nodes? - fn live_on_exit(&self, ln: LiveNode, var: Variable) -> bool { - let successor = self.successors[ln].unwrap(); - self.live_on_entry(successor, var) - } - - fn used_on_entry(&self, ln: LiveNode, var: Variable) -> bool { - self.rwu_table.get_used(ln, var) - } - - fn assigned_on_entry(&self, ln: LiveNode, var: Variable) -> bool { - self.rwu_table.get_writer(ln, var) - } - - fn assigned_on_exit(&self, ln: LiveNode, var: Variable) -> bool { - match self.successors[ln] { - Some(successor) => self.assigned_on_entry(successor, var), - None => { - self.ir.tcx.dcx().delayed_bug("no successor"); - true - } - } - } - - fn write_vars(&self, wr: &mut dyn Write, mut test: F) -> io::Result<()> - where - F: FnMut(Variable) -> bool, - { - for var in self.ir.var_kinds.indices() { - if test(var) { - write!(wr, " {var:?}")?; - } - } - Ok(()) - } - - #[allow(unused_must_use)] - fn ln_str(&self, ln: LiveNode) -> String { - let mut wr = Vec::new(); - { - let wr = &mut wr as &mut dyn Write; - write!(wr, "[{:?} of kind {:?} reads", ln, self.ir.lnks[ln]); - self.write_vars(wr, |var| self.rwu_table.get_reader(ln, var)); - write!(wr, " writes"); - self.write_vars(wr, |var| self.rwu_table.get_writer(ln, var)); - write!(wr, " uses"); - self.write_vars(wr, |var| self.rwu_table.get_used(ln, var)); - - write!(wr, " precedes {:?}]", self.successors[ln]); - } - String::from_utf8(wr).unwrap() - } - - fn log_liveness(&self, entry_ln: LiveNode, hir_id: HirId) { - // hack to skip the loop unless debug! is enabled: - debug!( - "^^ liveness computation results for body {} (entry={:?})", - { - for ln_idx in self.ir.lnks.indices() { - debug!("{:?}", self.ln_str(ln_idx)); - } - hir_id - }, - entry_ln - ); - } - - fn init_empty(&mut self, ln: LiveNode, succ_ln: LiveNode) { - self.successors[ln] = Some(succ_ln); - - // It is not necessary to initialize the RWUs here because they are all - // empty when created, and the sets only grow during iterations. - } - - fn init_from_succ(&mut self, ln: LiveNode, succ_ln: LiveNode) { - // more efficient version of init_empty() / merge_from_succ() - self.successors[ln] = Some(succ_ln); - self.rwu_table.copy(ln, succ_ln); - debug!("init_from_succ(ln={}, succ={})", self.ln_str(ln), self.ln_str(succ_ln)); - } - - fn merge_from_succ(&mut self, ln: LiveNode, succ_ln: LiveNode) -> bool { - if ln == succ_ln { - return false; - } - - let changed = self.rwu_table.union(ln, succ_ln); - debug!("merge_from_succ(ln={:?}, succ={}, changed={})", ln, self.ln_str(succ_ln), changed); - changed - } - - // Indicates that a local variable was *defined*; we know that no - // uses of the variable can precede the definition (resolve checks - // this) so we just clear out all the data. - fn define(&mut self, writer: LiveNode, var: Variable) { - let used = self.rwu_table.get_used(writer, var); - self.rwu_table.set(writer, var, rwu_table::RWU { reader: false, writer: false, used }); - debug!("{:?} defines {:?}: {}", writer, var, self.ln_str(writer)); - } - - // Either read, write, or both depending on the acc bitset - fn acc(&mut self, ln: LiveNode, var: Variable, acc: u32) { - debug!("{:?} accesses[{:x}] {:?}: {}", ln, acc, var, self.ln_str(ln)); - - let mut rwu = self.rwu_table.get(ln, var); - - if (acc & ACC_WRITE) != 0 { - rwu.reader = false; - rwu.writer = true; - } - - // Important: if we both read/write, must do read second - // or else the write will override. - if (acc & ACC_READ) != 0 { - rwu.reader = true; - } - - if (acc & ACC_USE) != 0 { - rwu.used = true; - } - - self.rwu_table.set(ln, var, rwu); - } - - fn compute(&mut self, body: &hir::Body<'_>, hir_id: HirId) -> LiveNode { - debug!("compute: for body {:?}", body.id().hir_id); - - // # Liveness of captured variables - // - // When computing the liveness for captured variables we take into - // account how variable is captured (ByRef vs ByValue) and what is the - // closure kind (Coroutine / FnOnce vs Fn / FnMut). - // - // Variables captured by reference are assumed to be used on the exit - // from the closure. - // - // In FnOnce closures, variables captured by value are known to be dead - // on exit since it is impossible to call the closure again. - // - // In Fn / FnMut closures, variables captured by value are live on exit - // if they are live on the entry to the closure, since only the closure - // itself can access them on subsequent calls. - - if let Some(closure_min_captures) = self.closure_min_captures { - // Mark upvars captured by reference as used after closure exits. - for (&var_hir_id, min_capture_list) in closure_min_captures { - for captured_place in min_capture_list { - match captured_place.info.capture_kind { - ty::UpvarCapture::ByRef(_) => { - let var = self.variable( - var_hir_id, - captured_place.get_capture_kind_span(self.ir.tcx), - ); - self.acc(self.exit_ln, var, ACC_READ | ACC_USE); - } - ty::UpvarCapture::ByValue | ty::UpvarCapture::ByUse => {} - } - } - } - } - - let succ = self.propagate_through_expr(body.value, self.exit_ln); - - if self.closure_min_captures.is_none() { - // Either not a closure, or closure without any captured variables. - // No need to determine liveness of captured variables, since there - // are none. - return succ; - } - - let ty = self.typeck_results.node_type(hir_id); - match ty.kind() { - ty::Closure(_def_id, args) => match args.as_closure().kind() { - ty::ClosureKind::Fn => {} - ty::ClosureKind::FnMut => {} - ty::ClosureKind::FnOnce => return succ, - }, - ty::CoroutineClosure(_def_id, args) => match args.as_coroutine_closure().kind() { - ty::ClosureKind::Fn => {} - ty::ClosureKind::FnMut => {} - ty::ClosureKind::FnOnce => return succ, - }, - ty::Coroutine(..) => return succ, - _ => { - span_bug!( - body.value.span, - "{} has upvars so it should have a closure type: {:?}", - hir_id, - ty - ); - } - }; - - // Propagate through calls to the closure. - loop { - self.init_from_succ(self.closure_ln, succ); - for param in body.params { - param.pat.each_binding(|_bm, hir_id, _x, ident| { - let var = self.variable(hir_id, ident.span); - self.define(self.closure_ln, var); - }) - } - - if !self.merge_from_succ(self.exit_ln, self.closure_ln) { - break; - } - assert_eq!(succ, self.propagate_through_expr(body.value, self.exit_ln)); - } - - succ - } - - fn propagate_through_block(&mut self, blk: &hir::Block<'_>, succ: LiveNode) -> LiveNode { - if blk.targeted_by_break { - self.break_ln.insert(blk.hir_id, succ); - } - let succ = self.propagate_through_opt_expr(blk.expr, succ); - blk.stmts.iter().rev().fold(succ, |succ, stmt| self.propagate_through_stmt(stmt, succ)) - } - - fn propagate_through_stmt(&mut self, stmt: &hir::Stmt<'_>, succ: LiveNode) -> LiveNode { - match stmt.kind { - hir::StmtKind::Let(local) => { - // Note: we mark the variable as defined regardless of whether - // there is an initializer. Initially I had thought to only mark - // the live variable as defined if it was initialized, and then we - // could check for uninit variables just by scanning what is live - // at the start of the function. But that doesn't work so well for - // immutable variables defined in a loop: - // loop { let x; x = 5; } - // because the "assignment" loops back around and generates an error. - // - // So now we just check that variables defined w/o an - // initializer are not live at the point of their - // initialization, which is mildly more complex than checking - // once at the func header but otherwise equivalent. - - if let Some(els) = local.els { - // Eventually, `let pat: ty = init else { els };` is mostly equivalent to - // `let (bindings, ...) = match init { pat => (bindings, ...), _ => els };` - // except that extended lifetime applies at the `init` location. - // - // (e) - // | - // v - // (expr) - // / \ - // | | - // v v - // bindings els - // | - // v - // ( succ ) - // - if let Some(init) = local.init { - let else_ln = self.propagate_through_block(els, succ); - let ln = self.live_node(local.hir_id, local.span); - self.init_from_succ(ln, succ); - self.merge_from_succ(ln, else_ln); - let succ = self.propagate_through_expr(init, ln); - self.define_bindings_in_pat(local.pat, succ) - } else { - span_bug!( - stmt.span, - "variable is uninitialized but an unexpected else branch is found" - ) - } - } else { - let succ = self.propagate_through_opt_expr(local.init, succ); - self.define_bindings_in_pat(local.pat, succ) - } - } - hir::StmtKind::Item(..) => succ, - hir::StmtKind::Expr(ref expr) | hir::StmtKind::Semi(ref expr) => { - self.propagate_through_expr(expr, succ) - } - } - } - - fn propagate_through_exprs(&mut self, exprs: &[Expr<'_>], succ: LiveNode) -> LiveNode { - exprs.iter().rev().fold(succ, |succ, expr| self.propagate_through_expr(expr, succ)) - } - - fn propagate_through_opt_expr( - &mut self, - opt_expr: Option<&Expr<'_>>, - succ: LiveNode, - ) -> LiveNode { - opt_expr.map_or(succ, |expr| self.propagate_through_expr(expr, succ)) - } - - fn propagate_through_expr(&mut self, expr: &Expr<'_>, succ: LiveNode) -> LiveNode { - debug!("propagate_through_expr: {:?}", expr); - - match expr.kind { - // Interesting cases with control flow or which gen/kill - hir::ExprKind::Path(hir::QPath::Resolved(_, path)) => { - self.access_path(expr.hir_id, path, succ, ACC_READ | ACC_USE) - } - - hir::ExprKind::Field(ref e, _) => self.propagate_through_expr(e, succ), - - hir::ExprKind::Closure { .. } => { - debug!("{:?} is an ExprKind::Closure", expr); - - // the construction of a closure itself is not important, - // but we have to consider the closed over variables. - let caps = self - .ir - .capture_info_map - .get(&expr.hir_id) - .cloned() - .unwrap_or_else(|| span_bug!(expr.span, "no registered caps")); - - caps.iter().rev().fold(succ, |succ, cap| { - self.init_from_succ(cap.ln, succ); - let var = self.variable(cap.var_hid, expr.span); - self.acc(cap.ln, var, ACC_READ | ACC_USE); - cap.ln - }) - } - - hir::ExprKind::Let(let_expr) => { - let succ = self.propagate_through_expr(let_expr.init, succ); - self.define_bindings_in_pat(let_expr.pat, succ) - } - - // Note that labels have been resolved, so we don't need to look - // at the label ident - hir::ExprKind::Loop(ref blk, ..) => self.propagate_through_loop(expr, blk, succ), - - hir::ExprKind::Yield(e, ..) => { - let yield_ln = self.live_node(expr.hir_id, expr.span); - self.init_from_succ(yield_ln, succ); - self.merge_from_succ(yield_ln, self.exit_ln); - self.propagate_through_expr(e, yield_ln) - } - - hir::ExprKind::If(ref cond, ref then, ref else_opt) => { - // - // (cond) - // | - // v - // (expr) - // / \ - // | | - // v v - // (then)(els) - // | | - // v v - // ( succ ) - // - let else_ln = self.propagate_through_opt_expr(else_opt.as_deref(), succ); - let then_ln = self.propagate_through_expr(then, succ); - let ln = self.live_node(expr.hir_id, expr.span); - self.init_from_succ(ln, else_ln); - self.merge_from_succ(ln, then_ln); - self.propagate_through_expr(cond, ln) - } - - hir::ExprKind::Match(ref e, arms, _) => { - // - // (e) - // | - // v - // (expr) - // / | \ - // | | | - // v v v - // (..arms..) - // | | | - // v v v - // ( succ ) - // - // - let ln = self.live_node(expr.hir_id, expr.span); - self.init_empty(ln, succ); - for arm in arms { - let body_succ = self.propagate_through_expr(arm.body, succ); - - let guard_succ = arm - .guard - .as_ref() - .map_or(body_succ, |g| self.propagate_through_expr(g, body_succ)); - let arm_succ = self.define_bindings_in_pat(&arm.pat, guard_succ); - self.merge_from_succ(ln, arm_succ); - } - self.propagate_through_expr(e, ln) - } - - hir::ExprKind::Ret(ref o_e) => { - // Ignore succ and subst exit_ln. - self.propagate_through_opt_expr(o_e.as_deref(), self.exit_ln) - } - - hir::ExprKind::Become(e) => { - // Ignore succ and subst exit_ln. - self.propagate_through_expr(e, self.exit_ln) - } - - hir::ExprKind::Break(label, ref opt_expr) => { - // Find which label this break jumps to - let target = match label.target_id { - Ok(hir_id) => self.break_ln.get(&hir_id), - Err(err) => span_bug!(expr.span, "loop scope error: {}", err), - } - .cloned(); - - // Now that we know the label we're going to, - // look it up in the break loop nodes table - - match target { - Some(b) => self.propagate_through_opt_expr(opt_expr.as_deref(), b), - None => span_bug!(expr.span, "`break` to unknown label"), - } - } - - hir::ExprKind::Continue(label) => { - // Find which label this expr continues to - let sc = label - .target_id - .unwrap_or_else(|err| span_bug!(expr.span, "loop scope error: {}", err)); - - // Now that we know the label we're going to, - // look it up in the continue loop nodes table - self.cont_ln.get(&sc).cloned().unwrap_or_else(|| { - // Liveness linting happens after building the THIR. Bad labels should already - // have been caught. - span_bug!(expr.span, "continue to unknown label"); - }) - } - - hir::ExprKind::Assign(ref l, ref r, _) => { - // see comment on places in - // propagate_through_place_components() - let succ = self.write_place(l, succ, ACC_WRITE); - let succ = self.propagate_through_place_components(l, succ); - self.propagate_through_expr(r, succ) - } - - hir::ExprKind::AssignOp(_, ref l, ref r) => { - // an overloaded assign op is like a method call - if self.typeck_results.is_method_call(expr) { - let succ = self.propagate_through_expr(l, succ); - self.propagate_through_expr(r, succ) - } else { - // see comment on places in - // propagate_through_place_components() - let succ = self.write_place(l, succ, ACC_WRITE | ACC_READ); - let succ = self.propagate_through_expr(r, succ); - self.propagate_through_place_components(l, succ) - } - } - - // Uninteresting cases: just propagate in rev exec order - hir::ExprKind::Array(exprs) => self.propagate_through_exprs(exprs, succ), - - hir::ExprKind::Struct(_, fields, ref with_expr) => { - let succ = match with_expr { - hir::StructTailExpr::Base(base) => { - self.propagate_through_opt_expr(Some(base), succ) - } - hir::StructTailExpr::None | hir::StructTailExpr::DefaultFields(_) => succ, - }; - fields - .iter() - .rev() - .fold(succ, |succ, field| self.propagate_through_expr(field.expr, succ)) - } - - hir::ExprKind::Call(ref f, args) => { - let is_ctor = |f: &Expr<'_>| matches!(f.kind, hir::ExprKind::Path(hir::QPath::Resolved(_, path)) if matches!(path.res, rustc_hir::def::Res::Def(rustc_hir::def::DefKind::Ctor(_, _), _))); - let succ = - if !is_ctor(f) { self.check_is_ty_uninhabited(expr, succ) } else { succ }; - - let succ = self.propagate_through_exprs(args, succ); - self.propagate_through_expr(f, succ) - } - - hir::ExprKind::MethodCall(.., receiver, args, _) => { - let succ = self.check_is_ty_uninhabited(expr, succ); - let succ = self.propagate_through_exprs(args, succ); - self.propagate_through_expr(receiver, succ) - } - - hir::ExprKind::Use(expr, _) => { - let succ = self.check_is_ty_uninhabited(expr, succ); - self.propagate_through_expr(expr, succ) - } - - hir::ExprKind::Tup(exprs) => self.propagate_through_exprs(exprs, succ), - - hir::ExprKind::Binary(op, ref l, ref r) if op.node.is_lazy() => { - let r_succ = self.propagate_through_expr(r, succ); - - let ln = self.live_node(expr.hir_id, expr.span); - self.init_from_succ(ln, succ); - self.merge_from_succ(ln, r_succ); - - self.propagate_through_expr(l, ln) - } - - hir::ExprKind::Index(ref l, ref r, _) | hir::ExprKind::Binary(_, ref l, ref r) => { - let r_succ = self.propagate_through_expr(r, succ); - self.propagate_through_expr(l, r_succ) - } - - hir::ExprKind::AddrOf(_, _, ref e) - | hir::ExprKind::Cast(ref e, _) - | hir::ExprKind::Type(ref e, _) - | hir::ExprKind::UnsafeBinderCast(_, ref e, _) - | hir::ExprKind::DropTemps(ref e) - | hir::ExprKind::Unary(_, ref e) - | hir::ExprKind::Repeat(ref e, _) => self.propagate_through_expr(e, succ), - - hir::ExprKind::InlineAsm(asm) => { - // - // (inputs) - // | - // v - // (outputs) - // / \ - // | | - // v v - // (labels)(fallthrough) - // | | - // v v - // ( succ / exit_ln ) - - // Handle non-returning asm - let mut succ = - if self.typeck_results.expr_ty(expr).is_never() { self.exit_ln } else { succ }; - - // Do a first pass for labels only - if asm.contains_label() { - let ln = self.live_node(expr.hir_id, expr.span); - self.init_from_succ(ln, succ); - for (op, _op_sp) in asm.operands.iter().rev() { - match op { - hir::InlineAsmOperand::Label { block } => { - let label_ln = self.propagate_through_block(block, succ); - self.merge_from_succ(ln, label_ln); - } - hir::InlineAsmOperand::In { .. } - | hir::InlineAsmOperand::Out { .. } - | hir::InlineAsmOperand::InOut { .. } - | hir::InlineAsmOperand::SplitInOut { .. } - | hir::InlineAsmOperand::Const { .. } - | hir::InlineAsmOperand::SymFn { .. } - | hir::InlineAsmOperand::SymStatic { .. } => {} - } - } - succ = ln; - } - - // Do a second pass for writing outputs only - for (op, _op_sp) in asm.operands.iter().rev() { - match op { - hir::InlineAsmOperand::In { .. } - | hir::InlineAsmOperand::Const { .. } - | hir::InlineAsmOperand::SymFn { .. } - | hir::InlineAsmOperand::SymStatic { .. } - | hir::InlineAsmOperand::Label { .. } => {} - hir::InlineAsmOperand::Out { expr, .. } => { - if let Some(expr) = expr { - succ = self.write_place(expr, succ, ACC_WRITE); - } - } - hir::InlineAsmOperand::InOut { expr, .. } => { - succ = self.write_place(expr, succ, ACC_READ | ACC_WRITE | ACC_USE); - } - hir::InlineAsmOperand::SplitInOut { out_expr, .. } => { - if let Some(expr) = out_expr { - succ = self.write_place(expr, succ, ACC_WRITE); - } - } - } - } - - // Then do a third pass for inputs - for (op, _op_sp) in asm.operands.iter().rev() { - match op { - hir::InlineAsmOperand::In { expr, .. } => { - succ = self.propagate_through_expr(expr, succ) - } - hir::InlineAsmOperand::Out { expr, .. } => { - if let Some(expr) = expr { - succ = self.propagate_through_place_components(expr, succ); - } - } - hir::InlineAsmOperand::InOut { expr, .. } => { - succ = self.propagate_through_place_components(expr, succ); - } - hir::InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => { - if let Some(expr) = out_expr { - succ = self.propagate_through_place_components(expr, succ); - } - succ = self.propagate_through_expr(in_expr, succ); - } - hir::InlineAsmOperand::Const { .. } - | hir::InlineAsmOperand::SymFn { .. } - | hir::InlineAsmOperand::SymStatic { .. } - | hir::InlineAsmOperand::Label { .. } => {} - } - } - succ - } - - hir::ExprKind::Lit(..) - | hir::ExprKind::ConstBlock(..) - | hir::ExprKind::Err(_) - | hir::ExprKind::Path(hir::QPath::TypeRelative(..)) - | hir::ExprKind::Path(hir::QPath::LangItem(..)) - | hir::ExprKind::OffsetOf(..) => succ, - - // Note that labels have been resolved, so we don't need to look - // at the label ident - hir::ExprKind::Block(ref blk, _) => self.propagate_through_block(blk, succ), - } - } - - fn propagate_through_place_components(&mut self, expr: &Expr<'_>, succ: LiveNode) -> LiveNode { - // # Places - // - // In general, the full flow graph structure for an - // assignment/move/etc can be handled in one of two ways, - // depending on whether what is being assigned is a "tracked - // value" or not. A tracked value is basically a local - // variable or argument. - // - // The two kinds of graphs are: - // - // Tracked place Untracked place - // ----------------------++----------------------- - // || - // | || | - // v || v - // (rvalue) || (rvalue) - // | || | - // v || v - // (write of place) || (place components) - // | || | - // v || v - // (succ) || (succ) - // || - // ----------------------++----------------------- - // - // I will cover the two cases in turn: - // - // # Tracked places - // - // A tracked place is a local variable/argument `x`. In - // these cases, the link_node where the write occurs is linked - // to node id of `x`. The `write_place()` routine generates - // the contents of this node. There are no subcomponents to - // consider. - // - // # Non-tracked places - // - // These are places like `x[5]` or `x.f`. In that case, we - // basically ignore the value which is written to but generate - // reads for the components---`x` in these two examples. The - // components reads are generated by - // `propagate_through_place_components()` (this fn). - // - // # Illegal places - // - // It is still possible to observe assignments to non-places; - // these errors are detected in the later pass borrowck. We - // just ignore such cases and treat them as reads. - - match expr.kind { - hir::ExprKind::Path(_) => succ, - hir::ExprKind::Field(ref e, _) => self.propagate_through_expr(e, succ), - _ => self.propagate_through_expr(expr, succ), - } - } - - // see comment on propagate_through_place() - fn write_place(&mut self, expr: &Expr<'_>, succ: LiveNode, acc: u32) -> LiveNode { - match expr.kind { - hir::ExprKind::Path(hir::QPath::Resolved(_, path)) => { - self.access_path(expr.hir_id, path, succ, acc) - } - - // We do not track other places, so just propagate through - // to their subcomponents. Also, it may happen that - // non-places occur here, because those are detected in the - // later pass borrowck. - _ => succ, - } - } - - fn access_var( - &mut self, - hir_id: HirId, - var_hid: HirId, - succ: LiveNode, - acc: u32, - span: Span, - ) -> LiveNode { - let ln = self.live_node(hir_id, span); - if acc != 0 { - self.init_from_succ(ln, succ); - let var = self.variable(var_hid, span); - self.acc(ln, var, acc); - } - ln - } - - fn access_path( - &mut self, - hir_id: HirId, - path: &hir::Path<'_>, - succ: LiveNode, - acc: u32, - ) -> LiveNode { - match path.res { - Res::Local(hid) => self.access_var(hir_id, hid, succ, acc, path.span), - _ => succ, - } - } - - fn propagate_through_loop( - &mut self, - expr: &Expr<'_>, - body: &hir::Block<'_>, - succ: LiveNode, - ) -> LiveNode { - /* - We model control flow like this: - - (expr) <-+ - | | - v | - (body) --+ - - Note that a `continue` expression targeting the `loop` will have a successor of `expr`. - Meanwhile, a `break` expression will have a successor of `succ`. - */ - - // first iteration: - let ln = self.live_node(expr.hir_id, expr.span); - self.init_empty(ln, succ); - debug!("propagate_through_loop: using id for loop body {} {:?}", expr.hir_id, body); - - self.break_ln.insert(expr.hir_id, succ); - - self.cont_ln.insert(expr.hir_id, ln); - - let body_ln = self.propagate_through_block(body, ln); - - // repeat until fixed point is reached: - while self.merge_from_succ(ln, body_ln) { - assert_eq!(body_ln, self.propagate_through_block(body, ln)); - } - - ln - } - - fn check_is_ty_uninhabited(&mut self, expr: &Expr<'_>, succ: LiveNode) -> LiveNode { - let ty = self.typeck_results.expr_ty(expr); - let m = self.ir.tcx.parent_module(expr.hir_id).to_def_id(); - if ty.is_inhabited_from(self.ir.tcx, m, self.typing_env) { succ } else { self.exit_ln } - } -} - -// _______________________________________________________________________ -// Checking for error conditions - -impl<'a, 'tcx> Visitor<'tcx> for Liveness<'a, 'tcx> { - fn visit_local(&mut self, local: &'tcx hir::LetStmt<'tcx>) { - self.check_unused_vars_in_pat(local.pat, None, None, |spans, hir_id, ln, var| { - if local.init.is_some() { - self.warn_about_dead_assign(spans, hir_id, ln, var, None); - } - }); - - intravisit::walk_local(self, local); - } - - fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) { - check_expr(self, ex); - intravisit::walk_expr(self, ex); - } - - fn visit_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) { - self.check_unused_vars_in_pat(arm.pat, None, None, |_, _, _, _| {}); - intravisit::walk_arm(self, arm); - } -} - -fn check_expr<'tcx>(this: &mut Liveness<'_, 'tcx>, expr: &'tcx Expr<'tcx>) { - match expr.kind { - hir::ExprKind::Assign(ref l, ..) => { - this.check_place(l); - } - - hir::ExprKind::AssignOp(_, ref l, _) => { - if !this.typeck_results.is_method_call(expr) { - this.check_place(l); - } - } - - hir::ExprKind::InlineAsm(asm) => { - for (op, _op_sp) in asm.operands { - match op { - hir::InlineAsmOperand::Out { expr, .. } => { - if let Some(expr) = expr { - this.check_place(expr); - } - } - hir::InlineAsmOperand::InOut { expr, .. } => { - this.check_place(expr); - } - hir::InlineAsmOperand::SplitInOut { out_expr, .. } => { - if let Some(out_expr) = out_expr { - this.check_place(out_expr); - } - } - _ => {} - } - } - } - - hir::ExprKind::Let(let_expr) => { - this.check_unused_vars_in_pat(let_expr.pat, None, None, |_, _, _, _| {}); - } - - // no correctness conditions related to liveness - hir::ExprKind::Call(..) - | hir::ExprKind::MethodCall(..) - | hir::ExprKind::Use(..) - | hir::ExprKind::Match(..) - | hir::ExprKind::Loop(..) - | hir::ExprKind::Index(..) - | hir::ExprKind::Field(..) - | hir::ExprKind::Array(..) - | hir::ExprKind::Tup(..) - | hir::ExprKind::Binary(..) - | hir::ExprKind::Cast(..) - | hir::ExprKind::If(..) - | hir::ExprKind::DropTemps(..) - | hir::ExprKind::Unary(..) - | hir::ExprKind::Ret(..) - | hir::ExprKind::Become(..) - | hir::ExprKind::Break(..) - | hir::ExprKind::Continue(..) - | hir::ExprKind::Lit(_) - | hir::ExprKind::ConstBlock(..) - | hir::ExprKind::Block(..) - | hir::ExprKind::AddrOf(..) - | hir::ExprKind::OffsetOf(..) - | hir::ExprKind::Struct(..) - | hir::ExprKind::Repeat(..) - | hir::ExprKind::Closure { .. } - | hir::ExprKind::Path(_) - | hir::ExprKind::Yield(..) - | hir::ExprKind::Type(..) - | hir::ExprKind::UnsafeBinderCast(..) - | hir::ExprKind::Err(_) => {} - } -} - -impl<'tcx> Liveness<'_, 'tcx> { - fn check_place(&mut self, expr: &'tcx Expr<'tcx>) { - match expr.kind { - hir::ExprKind::Path(hir::QPath::Resolved(_, path)) => { - if let Res::Local(var_hid) = path.res { - // Assignment to an immutable variable or argument: only legal - // if there is no later assignment. If this local is actually - // mutable, then check for a reassignment to flag the mutability - // as being used. - let ln = self.live_node(expr.hir_id, expr.span); - let var = self.variable(var_hid, expr.span); - let sugg = self.annotate_mut_binding_to_immutable_binding(var_hid, expr); - self.warn_about_dead_assign(vec![expr.span], expr.hir_id, ln, var, sugg); - } - } - _ => { - // For other kinds of places, no checks are required, - // and any embedded expressions are actually rvalues - intravisit::walk_expr(self, expr); - } - } - } - - fn should_warn(&self, var: Variable) -> Option { - let name = self.ir.variable_name(var); - let name = name.as_str(); - if name.as_bytes()[0] == b'_' { - return None; - } - Some(name.to_owned()) - } - - fn warn_about_unused_upvars(&self, entry_ln: LiveNode) { - let Some(closure_min_captures) = self.closure_min_captures else { - return; - }; - - // If closure_min_captures is Some(), upvars must be Some() too. - for (&var_hir_id, min_capture_list) in closure_min_captures { - for captured_place in min_capture_list { - match captured_place.info.capture_kind { - ty::UpvarCapture::ByValue | ty::UpvarCapture::ByUse => {} - ty::UpvarCapture::ByRef(..) => continue, - }; - let span = captured_place.get_capture_kind_span(self.ir.tcx); - let var = self.variable(var_hir_id, span); - if self.used_on_entry(entry_ln, var) { - if !self.live_on_entry(entry_ln, var) { - if let Some(name) = self.should_warn(var) { - self.ir.tcx.emit_node_span_lint( - lint::builtin::UNUSED_ASSIGNMENTS, - var_hir_id, - vec![span], - errors::UnusedCaptureMaybeCaptureRef { name }, - ); - } - } - } else if let Some(name) = self.should_warn(var) { - self.ir.tcx.emit_node_span_lint( - lint::builtin::UNUSED_VARIABLES, - var_hir_id, - vec![span], - errors::UnusedVarMaybeCaptureRef { name }, - ); - } - } - } - } - - fn warn_about_unused_args(&self, body: &hir::Body<'_>, entry_ln: LiveNode) { - if let Some(intrinsic) = self.ir.tcx.intrinsic(self.ir.tcx.hir_body_owner_def_id(body.id())) - { - if intrinsic.must_be_overridden { - return; - } - } - - for p in body.params { - self.check_unused_vars_in_pat( - p.pat, - Some(entry_ln), - Some(body), - |spans, hir_id, ln, var| { - if !self.live_on_entry(ln, var) - && let Some(name) = self.should_warn(var) - { - self.ir.tcx.emit_node_span_lint( - lint::builtin::UNUSED_ASSIGNMENTS, - hir_id, - spans, - errors::UnusedAssignPassed { name }, - ); - } - }, - ); - } - } - - fn check_unused_vars_in_pat( - &self, - pat: &hir::Pat<'_>, - entry_ln: Option, - opt_body: Option<&hir::Body<'_>>, - on_used_on_entry: impl Fn(Vec, HirId, LiveNode, Variable), - ) { - // In an or-pattern, only consider the variable; any later patterns must have the same - // bindings, and we also consider the first pattern to be the "authoritative" set of ids. - // However, we should take the ids and spans of variables with the same name from the later - // patterns so the suggestions to prefix with underscores will apply to those too. - let mut vars: FxIndexMap)> = - <_>::default(); - - pat.each_binding(|_, hir_id, pat_sp, ident| { - let ln = entry_ln.unwrap_or_else(|| self.live_node(hir_id, pat_sp)); - let var = self.variable(hir_id, ident.span); - let id_and_sp = (hir_id, pat_sp, ident.span); - vars.entry(self.ir.variable_name(var)) - .and_modify(|(.., hir_ids_and_spans)| hir_ids_and_spans.push(id_and_sp)) - .or_insert_with(|| (ln, var, vec![id_and_sp])); - }); - - let can_remove = match pat.kind { - hir::PatKind::Struct(_, fields, true) => { - // if all fields are shorthand, remove the struct field, otherwise, mark with _ as prefix - fields.iter().all(|f| f.is_shorthand) - } - _ => false, - }; - - for (_, (ln, var, hir_ids_and_spans)) in vars { - if self.used_on_entry(ln, var) { - let id = hir_ids_and_spans[0].0; - let spans = - hir_ids_and_spans.into_iter().map(|(_, _, ident_span)| ident_span).collect(); - on_used_on_entry(spans, id, ln, var); - } else { - self.report_unused(hir_ids_and_spans, ln, var, can_remove, pat, opt_body); - } - } - } - - /// Detect the following case - /// - /// ```text - /// fn change_object(mut a: &Ty) { - /// let a = Ty::new(); - /// b = &a; - /// } - /// ``` - /// - /// where the user likely meant to modify the value behind there reference, use `a` as an out - /// parameter, instead of mutating the local binding. When encountering this we suggest: - /// - /// ```text - /// fn change_object(a: &'_ mut Ty) { - /// let a = Ty::new(); - /// *b = a; - /// } - /// ``` - fn annotate_mut_binding_to_immutable_binding( - &self, - var_hid: HirId, - expr: &'tcx Expr<'tcx>, - ) -> Option { - if let hir::Node::Expr(parent) = self.ir.tcx.parent_hir_node(expr.hir_id) - && let hir::ExprKind::Assign(_, rhs, _) = parent.kind - && let hir::ExprKind::AddrOf(borrow_kind, _mut, inner) = rhs.kind - && let hir::BorrowKind::Ref = borrow_kind - && let hir::Node::Pat(pat) = self.ir.tcx.hir_node(var_hid) - && let hir::Node::Param(hir::Param { ty_span, .. }) = - self.ir.tcx.parent_hir_node(pat.hir_id) - && let item_id = self.ir.tcx.hir_get_parent_item(pat.hir_id) - && let item = self.ir.tcx.hir_owner_node(item_id) - && let Some(fn_decl) = item.fn_decl() - && let hir::PatKind::Binding(hir::BindingMode::MUT, _hir_id, ident, _) = pat.kind - && let Some((lt, mut_ty)) = fn_decl - .inputs - .iter() - .filter_map(|ty| { - if ty.span == *ty_span - && let hir::TyKind::Ref(lt, mut_ty) = ty.kind - { - Some((lt, mut_ty)) - } else { - None - } - }) - .next() - { - let ty_span = if mut_ty.mutbl.is_mut() { - // Leave `&'name mut Ty` and `&mut Ty` as they are (#136028). - None - } else { - // `&'name Ty` -> `&'name mut Ty` or `&Ty` -> `&mut Ty` - Some(mut_ty.ty.span.shrink_to_lo()) - }; - let pre = if lt.ident.span.is_empty() { "" } else { " " }; - Some(errors::UnusedAssignSuggestion { - ty_span, - pre, - ty_ref_span: pat.span.until(ident.span), - ident_span: expr.span.shrink_to_lo(), - expr_ref_span: rhs.span.until(inner.span), - }) - } else { - None - } - } - - #[instrument(skip(self), level = "INFO")] - fn report_unused( - &self, - hir_ids_and_spans: Vec<(HirId, Span, Span)>, - ln: LiveNode, - var: Variable, - can_remove: bool, - pat: &hir::Pat<'_>, - opt_body: Option<&hir::Body<'_>>, - ) { - let first_hir_id = hir_ids_and_spans[0].0; - if let Some(name) = self.should_warn(var).filter(|name| name != "self") { - // annoying: for parameters in funcs like `fn(x: i32) - // {ret}`, there is only one node, so asking about - // assigned_on_exit() is not meaningful. - let is_assigned = - if ln == self.exit_ln { false } else { self.assigned_on_exit(ln, var) }; - - if is_assigned { - self.ir.tcx.emit_node_span_lint( - lint::builtin::UNUSED_VARIABLES, - first_hir_id, - hir_ids_and_spans - .into_iter() - .map(|(_, _, ident_span)| ident_span) - .collect::>(), - errors::UnusedVarAssignedOnly { name }, - ) - } else if can_remove { - let spans = hir_ids_and_spans - .iter() - .map(|(_, pat_span, _)| { - let span = self - .ir - .tcx - .sess - .source_map() - .span_extend_to_next_char(*pat_span, ',', true); - span.with_hi(BytePos(span.hi().0 + 1)) - }) - .collect(); - self.ir.tcx.emit_node_span_lint( - lint::builtin::UNUSED_VARIABLES, - first_hir_id, - hir_ids_and_spans.iter().map(|(_, pat_span, _)| *pat_span).collect::>(), - errors::UnusedVarRemoveField { - name, - sugg: errors::UnusedVarRemoveFieldSugg { spans }, - }, - ); - } else { - let (shorthands, non_shorthands): (Vec<_>, Vec<_>) = - hir_ids_and_spans.iter().copied().partition(|(hir_id, _, ident_span)| { - let var = self.variable(*hir_id, *ident_span); - self.ir.variable_is_shorthand(var) - }); - - // If we have both shorthand and non-shorthand, prefer the "try ignoring - // the field" message, and suggest `_` for the non-shorthands. If we only - // have non-shorthand, then prefix with an underscore instead. - if !shorthands.is_empty() { - let shorthands = - shorthands.into_iter().map(|(_, pat_span, _)| pat_span).collect(); - let non_shorthands = - non_shorthands.into_iter().map(|(_, pat_span, _)| pat_span).collect(); - - self.ir.tcx.emit_node_span_lint( - lint::builtin::UNUSED_VARIABLES, - first_hir_id, - hir_ids_and_spans - .iter() - .map(|(_, pat_span, _)| *pat_span) - .collect::>(), - errors::UnusedVarTryIgnore { - sugg: errors::UnusedVarTryIgnoreSugg { - shorthands, - non_shorthands, - name, - }, - }, - ); - } else { - // #117284, when `pat_span` and `ident_span` have different contexts - // we can't provide a good suggestion, instead we pointed out the spans from macro - let from_macro = non_shorthands - .iter() - .find(|(_, pat_span, ident_span)| { - !pat_span.eq_ctxt(*ident_span) && pat_span.from_expansion() - }) - .map(|(_, pat_span, _)| *pat_span); - let non_shorthands = non_shorthands - .into_iter() - .map(|(_, _, ident_span)| ident_span) - .collect::>(); - - let suggestions = self.string_interp_suggestions(&name, opt_body); - let sugg = if let Some(span) = from_macro { - errors::UnusedVariableSugg::NoSugg { span, name: name.clone() } - } else { - errors::UnusedVariableSugg::TryPrefixSugg { - spans: non_shorthands, - name: name.clone(), - } - }; - - self.ir.tcx.emit_node_span_lint( - lint::builtin::UNUSED_VARIABLES, - first_hir_id, - hir_ids_and_spans - .iter() - .map(|(_, _, ident_span)| *ident_span) - .collect::>(), - errors::UnusedVariableTryPrefix { - label: if !suggestions.is_empty() { Some(pat.span) } else { None }, - name, - sugg, - string_interp: suggestions, - }, - ); - } - } - } - } - - fn string_interp_suggestions( - &self, - name: &str, - opt_body: Option<&hir::Body<'_>>, - ) -> Vec { - let mut suggs = Vec::new(); - let Some(opt_body) = opt_body else { - return suggs; - }; - let mut visitor = CollectLitsVisitor { lit_exprs: vec![] }; - intravisit::walk_body(&mut visitor, opt_body); - for lit_expr in visitor.lit_exprs { - let hir::ExprKind::Lit(litx) = &lit_expr.kind else { continue }; - let rustc_ast::LitKind::Str(syb, _) = litx.node else { - continue; - }; - let name_str: &str = syb.as_str(); - let name_pa = format!("{{{name}}}"); - if name_str.contains(&name_pa) { - suggs.push(errors::UnusedVariableStringInterp { - lit: lit_expr.span, - lo: lit_expr.span.shrink_to_lo(), - hi: lit_expr.span.shrink_to_hi(), - }); - } - } - suggs - } - - fn warn_about_dead_assign( - &self, - spans: Vec, - hir_id: HirId, - ln: LiveNode, - var: Variable, - suggestion: Option, - ) { - if !self.live_on_exit(ln, var) - && let Some(name) = self.should_warn(var) - { - let help = suggestion.is_none(); - self.ir.tcx.emit_node_span_lint( - lint::builtin::UNUSED_ASSIGNMENTS, - hir_id, - spans, - errors::UnusedAssign { name, suggestion, help }, - ); - } - } -} diff --git a/compiler/rustc_passes/src/liveness/rwu_table.rs b/compiler/rustc_passes/src/liveness/rwu_table.rs deleted file mode 100644 index 4c1f6ea141e66..0000000000000 --- a/compiler/rustc_passes/src/liveness/rwu_table.rs +++ /dev/null @@ -1,146 +0,0 @@ -use std::iter; - -use crate::liveness::{LiveNode, Variable}; - -#[derive(Clone, Copy)] -pub(super) struct RWU { - pub(super) reader: bool, - pub(super) writer: bool, - pub(super) used: bool, -} - -/// Conceptually, this is like a `Vec>`. But the number of -/// RWU's can get very large, so it uses a more compact representation. -pub(super) struct RWUTable { - /// Total number of live nodes. - live_nodes: usize, - /// Total number of variables. - vars: usize, - - /// A compressed representation of `RWU`s. - /// - /// Each word represents 2 different `RWU`s packed together. Each packed RWU - /// is stored in 4 bits: a reader bit, a writer bit, a used bit and a - /// padding bit. - /// - /// The data for each live node is contiguous and starts at a word boundary, - /// so there might be an unused space left. - words: Vec, - /// Number of words per each live node. - live_node_words: usize, -} - -impl RWUTable { - const RWU_READER: u8 = 0b0001; - const RWU_WRITER: u8 = 0b0010; - const RWU_USED: u8 = 0b0100; - const RWU_MASK: u8 = 0b1111; - - /// Size of packed RWU in bits. - const RWU_BITS: usize = 4; - /// Size of a word in bits. - const WORD_BITS: usize = size_of::() * 8; - /// Number of packed RWUs that fit into a single word. - const WORD_RWU_COUNT: usize = Self::WORD_BITS / Self::RWU_BITS; - - pub(super) fn new(live_nodes: usize, vars: usize) -> RWUTable { - let live_node_words = (vars + Self::WORD_RWU_COUNT - 1) / Self::WORD_RWU_COUNT; - Self { live_nodes, vars, live_node_words, words: vec![0u8; live_node_words * live_nodes] } - } - - fn word_and_shift(&self, ln: LiveNode, var: Variable) -> (usize, u32) { - assert!(ln.index() < self.live_nodes); - assert!(var.index() < self.vars); - - let var = var.index(); - let word = var / Self::WORD_RWU_COUNT; - let shift = Self::RWU_BITS * (var % Self::WORD_RWU_COUNT); - (ln.index() * self.live_node_words + word, shift as u32) - } - - fn pick2_rows_mut(&mut self, a: LiveNode, b: LiveNode) -> (&mut [u8], &mut [u8]) { - assert!(a.index() < self.live_nodes); - assert!(b.index() < self.live_nodes); - assert!(a != b); - - let a_start = a.index() * self.live_node_words; - let b_start = b.index() * self.live_node_words; - - unsafe { - let ptr = self.words.as_mut_ptr(); - ( - std::slice::from_raw_parts_mut(ptr.add(a_start), self.live_node_words), - std::slice::from_raw_parts_mut(ptr.add(b_start), self.live_node_words), - ) - } - } - - pub(super) fn copy(&mut self, dst: LiveNode, src: LiveNode) { - if dst == src { - return; - } - - let (dst_row, src_row) = self.pick2_rows_mut(dst, src); - dst_row.copy_from_slice(src_row); - } - - /// Sets `dst` to the union of `dst` and `src`, returns true if `dst` was - /// changed. - pub(super) fn union(&mut self, dst: LiveNode, src: LiveNode) -> bool { - if dst == src { - return false; - } - - let mut changed = false; - let (dst_row, src_row) = self.pick2_rows_mut(dst, src); - for (dst_word, src_word) in iter::zip(dst_row, &*src_row) { - let old = *dst_word; - let new = *dst_word | src_word; - *dst_word = new; - changed |= old != new; - } - changed - } - - pub(super) fn get_reader(&self, ln: LiveNode, var: Variable) -> bool { - let (word, shift) = self.word_and_shift(ln, var); - (self.words[word] >> shift) & Self::RWU_READER != 0 - } - - pub(super) fn get_writer(&self, ln: LiveNode, var: Variable) -> bool { - let (word, shift) = self.word_and_shift(ln, var); - (self.words[word] >> shift) & Self::RWU_WRITER != 0 - } - - pub(super) fn get_used(&self, ln: LiveNode, var: Variable) -> bool { - let (word, shift) = self.word_and_shift(ln, var); - (self.words[word] >> shift) & Self::RWU_USED != 0 - } - - pub(super) fn get(&self, ln: LiveNode, var: Variable) -> RWU { - let (word, shift) = self.word_and_shift(ln, var); - let rwu_packed = self.words[word] >> shift; - RWU { - reader: rwu_packed & Self::RWU_READER != 0, - writer: rwu_packed & Self::RWU_WRITER != 0, - used: rwu_packed & Self::RWU_USED != 0, - } - } - - pub(super) fn set(&mut self, ln: LiveNode, var: Variable, rwu: RWU) { - let mut packed = 0; - if rwu.reader { - packed |= Self::RWU_READER; - } - if rwu.writer { - packed |= Self::RWU_WRITER; - } - if rwu.used { - packed |= Self::RWU_USED; - } - - let (word, shift) = self.word_and_shift(ln, var); - let word = &mut self.words[word]; - *word = (*word & !(Self::RWU_MASK << shift)) | (packed << shift) - } -} diff --git a/src/tools/clippy/tests/ui/needless_match.fixed b/src/tools/clippy/tests/ui/needless_match.fixed index 41acf44023f63..57273012a192b 100644 --- a/src/tools/clippy/tests/ui/needless_match.fixed +++ b/src/tools/clippy/tests/ui/needless_match.fixed @@ -1,7 +1,7 @@ #![warn(clippy::needless_match)] #![allow(clippy::manual_map)] #![allow(dead_code)] - +#![allow(unused)] #[derive(Clone, Copy)] enum Simple { A, diff --git a/src/tools/clippy/tests/ui/needless_match.rs b/src/tools/clippy/tests/ui/needless_match.rs index 936653b961bbb..3eb577868f2b4 100644 --- a/src/tools/clippy/tests/ui/needless_match.rs +++ b/src/tools/clippy/tests/ui/needless_match.rs @@ -1,7 +1,7 @@ #![warn(clippy::needless_match)] #![allow(clippy::manual_map)] #![allow(dead_code)] - +#![allow(unused)] #[derive(Clone, Copy)] enum Simple { A, diff --git a/src/tools/clippy/tests/ui/useless_conversion.fixed b/src/tools/clippy/tests/ui/useless_conversion.fixed index ad30c94f34781..2942f64741e91 100644 --- a/src/tools/clippy/tests/ui/useless_conversion.fixed +++ b/src/tools/clippy/tests/ui/useless_conversion.fixed @@ -1,5 +1,5 @@ #![deny(clippy::useless_conversion)] -#![allow(clippy::needless_if, clippy::unnecessary_wraps)] +#![allow(clippy::needless_if, clippy::unnecessary_wraps, unused)] // FIXME(static_mut_refs): Do not allow `static_mut_refs` lint #![allow(static_mut_refs)] diff --git a/src/tools/clippy/tests/ui/useless_conversion.rs b/src/tools/clippy/tests/ui/useless_conversion.rs index 505afb340009a..f2da414e9f652 100644 --- a/src/tools/clippy/tests/ui/useless_conversion.rs +++ b/src/tools/clippy/tests/ui/useless_conversion.rs @@ -1,5 +1,5 @@ #![deny(clippy::useless_conversion)] -#![allow(clippy::needless_if, clippy::unnecessary_wraps)] +#![allow(clippy::needless_if, clippy::unnecessary_wraps, unused)] // FIXME(static_mut_refs): Do not allow `static_mut_refs` lint #![allow(static_mut_refs)] diff --git a/src/tools/tidy/src/ui_tests.rs b/src/tools/tidy/src/ui_tests.rs index 8f9b07c49acbe..44dd1e50f5b04 100644 --- a/src/tools/tidy/src/ui_tests.rs +++ b/src/tools/tidy/src/ui_tests.rs @@ -17,7 +17,7 @@ use ignore::Walk; const ENTRY_LIMIT: u32 = 901; // FIXME: The following limits should be reduced eventually. -const ISSUES_ENTRY_LIMIT: u32 = 1623; +const ISSUES_ENTRY_LIMIT: u32 = 1624; const EXPECTED_TEST_FILE_EXTENSIONS: &[&str] = &[ "rs", // test source files diff --git a/tests/ui/asm/x86_64/goto.rs b/tests/ui/asm/x86_64/goto.rs index 00a8e588f9673..65bdd7b3e0500 100644 --- a/tests/ui/asm/x86_64/goto.rs +++ b/tests/ui/asm/x86_64/goto.rs @@ -68,6 +68,7 @@ fn goto_out_jump() { fn goto_out_jump_noreturn() { unsafe { let mut value = false; + //~^ WARN value assigned to `value` is never read let mut out: usize; asm!( "lea {}, [{} + 1]", diff --git a/tests/ui/asm/x86_64/goto.stderr b/tests/ui/asm/x86_64/goto.stderr index 78b726b3f3d31..230faf8001136 100644 --- a/tests/ui/asm/x86_64/goto.stderr +++ b/tests/ui/asm/x86_64/goto.stderr @@ -1,5 +1,5 @@ warning: unreachable statement - --> $DIR/goto.rs:143:9 + --> $DIR/goto.rs:144:9 | LL | / asm!( LL | | "jmp {}", @@ -13,10 +13,19 @@ LL | unreachable!(); | ^^^^^^^^^^^^^^ unreachable statement | note: the lint level is defined here - --> $DIR/goto.rs:133:8 + --> $DIR/goto.rs:134:8 | LL | #[warn(unreachable_code)] | ^^^^^^^^^^^^^^^^ -warning: 1 warning emitted +warning: value assigned to `value` is never read + --> $DIR/goto.rs:70:25 + | +LL | let mut value = false; + | ^^^^^ + | + = help: maybe it is overwritten before being read? + = note: `#[warn(unused_assignments)]` on by default + +warning: 2 warnings emitted diff --git a/tests/ui/async-await/async-closures/precise-captures.rs b/tests/ui/async-await/async-closures/precise-captures.rs index 638fb67c3a465..d654906e8d4a1 100644 --- a/tests/ui/async-await/async-closures/precise-captures.rs +++ b/tests/ui/async-await/async-closures/precise-captures.rs @@ -10,6 +10,7 @@ // in . #![allow(unused_mut)] +#![allow(unused_assignments)] extern crate block_on; diff --git a/tests/ui/borrowck/borrowck-assign-to-subfield.rs b/tests/ui/borrowck/borrowck-assign-to-subfield.rs index 27d9046e7b7cb..8e5192611ffd9 100644 --- a/tests/ui/borrowck/borrowck-assign-to-subfield.rs +++ b/tests/ui/borrowck/borrowck-assign-to-subfield.rs @@ -1,5 +1,6 @@ //@ run-pass +#[allow(unused)] pub fn main() { struct A { a: isize, diff --git a/tests/ui/closures/2229_closure_analysis/diagnostics/liveness.rs b/tests/ui/closures/2229_closure_analysis/diagnostics/liveness.rs index c550af21f0753..641066b002273 100644 --- a/tests/ui/closures/2229_closure_analysis/diagnostics/liveness.rs +++ b/tests/ui/closures/2229_closure_analysis/diagnostics/liveness.rs @@ -13,13 +13,11 @@ struct Point { pub fn f() { let mut a = 1; - let mut c = Point{ x:1, y:0 }; + let mut c = Point { x: 1, y: 0 }; // Captured by value, but variable is dead on entry. (move || { - // This will not trigger a warning for unused variable as - // c.x will be treated as a Non-tracked place - c.x = 1; + c.x = 1; //~ WARN value captured by `c.x` is never read println!("{}", c.x); a = 1; //~ WARN value captured by `a` is never read println!("{}", a); @@ -27,10 +25,10 @@ pub fn f() { // Read and written to, but never actually used. (move || { - // This will not trigger a warning for unused variable as - // c.x will be treated as a Non-tracked place - c.x += 1; - a += 1; //~ WARN unused variable: `a` + c.x += 1; //~ WARN value captured by `c.x` is never read + //~| WARN value assigned to `c.x` is never read + a += 1; //~ WARN value captured by `a` is never read + //~| WARN value assigned to `a` is never read })(); (move || { @@ -45,10 +43,7 @@ pub fn f() { let b = Box::new(42); (move || { println!("{}", c.x); - // Never read because this is FnOnce closure. - // This will not trigger a warning for unused variable as - // c.x will be treated as a Non-tracked place - c.x += 1; + c.x += 1; //~ WARN value assigned to `c.x` is never read println!("{}", a); a += 1; //~ WARN value assigned to `a` is never read drop(b); @@ -56,35 +51,32 @@ pub fn f() { } #[derive(Debug)] -struct MyStruct<'a> { - x: Option<& 'a str>, +struct MyStruct<'a> { + x: Option<&'a str>, y: i32, } pub fn nested() { - let mut a : Option<& str>; + let mut a: Option<&str>; a = None; - let mut b : Option<& str>; + let mut b: Option<&str>; b = None; - let mut d = MyStruct{ x: None, y: 1}; - let mut e = MyStruct{ x: None, y: 1}; + let mut d = MyStruct { x: None, y: 1 }; + let mut e = MyStruct { x: None, y: 1 }; (|| { (|| { - // This will not trigger a warning for unused variable as - // d.x will be treated as a Non-tracked place - d.x = Some("d1"); + d.x = Some("d1"); //~ WARN value assigned to `d.x` is never read d.x = Some("d2"); a = Some("d1"); //~ WARN value assigned to `a` is never read a = Some("d2"); })(); (move || { - // This will not trigger a warning for unused variable as - //e.x will be treated as a Non-tracked place - e.x = Some("e1"); - e.x = Some("e2"); - b = Some("e1"); //~ WARN value assigned to `b` is never read - //~| WARN unused variable: `b` - b = Some("e2"); //~ WARN value assigned to `b` is never read + e.x = Some("e1"); //~ WARN value captured by `e.x` is never read + //~| WARN value assigned to `e.x` is never read + e.x = Some("e2"); //~ WARN value assigned to `e.x` is never read + b = Some("e1"); //~ WARN value captured by `b` is never read + //~| WARN value assigned to `b` is never read + b = Some("e2"); //~ WARN value assigned to `b` is never read })(); })(); } diff --git a/tests/ui/closures/2229_closure_analysis/diagnostics/liveness.stderr b/tests/ui/closures/2229_closure_analysis/diagnostics/liveness.stderr index cf414adc0b943..f697662d3ee93 100644 --- a/tests/ui/closures/2229_closure_analysis/diagnostics/liveness.stderr +++ b/tests/ui/closures/2229_closure_analysis/diagnostics/liveness.stderr @@ -1,10 +1,10 @@ -warning: value captured by `a` is never read - --> $DIR/liveness.rs:24:9 +warning: value assigned to `c.x` is never read + --> $DIR/liveness.rs:46:9 | -LL | a = 1; - | ^ +LL | c.x += 1; + | ^^^^^^^^ | - = help: did you mean to capture by reference instead? + = help: maybe it is overwritten before being read? note: the lint level is defined here --> $DIR/liveness.rs:5:9 | @@ -12,54 +12,125 @@ LL | #![warn(unused)] | ^^^^^^ = note: `#[warn(unused_assignments)]` implied by `#[warn(unused)]` -warning: unused variable: `a` - --> $DIR/liveness.rs:33:9 +warning: value assigned to `a` is never read + --> $DIR/liveness.rs:48:9 + | +LL | a += 1; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +warning: value captured by `c.x` is never read + --> $DIR/liveness.rs:28:9 + | +LL | c.x += 1; + | ^^^ + | + = help: did you mean to capture by reference instead? + +warning: value assigned to `c.x` is never read + --> $DIR/liveness.rs:28:9 + | +LL | c.x += 1; + | ^^^^^^^^ + | + = help: maybe it is overwritten before being read? + +warning: value captured by `a` is never read + --> $DIR/liveness.rs:30:9 | LL | a += 1; | ^ | = help: did you mean to capture by reference instead? - = note: `#[warn(unused_variables)]` implied by `#[warn(unused)]` warning: value assigned to `a` is never read - --> $DIR/liveness.rs:53:9 + --> $DIR/liveness.rs:30:9 | LL | a += 1; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +warning: value captured by `c.x` is never read + --> $DIR/liveness.rs:20:9 + | +LL | c.x = 1; + | ^^^ + | + = help: did you mean to capture by reference instead? + +warning: value captured by `a` is never read + --> $DIR/liveness.rs:22:9 + | +LL | a = 1; | ^ | + = help: did you mean to capture by reference instead? + +warning: value captured by `e.x` is never read + --> $DIR/liveness.rs:74:13 + | +LL | e.x = Some("e1"); + | ^^^ + | + = help: did you mean to capture by reference instead? + +warning: value assigned to `e.x` is never read + --> $DIR/liveness.rs:74:13 + | +LL | e.x = Some("e1"); + | ^^^^^^^^^^^^^^^^ + | = help: maybe it is overwritten before being read? -warning: value assigned to `a` is never read +warning: value assigned to `e.x` is never read + --> $DIR/liveness.rs:76:13 + | +LL | e.x = Some("e2"); + | ^^^^^^^^^^^^^^^^ + | + = help: maybe it is overwritten before being read? + +warning: value captured by `b` is never read --> $DIR/liveness.rs:77:13 | -LL | a = Some("d1"); +LL | b = Some("e1"); | ^ | - = help: maybe it is overwritten before being read? + = help: did you mean to capture by reference instead? warning: value assigned to `b` is never read - --> $DIR/liveness.rs:85:13 + --> $DIR/liveness.rs:77:13 | LL | b = Some("e1"); - | ^ + | ^^^^^^^^^^^^^^ | = help: maybe it is overwritten before being read? warning: value assigned to `b` is never read - --> $DIR/liveness.rs:87:13 + --> $DIR/liveness.rs:79:13 | LL | b = Some("e2"); - | ^ + | ^^^^^^^^^^^^^^ | = help: maybe it is overwritten before being read? -warning: unused variable: `b` - --> $DIR/liveness.rs:85:13 +warning: value assigned to `d.x` is never read + --> $DIR/liveness.rs:68:13 | -LL | b = Some("e1"); - | ^ +LL | d.x = Some("d1"); + | ^^^^^^^^^^^^^^^^ | - = help: did you mean to capture by reference instead? + = help: maybe it is overwritten before being read? + +warning: value assigned to `a` is never read + --> $DIR/liveness.rs:70:13 + | +LL | a = Some("d1"); + | ^^^^^^^^^^^^^^ + | + = help: maybe it is overwritten before being read? -warning: 7 warnings emitted +warning: 16 warnings emitted diff --git a/tests/ui/closures/2229_closure_analysis/diagnostics/liveness_unintentional_copy.rs b/tests/ui/closures/2229_closure_analysis/diagnostics/liveness_unintentional_copy.rs index bcd4d1090a411..2d4451d988ebc 100644 --- a/tests/ui/closures/2229_closure_analysis/diagnostics/liveness_unintentional_copy.rs +++ b/tests/ui/closures/2229_closure_analysis/diagnostics/liveness_unintentional_copy.rs @@ -12,14 +12,16 @@ struct MyStruct { pub fn unintentional_copy_one() { let mut a = 1; - let mut last = MyStruct{ a: 1, b: 1}; + //~^ WARN unused variable: `a` + let mut last = MyStruct { a: 1, b: 1 }; + //~^ WARN unused variable: `last` let mut f = move |s| { - // This will not trigger a warning for unused variable - // as last.a will be treated as a Non-tracked place last.a = s; + //~^ WARN value captured by `last.a` is never read + //~| WARN value assigned to `last.a` is never read a = s; - //~^ WARN value assigned to `a` is never read - //~| WARN unused variable: `a` + //~^ WARN value captured by `a` is never read + //~| WARN value assigned to `a` is never read }; f(2); f(3); @@ -28,12 +30,16 @@ pub fn unintentional_copy_one() { pub fn unintentional_copy_two() { let mut a = 1; - let mut sum = MyStruct{ a: 1, b: 0}; + //~^ WARN unused variable: `a` + let mut sum = MyStruct { a: 1, b: 0 }; + //~^ WARN unused variable: `sum` (1..10).for_each(move |x| { - // This will not trigger a warning for unused variable - // as sum.b will be treated as a Non-tracked place sum.b += x; - a += x; //~ WARN unused variable: `a` + //~^ WARN value captured by `sum.b` is never read + //~| WARN value assigned to `sum.b` is never read + a += x; + //~^ WARN value captured by `a` is never read + //~| WARN value assigned to `a` is never read }); } diff --git a/tests/ui/closures/2229_closure_analysis/diagnostics/liveness_unintentional_copy.stderr b/tests/ui/closures/2229_closure_analysis/diagnostics/liveness_unintentional_copy.stderr index 0410de4c7982a..35b6d547eece0 100644 --- a/tests/ui/closures/2229_closure_analysis/diagnostics/liveness_unintentional_copy.stderr +++ b/tests/ui/closures/2229_closure_analysis/diagnostics/liveness_unintentional_copy.stderr @@ -1,10 +1,10 @@ -warning: value assigned to `a` is never read - --> $DIR/liveness_unintentional_copy.rs:20:9 +warning: value captured by `last.a` is never read + --> $DIR/liveness_unintentional_copy.rs:19:9 | -LL | a = s; - | ^ +LL | last.a = s; + | ^^^^^^ | - = help: maybe it is overwritten before being read? + = help: did you mean to capture by reference instead? note: the lint level is defined here --> $DIR/liveness_unintentional_copy.rs:4:9 | @@ -12,22 +12,87 @@ LL | #![warn(unused)] | ^^^^^^ = note: `#[warn(unused_assignments)]` implied by `#[warn(unused)]` -warning: unused variable: `a` - --> $DIR/liveness_unintentional_copy.rs:20:9 +warning: value assigned to `last.a` is never read + --> $DIR/liveness_unintentional_copy.rs:19:9 + | +LL | last.a = s; + | ^^^^^^^^^^ + | + = help: maybe it is overwritten before being read? + +warning: value captured by `a` is never read + --> $DIR/liveness_unintentional_copy.rs:22:9 | LL | a = s; | ^ | = help: did you mean to capture by reference instead? - = note: `#[warn(unused_variables)]` implied by `#[warn(unused)]` + +warning: value assigned to `a` is never read + --> $DIR/liveness_unintentional_copy.rs:22:9 + | +LL | a = s; + | ^^^^^ + | + = help: maybe it is overwritten before being read? warning: unused variable: `a` - --> $DIR/liveness_unintentional_copy.rs:36:9 + --> $DIR/liveness_unintentional_copy.rs:14:9 + | +LL | let mut a = 1; + | ^^^^^ help: if this is intentional, prefix it with an underscore: `_a` + | + = note: `#[warn(unused_variables)]` implied by `#[warn(unused)]` + +warning: unused variable: `last` + --> $DIR/liveness_unintentional_copy.rs:16:9 + | +LL | let mut last = MyStruct { a: 1, b: 1 }; + | ^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_last` + +warning: value captured by `sum.b` is never read + --> $DIR/liveness_unintentional_copy.rs:37:9 + | +LL | sum.b += x; + | ^^^^^ + | + = help: did you mean to capture by reference instead? + +warning: value assigned to `sum.b` is never read + --> $DIR/liveness_unintentional_copy.rs:37:9 + | +LL | sum.b += x; + | ^^^^^^^^^^ + | + = help: maybe it is overwritten before being read? + +warning: value captured by `a` is never read + --> $DIR/liveness_unintentional_copy.rs:40:9 | LL | a += x; | ^ | = help: did you mean to capture by reference instead? -warning: 3 warnings emitted +warning: value assigned to `a` is never read + --> $DIR/liveness_unintentional_copy.rs:40:9 + | +LL | a += x; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +warning: unused variable: `a` + --> $DIR/liveness_unintentional_copy.rs:32:9 + | +LL | let mut a = 1; + | ^^^^^ help: if this is intentional, prefix it with an underscore: `_a` + +warning: unused variable: `sum` + --> $DIR/liveness_unintentional_copy.rs:34:9 + | +LL | let mut sum = MyStruct { a: 1, b: 0 }; + | ^^^^^^^ help: if this is intentional, prefix it with an underscore: `_sum` + +warning: 12 warnings emitted diff --git a/tests/ui/closures/2229_closure_analysis/run_pass/destructure-pattern-closure-within-closure.stderr b/tests/ui/closures/2229_closure_analysis/run_pass/destructure-pattern-closure-within-closure.stderr index cf8bd7a0a2765..40274c88318c3 100644 --- a/tests/ui/closures/2229_closure_analysis/run_pass/destructure-pattern-closure-within-closure.stderr +++ b/tests/ui/closures/2229_closure_analysis/run_pass/destructure-pattern-closure-within-closure.stderr @@ -1,8 +1,8 @@ -warning: unused variable: `g2` - --> $DIR/destructure-pattern-closure-within-closure.rs:10:17 +warning: unused variable: `t2` + --> $DIR/destructure-pattern-closure-within-closure.rs:13:21 | -LL | let (_, g2) = g; - | ^^ help: if this is intentional, prefix it with an underscore: `_g2` +LL | let (_, t2) = t; + | ^^ help: if this is intentional, prefix it with an underscore: `_t2` | note: the lint level is defined here --> $DIR/destructure-pattern-closure-within-closure.rs:3:9 @@ -11,11 +11,11 @@ LL | #![warn(unused)] | ^^^^^^ = note: `#[warn(unused_variables)]` implied by `#[warn(unused)]` -warning: unused variable: `t2` - --> $DIR/destructure-pattern-closure-within-closure.rs:13:21 +warning: unused variable: `g2` + --> $DIR/destructure-pattern-closure-within-closure.rs:10:17 | -LL | let (_, t2) = t; - | ^^ help: if this is intentional, prefix it with an underscore: `_t2` +LL | let (_, g2) = g; + | ^^ help: if this is intentional, prefix it with an underscore: `_g2` warning: 2 warnings emitted diff --git a/tests/ui/const-generics/defaults/trait_object_lt_defaults.rs b/tests/ui/const-generics/defaults/trait_object_lt_defaults.rs index 39dd6cb031f79..da25c0146c543 100644 --- a/tests/ui/const-generics/defaults/trait_object_lt_defaults.rs +++ b/tests/ui/const-generics/defaults/trait_object_lt_defaults.rs @@ -1,6 +1,6 @@ //@ aux-build:trait_object_lt_defaults_lib.rs //@ run-pass -#![allow(dead_code)] +#![allow(dead_code, unused)] extern crate trait_object_lt_defaults_lib; // Tests that `A<'a, 3, dyn Test>` is short for `A<'a, 3, dyn Test + 'a>` diff --git a/tests/ui/drop/or-pattern-drop-order.rs b/tests/ui/drop/or-pattern-drop-order.rs index fdc28225c3591..313fa84d50f97 100644 --- a/tests/ui/drop/or-pattern-drop-order.rs +++ b/tests/ui/drop/or-pattern-drop-order.rs @@ -24,7 +24,7 @@ fn assert_drop_order(expected_drops: impl IntoIterator, f: impl Fn(& assert_eq!(order, correct_order); } -#[expect(unused_variables, unused_assignments, irrefutable_let_patterns)] +#[expect(unused_variables, irrefutable_let_patterns)] fn main() { // When bindings are declared with `let pat;`, they're visited in left-to-right order, using the // order given by the first occurrence of each variable. They're later dropped in reverse. diff --git a/tests/ui/dropck/dropck-empty-array.rs b/tests/ui/dropck/dropck-empty-array.rs index f3eca6aed8d5e..5df274d455420 100644 --- a/tests/ui/dropck/dropck-empty-array.rs +++ b/tests/ui/dropck/dropck-empty-array.rs @@ -1,6 +1,7 @@ //@ run-pass -#[allow(dead_code)] +#![allow(dead_code, unused_variables, unused_assignments)] + struct Struct<'s>(&'s str); impl<'s> Drop for Struct<'s> { diff --git a/tests/ui/fn/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.stderr b/tests/ui/fn/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.stderr index 0330853d922fe..5ff4dbe603085 100644 --- a/tests/ui/fn/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.stderr +++ b/tests/ui/fn/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.stderr @@ -14,11 +14,41 @@ LL | let object2 = Object; LL ~ *object = object2; | +error[E0597]: `object2` does not live long enough + --> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:14:13 + | +LL | fn change_object2(mut object: &Object) { + | - let's call the lifetime of this reference `'1` +LL | +LL | let object2 = Object; + | ------- binding `object2` declared here +LL | object = &object2; + | ---------^^^^^^^^ + | | | + | | borrowed value does not live long enough + | assignment requires that `object2` is borrowed for `'1` +... +LL | } + | - `object2` dropped here while still borrowed + +error: variable `object` is assigned to, but never used + --> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:11:19 + | +LL | fn change_object2(mut object: &Object) { + | ^^^^^^^^^^ + | + = note: consider using `_object` instead +note: the lint level is defined here + --> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:2:29 + | +LL | #![deny(unused_assignments, unused_variables)] + | ^^^^^^^^^^^^^^^^ + error: value assigned to `object` is never read --> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:14:4 | LL | object = &object2; - | ^^^^^^ + | ^^^^^^^^^^^^^^^^^ | note: the lint level is defined here --> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:2:9 @@ -33,41 +63,30 @@ LL | let object2 = Object; LL ~ *object = object2; | -error: variable `object` is assigned to, but never used - --> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:11:23 +error[E0596]: cannot borrow `object2` as mutable, as it is not declared as mutable + --> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:22:14 | -LL | fn change_object2(mut object: &Object) { - | ^^^^^^ +LL | object = &mut object2; + | ^^^^^^^^^^^^ cannot borrow as mutable | - = note: consider using `_object` instead -note: the lint level is defined here - --> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:2:29 +help: consider changing this to be mutable | -LL | #![deny(unused_assignments, unused_variables)] - | ^^^^^^^^^^^^^^^^ +LL | let mut object2 = Object; + | +++ -error[E0597]: `object2` does not live long enough - --> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:14:13 +error: variable `object` is assigned to, but never used + --> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:19:19 | -LL | fn change_object2(mut object: &Object) { - | - let's call the lifetime of this reference `'1` -LL | -LL | let object2 = Object; - | ------- binding `object2` declared here -LL | object = &object2; - | ---------^^^^^^^^ - | | | - | | borrowed value does not live long enough - | assignment requires that `object2` is borrowed for `'1` -... -LL | } - | - `object2` dropped here while still borrowed +LL | fn change_object3(mut object: &mut Object) { + | ^^^^^^^^^^ + | + = note: consider using `_object` instead error: value assigned to `object` is never read --> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:22:5 | LL | object = &mut object2; - | ^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^ | help: you might have meant to mutate the pointed at value being passed in, instead of changing the reference in the local binding | @@ -77,25 +96,6 @@ LL | let object2 = Object; LL ~ *object = object2; | -error: variable `object` is assigned to, but never used - --> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:19:23 - | -LL | fn change_object3(mut object: &mut Object) { - | ^^^^^^ - | - = note: consider using `_object` instead - -error[E0596]: cannot borrow `object2` as mutable, as it is not declared as mutable - --> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:22:14 - | -LL | object = &mut object2; - | ^^^^^^^^^^^^ cannot borrow as mutable - | -help: consider changing this to be mutable - | -LL | let mut object2 = Object; - | +++ - error: aborting due to 7 previous errors Some errors have detailed explanations: E0308, E0596, E0597. diff --git a/tests/ui/issues/issue-11958.rs b/tests/ui/issues/issue-11958.rs index 9185c5158af64..6291f5e4033a4 100644 --- a/tests/ui/issues/issue-11958.rs +++ b/tests/ui/issues/issue-11958.rs @@ -5,7 +5,8 @@ pub fn main() { let mut x = 1; + //~^ WARN unused variable: `x` let _thunk = Box::new(move|| { x = 2; }); - //~^ WARN value assigned to `x` is never read - //~| WARN unused variable: `x` + //~^ WARN value captured by `x` is never read + //~| WARN value assigned to `x` is never read } diff --git a/tests/ui/issues/issue-11958.stderr b/tests/ui/issues/issue-11958.stderr index 5dca4c2f01d56..f593057362477 100644 --- a/tests/ui/issues/issue-11958.stderr +++ b/tests/ui/issues/issue-11958.stderr @@ -1,20 +1,27 @@ -warning: value assigned to `x` is never read - --> $DIR/issue-11958.rs:8:36 +warning: value captured by `x` is never read + --> $DIR/issue-11958.rs:9:36 | LL | let _thunk = Box::new(move|| { x = 2; }); | ^ | - = help: maybe it is overwritten before being read? + = help: did you mean to capture by reference instead? = note: `#[warn(unused_assignments)]` on by default -warning: unused variable: `x` - --> $DIR/issue-11958.rs:8:36 +warning: value assigned to `x` is never read + --> $DIR/issue-11958.rs:9:36 | LL | let _thunk = Box::new(move|| { x = 2; }); - | ^ + | ^^^^^ + | + = help: maybe it is overwritten before being read? + +warning: unused variable: `x` + --> $DIR/issue-11958.rs:7:9 + | +LL | let mut x = 1; + | ^^^^^ help: if this is intentional, prefix it with an underscore: `_x` | - = help: did you mean to capture by reference instead? = note: `#[warn(unused_variables)]` on by default -warning: 2 warnings emitted +warning: 3 warnings emitted diff --git a/tests/ui/issues/issue-19367.rs b/tests/ui/issues/issue-19367.rs index ce8451dd2de13..1cd6c483375ab 100644 --- a/tests/ui/issues/issue-19367.rs +++ b/tests/ui/issues/issue-19367.rs @@ -1,4 +1,7 @@ //@ run-pass + +#![allow(unused_assignments)] + struct S { o: Option } diff --git a/tests/ui/issues/issue-24353.rs b/tests/ui/issues/issue-24353.rs index 369fc238d1173..c754b9d631a4e 100644 --- a/tests/ui/issues/issue-24353.rs +++ b/tests/ui/issues/issue-24353.rs @@ -4,5 +4,6 @@ fn main() { return (); let x = (); + //~^ WARN unused variable: `x` x } diff --git a/tests/ui/issues/issue-24353.stderr b/tests/ui/issues/issue-24353.stderr new file mode 100644 index 0000000000000..bd7699a1bbb0c --- /dev/null +++ b/tests/ui/issues/issue-24353.stderr @@ -0,0 +1,10 @@ +warning: unused variable: `x` + --> $DIR/issue-24353.rs:6:9 + | +LL | let x = (); + | ^ help: if this is intentional, prefix it with an underscore: `_x` + | + = note: `#[warn(unused_variables)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/lint/dead-code/issue-85071-2.stderr b/tests/ui/lint/dead-code/issue-85071-2.stderr index ab88d72359695..5cc94e25e3a69 100644 --- a/tests/ui/lint/dead-code/issue-85071-2.stderr +++ b/tests/ui/lint/dead-code/issue-85071-2.stderr @@ -1,15 +1,3 @@ -warning: unused variable: `x` - --> $DIR/issue-85071-2.rs:18:9 - | -LL | let x = s.f(); - | ^ help: if this is intentional, prefix it with an underscore: `_x` - | -note: the lint level is defined here - --> $DIR/issue-85071-2.rs:7:9 - | -LL | #![warn(unused_variables,unreachable_code)] - | ^^^^^^^^^^^^^^^^ - warning: unreachable definition --> $DIR/issue-85071-2.rs:18:9 | @@ -29,5 +17,17 @@ note: the lint level is defined here LL | #![warn(unused_variables,unreachable_code)] | ^^^^^^^^^^^^^^^^ +warning: unused variable: `x` + --> $DIR/issue-85071-2.rs:18:9 + | +LL | let x = s.f(); + | ^ help: if this is intentional, prefix it with an underscore: `_x` + | +note: the lint level is defined here + --> $DIR/issue-85071-2.rs:7:9 + | +LL | #![warn(unused_variables,unreachable_code)] + | ^^^^^^^^^^^^^^^^ + warning: 2 warnings emitted diff --git a/tests/ui/lint/dead-code/issue-85071.stderr b/tests/ui/lint/dead-code/issue-85071.stderr index c94923063903a..b95ae09385dec 100644 --- a/tests/ui/lint/dead-code/issue-85071.stderr +++ b/tests/ui/lint/dead-code/issue-85071.stderr @@ -1,15 +1,3 @@ -warning: unused variable: `x` - --> $DIR/issue-85071.rs:15:9 - | -LL | let x = f(); - | ^ help: if this is intentional, prefix it with an underscore: `_x` - | -note: the lint level is defined here - --> $DIR/issue-85071.rs:9:9 - | -LL | #![warn(unused_variables,unreachable_code)] - | ^^^^^^^^^^^^^^^^ - warning: unreachable definition --> $DIR/issue-85071.rs:15:9 | @@ -29,5 +17,17 @@ note: the lint level is defined here LL | #![warn(unused_variables,unreachable_code)] | ^^^^^^^^^^^^^^^^ +warning: unused variable: `x` + --> $DIR/issue-85071.rs:15:9 + | +LL | let x = f(); + | ^ help: if this is intentional, prefix it with an underscore: `_x` + | +note: the lint level is defined here + --> $DIR/issue-85071.rs:9:9 + | +LL | #![warn(unused_variables,unreachable_code)] + | ^^^^^^^^^^^^^^^^ + warning: 2 warnings emitted diff --git a/tests/ui/lint/future-incompat-json-test.stderr b/tests/ui/lint/future-incompat-json-test.stderr index bf29b9577db5e..2e7d0cf626f1d 100644 --- a/tests/ui/lint/future-incompat-json-test.stderr +++ b/tests/ui/lint/future-incompat-json-test.stderr @@ -1,4 +1,4 @@ -{"$message_type":"future_incompat","future_incompat_report":[{"diagnostic":{"$message_type":"diagnostic","message":"unused variable: `x`","code":{"code":"unused_variables","explanation":null},"level":"warning","spans":[{"file_name":"$DIR/future-incompat-json-test.rs","byte_start":340,"byte_end":341,"line_start":9,"line_end":9,"column_start":9,"column_end":10,"is_primary":true,"text":[{"text":" let x = 1;","highlight_start":9,"highlight_end":10}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"if this is intentional, prefix it with an underscore","code":null,"level":"help","spans":[{"file_name":"$DIR/future-incompat-json-test.rs","byte_start":340,"byte_end":341,"line_start":9,"line_end":9,"column_start":9,"column_end":10,"is_primary":true,"text":[{"text":" let x = 1;","highlight_start":9,"highlight_end":10}],"label":null,"suggested_replacement":"_x","suggestion_applicability":"MaybeIncorrect","expansion":null}],"children":[],"rendered":null}],"rendered":"warning: unused variable: `x` +{"$message_type":"future_incompat","future_incompat_report":[{"diagnostic":{"$message_type":"diagnostic","message":"unused variable: `x`","code":{"code":"unused_variables","explanation":null},"level":"warning","spans":[{"file_name":"$DIR/future-incompat-json-test.rs","byte_start":340,"byte_end":341,"line_start":9,"line_end":9,"column_start":9,"column_end":10,"is_primary":true,"text":[{"text":" let x = 1;","highlight_start":9,"highlight_end":10}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"if this is intentional, prefix it with an underscore","code":null,"level":"help","spans":[{"file_name":"$DIR/future-incompat-json-test.rs","byte_start":340,"byte_end":341,"line_start":9,"line_end":9,"column_start":9,"column_end":10,"is_primary":true,"text":[{"text":" let x = 1;","highlight_start":9,"highlight_end":10}],"label":null,"suggested_replacement":"_x","suggestion_applicability":"MachineApplicable","expansion":null}],"children":[],"rendered":null}],"rendered":"warning: unused variable: `x` --> $DIR/future-incompat-json-test.rs:9:9 | LL | let x = 1; diff --git a/tests/ui/lint/issue-49588-non-shorthand-field-patterns-in-pattern-macro.rs b/tests/ui/lint/issue-49588-non-shorthand-field-patterns-in-pattern-macro.rs index 8e74531e7762a..5b5843a8ddbb6 100644 --- a/tests/ui/lint/issue-49588-non-shorthand-field-patterns-in-pattern-macro.rs +++ b/tests/ui/lint/issue-49588-non-shorthand-field-patterns-in-pattern-macro.rs @@ -13,4 +13,5 @@ macro_rules! pat { fn main() { let pat!(value) = Value { value: () }; + //~^ WARN value assigned to `value` is never read } diff --git a/tests/ui/lint/issue-49588-non-shorthand-field-patterns-in-pattern-macro.stderr b/tests/ui/lint/issue-49588-non-shorthand-field-patterns-in-pattern-macro.stderr new file mode 100644 index 0000000000000..e30d433cdfa38 --- /dev/null +++ b/tests/ui/lint/issue-49588-non-shorthand-field-patterns-in-pattern-macro.stderr @@ -0,0 +1,11 @@ +warning: value assigned to `value` is never read + --> $DIR/issue-49588-non-shorthand-field-patterns-in-pattern-macro.rs:15:14 + | +LL | let pat!(value) = Value { value: () }; + | ^^^^^ + | + = help: maybe it is overwritten before being read? + = note: `#[warn(unused_assignments)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/lint/rfc-2383-lint-reason/expect_lint_from_macro.stderr b/tests/ui/lint/rfc-2383-lint-reason/expect_lint_from_macro.stderr index 49dba1c7ffe8c..b09270d94ba31 100644 --- a/tests/ui/lint/rfc-2383-lint-reason/expect_lint_from_macro.stderr +++ b/tests/ui/lint/rfc-2383-lint-reason/expect_lint_from_macro.stderr @@ -2,11 +2,19 @@ warning: unused variable: `x` --> $DIR/expect_lint_from_macro.rs:7:13 | LL | let x = 0; - | ^ help: if this is intentional, prefix it with an underscore: `_x` + | ^ ... LL | trigger_unused_variables_macro!(); | --------------------------------- in this macro invocation | +help: `x` is captured in macro and introduced a unused variable + --> $DIR/expect_lint_from_macro.rs:7:13 + | +LL | let x = 0; + | ^ +... +LL | trigger_unused_variables_macro!(); + | --------------------------------- in this macro invocation note: the lint level is defined here --> $DIR/expect_lint_from_macro.rs:3:9 | @@ -18,11 +26,19 @@ warning: unused variable: `x` --> $DIR/expect_lint_from_macro.rs:7:13 | LL | let x = 0; - | ^ help: if this is intentional, prefix it with an underscore: `_x` + | ^ ... LL | trigger_unused_variables_macro!(); | --------------------------------- in this macro invocation | +help: `x` is captured in macro and introduced a unused variable + --> $DIR/expect_lint_from_macro.rs:7:13 + | +LL | let x = 0; + | ^ +... +LL | trigger_unused_variables_macro!(); + | --------------------------------- in this macro invocation = note: this warning originates in the macro `trigger_unused_variables_macro` (in Nightly builds, run with -Z macro-backtrace for more info) warning: 2 warnings emitted diff --git a/tests/ui/lint/rfc-2383-lint-reason/force_warn_expected_lints_fulfilled.stderr b/tests/ui/lint/rfc-2383-lint-reason/force_warn_expected_lints_fulfilled.stderr index eaf9edd8e8fee..dbfdfc7d4fa7a 100644 --- a/tests/ui/lint/rfc-2383-lint-reason/force_warn_expected_lints_fulfilled.stderr +++ b/tests/ui/lint/rfc-2383-lint-reason/force_warn_expected_lints_fulfilled.stderr @@ -14,12 +14,6 @@ LL | let x = 2; | = note: requested on the command line with `--force-warn unused-variables` -warning: unused variable: `fox_name` - --> $DIR/force_warn_expected_lints_fulfilled.rs:26:9 - | -LL | let fox_name = "Sir Nibbles"; - | ^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_fox_name` - warning: variable does not need to be mutable --> $DIR/force_warn_expected_lints_fulfilled.rs:30:9 | @@ -30,6 +24,12 @@ LL | let mut what_does_the_fox_say = "*ding* *deng* *dung*"; | = note: requested on the command line with `--force-warn unused-mut` +warning: unused variable: `fox_name` + --> $DIR/force_warn_expected_lints_fulfilled.rs:26:9 + | +LL | let fox_name = "Sir Nibbles"; + | ^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_fox_name` + warning: unused variable: `this_should_fulfill_the_expectation` --> $DIR/force_warn_expected_lints_fulfilled.rs:41:9 | diff --git a/tests/ui/lint/unused/issue-117284-arg-in-macro.rs b/tests/ui/lint/unused/issue-117284-arg-in-macro.rs index eea0f4c594dc8..3d622b6991300 100644 --- a/tests/ui/lint/unused/issue-117284-arg-in-macro.rs +++ b/tests/ui/lint/unused/issue-117284-arg-in-macro.rs @@ -1,7 +1,7 @@ #![deny(unused_variables)] macro_rules! make_var { ($struct:ident, $var:ident) => { - let $var = $struct.$var; + let $var = $struct.$var; //~ ERROR unused variable: `var` }; } @@ -12,6 +12,6 @@ struct MyStruct { fn main() { let s = MyStruct { var: 42 }; - make_var!(s, var); //~ ERROR unused variable: `var` + make_var!(s, var); let a = 1; //~ ERROR unused variable: `a` } diff --git a/tests/ui/lint/unused/issue-117284-arg-in-macro.stderr b/tests/ui/lint/unused/issue-117284-arg-in-macro.stderr index 84efaa4f3687b..b4a6871713c06 100644 --- a/tests/ui/lint/unused/issue-117284-arg-in-macro.stderr +++ b/tests/ui/lint/unused/issue-117284-arg-in-macro.stderr @@ -1,8 +1,11 @@ error: unused variable: `var` - --> $DIR/issue-117284-arg-in-macro.rs:15:18 + --> $DIR/issue-117284-arg-in-macro.rs:4:13 | +LL | let $var = $struct.$var; + | ^^^^ +... LL | make_var!(s, var); - | ^^^ + | ----------------- in this macro invocation | help: `var` is captured in macro and introduced a unused variable --> $DIR/issue-117284-arg-in-macro.rs:4:13 diff --git a/tests/ui/lint/unused/issue-47390-unused-variable-in-struct-pattern.stderr b/tests/ui/lint/unused/issue-47390-unused-variable-in-struct-pattern.stderr index fe2e3afc82ec8..c378b307b8b54 100644 --- a/tests/ui/lint/unused/issue-47390-unused-variable-in-struct-pattern.stderr +++ b/tests/ui/lint/unused/issue-47390-unused-variable-in-struct-pattern.stderr @@ -1,27 +1,45 @@ -warning: unused variable: `i_think_continually` - --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:26:9 +warning: variable does not need to be mutable + --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:33:9 | -LL | let i_think_continually = 2; - | ^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_i_think_continually` +LL | let mut mut_unused_var = 1; + | ----^^^^^^^^^^^^^^ + | | + | help: remove this `mut` | note: the lint level is defined here --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:5:9 | LL | #![warn(unused)] // UI tests pass `-A unused` (#43896) | ^^^^^^ + = note: `#[warn(unused_mut)]` implied by `#[warn(unused)]` + +warning: variable does not need to be mutable + --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:37:10 + | +LL | let (mut var, unused_var) = (1, 2); + | ----^^^ + | | + | help: remove this `mut` + +warning: unused variable: `i_think_continually` + --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:26:9 + | +LL | let i_think_continually = 2; + | ^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_i_think_continually` + | = note: `#[warn(unused_variables)]` implied by `#[warn(unused)]` warning: unused variable: `mut_unused_var` - --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:33:13 + --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:33:9 | LL | let mut mut_unused_var = 1; - | ^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_mut_unused_var` + | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_mut_unused_var` warning: unused variable: `var` - --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:37:14 + --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:37:10 | LL | let (mut var, unused_var) = (1, 2); - | ^^^ help: if this is intentional, prefix it with an underscore: `_var` + | ^^^^^^^ help: if this is intentional, prefix it with an underscore: `_var` warning: unused variable: `unused_var` --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:37:19 @@ -36,22 +54,13 @@ LL | if let SoulHistory { corridors_of_light, | ^^^^^^^^^^^^^^^^^^ help: try ignoring the field: `corridors_of_light: _` warning: variable `hours_are_suns` is assigned to, but never used - --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:46:30 + --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:46:26 | LL | mut hours_are_suns, - | ^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^ | = note: consider using `_hours_are_suns` instead -warning: value assigned to `hours_are_suns` is never read - --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:48:9 - | -LL | hours_are_suns = false; - | ^^^^^^^^^^^^^^ - | - = help: maybe it is overwritten before being read? - = note: `#[warn(unused_assignments)]` implied by `#[warn(unused)]` - warning: unused variable: `fire` --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:52:32 | @@ -94,23 +103,14 @@ warning: unused variable: `case` LL | Tuple(Large::Suit { case }, ()) => {} | ^^^^ help: try ignoring the field: `case: _` -warning: variable does not need to be mutable - --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:33:9 - | -LL | let mut mut_unused_var = 1; - | ----^^^^^^^^^^^^^^ - | | - | help: remove this `mut` +warning: value assigned to `hours_are_suns` is never read + --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:48:9 | - = note: `#[warn(unused_mut)]` implied by `#[warn(unused)]` - -warning: variable does not need to be mutable - --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:37:10 +LL | hours_are_suns = false; + | ^^^^^^^^^^^^^^^^^^^^^^ | -LL | let (mut var, unused_var) = (1, 2); - | ----^^^ - | | - | help: remove this `mut` + = help: maybe it is overwritten before being read? + = note: `#[warn(unused_assignments)]` implied by `#[warn(unused)]` warning: 16 warnings emitted diff --git a/tests/ui/lint/unused/issue-54180-unused-ref-field.stderr b/tests/ui/lint/unused/issue-54180-unused-ref-field.stderr index f2e6168998c44..c501aa25f1352 100644 --- a/tests/ui/lint/unused/issue-54180-unused-ref-field.stderr +++ b/tests/ui/lint/unused/issue-54180-unused-ref-field.stderr @@ -11,6 +11,12 @@ LL | #![deny(unused)] | ^^^^^^ = note: `#[deny(unused_variables)]` implied by `#[deny(unused)]` +error: unused variable: `x` + --> $DIR/issue-54180-unused-ref-field.rs:29:45 + | +LL | let _: i32 = points.iter().map(|Point { x, y }| y).sum(); + | ^ help: try ignoring the field: `x: _` + error: unused variable: `f1` --> $DIR/issue-54180-unused-ref-field.rs:26:13 | @@ -23,11 +29,5 @@ error: unused variable: `x` LL | Point { y, ref mut x } => y, | ^^^^^^^^^ help: try ignoring the field: `x: _` -error: unused variable: `x` - --> $DIR/issue-54180-unused-ref-field.rs:29:45 - | -LL | let _: i32 = points.iter().map(|Point { x, y }| y).sum(); - | ^ help: try ignoring the field: `x: _` - error: aborting due to 4 previous errors diff --git a/tests/ui/lint/unused/lint-unused-variables.stderr b/tests/ui/lint/unused/lint-unused-variables.stderr index 6106d4cd1bfc9..beb1c0d736b35 100644 --- a/tests/ui/lint/unused/lint-unused-variables.stderr +++ b/tests/ui/lint/unused/lint-unused-variables.stderr @@ -10,24 +10,18 @@ note: the lint level is defined here LL | #![deny(unused_variables)] | ^^^^^^^^^^^^^^^^ -error: unused variable: `a` - --> $DIR/lint-unused-variables.rs:21:9 +error: unused variable: `b` + --> $DIR/lint-unused-variables.rs:13:5 | -LL | a: i32, - | ^ help: if this is intentional, prefix it with an underscore: `_a` +LL | b: i32, + | ^ help: if this is intentional, prefix it with an underscore: `_b` error: unused variable: `a` - --> $DIR/lint-unused-variables.rs:67:9 + --> $DIR/lint-unused-variables.rs:21:9 | LL | a: i32, | ^ help: if this is intentional, prefix it with an underscore: `_a` -error: unused variable: `b` - --> $DIR/lint-unused-variables.rs:13:5 - | -LL | b: i32, - | ^ help: if this is intentional, prefix it with an underscore: `_b` - error: unused variable: `b` --> $DIR/lint-unused-variables.rs:28:9 | @@ -70,5 +64,11 @@ error: unused variable: `b` LL | b: i32, | ^ help: if this is intentional, prefix it with an underscore: `_b` +error: unused variable: `a` + --> $DIR/lint-unused-variables.rs:67:9 + | +LL | a: i32, + | ^ help: if this is intentional, prefix it with an underscore: `_a` + error: aborting due to 11 previous errors diff --git a/tests/ui/liveness/liveness-asm.stderr b/tests/ui/liveness/liveness-asm.stderr index 57d89e44dcb86..ca1c07046d0ef 100644 --- a/tests/ui/liveness/liveness-asm.stderr +++ b/tests/ui/liveness/liveness-asm.stderr @@ -1,8 +1,8 @@ warning: value assigned to `src` is never read - --> $DIR/liveness-asm.rs:14:32 + --> $DIR/liveness-asm.rs:14:5 | LL | asm!("/*{0}*/", inout(reg) src); - | ^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: maybe it is overwritten before being read? note: the lint level is defined here @@ -12,10 +12,10 @@ LL | #![warn(unused_assignments)] | ^^^^^^^^^^^^^^^^^^ warning: value assigned to `src` is never read - --> $DIR/liveness-asm.rs:24:39 + --> $DIR/liveness-asm.rs:24:5 | LL | asm!("/*{0}*/", inout(reg) src => src); - | ^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: maybe it is overwritten before being read? diff --git a/tests/ui/liveness/liveness-consts.rs b/tests/ui/liveness/liveness-consts.rs index 40d30fb9113a9..7e56bf8c2cda4 100644 --- a/tests/ui/liveness/liveness-consts.rs +++ b/tests/ui/liveness/liveness-consts.rs @@ -4,16 +4,18 @@ pub static A: i32 = { let mut i = 0; - let mut a = 0; //~ WARN variable `a` is assigned to, but never used + let mut a = 0; + //~^ WARN variable `a` is assigned to, but never used while i < 10 { i += 1; a += 1; + //~^ WARN value assigned to `a` is never read } i }; pub const B: u32 = { - let mut b = 1; + let mut b = 1; //~ WARN value assigned to `b` is never read b += 1; //~ WARN value assigned to `b` is never read b = 42; b diff --git a/tests/ui/liveness/liveness-consts.stderr b/tests/ui/liveness/liveness-consts.stderr index 34ce39473379e..2d2750fbaddff 100644 --- a/tests/ui/liveness/liveness-consts.stderr +++ b/tests/ui/liveness/liveness-consts.stderr @@ -1,5 +1,5 @@ warning: unused variable: `e` - --> $DIR/liveness-consts.rs:24:13 + --> $DIR/liveness-consts.rs:26:13 | LL | let e = 1; | ^ help: if this is intentional, prefix it with an underscore: `_e` @@ -12,53 +12,69 @@ LL | #![warn(unused)] = note: `#[warn(unused_variables)]` implied by `#[warn(unused)]` warning: unused variable: `s` - --> $DIR/liveness-consts.rs:33:24 + --> $DIR/liveness-consts.rs:35:24 | LL | pub fn f(x: [u8; { let s = 17; 100 }]) -> [u8; { let z = 18; 100 }] { | ^ help: if this is intentional, prefix it with an underscore: `_s` warning: unused variable: `z` - --> $DIR/liveness-consts.rs:33:55 + --> $DIR/liveness-consts.rs:35:55 | LL | pub fn f(x: [u8; { let s = 17; 100 }]) -> [u8; { let z = 18; 100 }] { | ^ help: if this is intentional, prefix it with an underscore: `_z` warning: variable `a` is assigned to, but never used - --> $DIR/liveness-consts.rs:7:13 + --> $DIR/liveness-consts.rs:7:9 | LL | let mut a = 0; - | ^ + | ^^^^^ | = note: consider using `_a` instead +warning: value assigned to `a` is never read + --> $DIR/liveness-consts.rs:11:9 + | +LL | a += 1; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + = note: `#[warn(unused_assignments)]` implied by `#[warn(unused)]` + warning: value assigned to `b` is never read - --> $DIR/liveness-consts.rs:17:5 + --> $DIR/liveness-consts.rs:18:17 + | +LL | let mut b = 1; + | ^ + | + = help: maybe it is overwritten before being read? + +warning: value assigned to `b` is never read + --> $DIR/liveness-consts.rs:19:5 | LL | b += 1; - | ^ + | ^^^^^^ | = help: maybe it is overwritten before being read? - = note: `#[warn(unused_assignments)]` implied by `#[warn(unused)]` warning: unused variable: `z` - --> $DIR/liveness-consts.rs:60:13 + --> $DIR/liveness-consts.rs:62:13 | LL | let z = 42; | ^ help: if this is intentional, prefix it with an underscore: `_z` warning: value assigned to `t` is never read - --> $DIR/liveness-consts.rs:42:9 + --> $DIR/liveness-consts.rs:44:9 | LL | t = t + t; - | ^ + | ^^^^^^^^^ | = help: maybe it is overwritten before being read? warning: unused variable: `w` - --> $DIR/liveness-consts.rs:49:13 + --> $DIR/liveness-consts.rs:51:13 | LL | let w = 10; | ^ help: if this is intentional, prefix it with an underscore: `_w` -warning: 8 warnings emitted +warning: 10 warnings emitted diff --git a/tests/ui/liveness/liveness-dead.stderr b/tests/ui/liveness/liveness-dead.stderr index de6d5bd993d62..4f7dda49cd03e 100644 --- a/tests/ui/liveness/liveness-dead.stderr +++ b/tests/ui/liveness/liveness-dead.stderr @@ -1,8 +1,8 @@ error: value assigned to `x` is never read - --> $DIR/liveness-dead.rs:9:13 + --> $DIR/liveness-dead.rs:9:24 | LL | let mut x: isize = 3; - | ^ + | ^ | = help: maybe it is overwritten before being read? note: the lint level is defined here @@ -15,15 +15,15 @@ error: value assigned to `x` is never read --> $DIR/liveness-dead.rs:17:5 | LL | x = 4; - | ^ + | ^^^^^ | = help: maybe it is overwritten before being read? error: value passed to `x` is never read - --> $DIR/liveness-dead.rs:20:11 + --> $DIR/liveness-dead.rs:20:7 | LL | fn f4(mut x: i32) { - | ^ + | ^^^^^ | = help: maybe it is overwritten before being read? @@ -31,7 +31,7 @@ error: value assigned to `x` is never read --> $DIR/liveness-dead.rs:27:5 | LL | x = 4; - | ^ + | ^^^^^ | = help: maybe it is overwritten before being read? diff --git a/tests/ui/liveness/liveness-unused.rs b/tests/ui/liveness/liveness-unused.rs index 49e7044aeda1f..639d7c2776dee 100644 --- a/tests/ui/liveness/liveness-unused.rs +++ b/tests/ui/liveness/liveness-unused.rs @@ -39,6 +39,7 @@ fn f3b() { //~^ ERROR variable `z` is assigned to, but never used loop { z += 4; + //~^ ERROR value assigned to `z` is never read } } @@ -46,6 +47,7 @@ fn f3b() { fn f3c() { let mut z = 3; loop { z += 4; } + //~^ ERROR value assigned to `z` is never read } #[allow(unused_variables)] @@ -55,6 +57,16 @@ fn f3d() { x += 4; } +fn f3e() { + let a = 13; + let mut z = 3; + //~^ ERROR variable `z` is assigned to, but never used + loop { + z += a; + //~^ ERROR value assigned to `z` is never read + } +} + fn f4() { match Some(3) { Some(i) => { @@ -68,7 +80,15 @@ enum tri { a(isize), b(isize), c(isize) } -fn f4b() -> isize { +fn f4b() { + match tri::a(3) { + tri::a(i) | tri::b(i) | tri::c(i) => { + //~^ ERROR unused variable: `i` + } + } +} + +fn f4c() -> isize { match tri::a(3) { tri::a(i) | tri::b(i) | tri::c(i) => { i @@ -76,6 +96,13 @@ fn f4b() -> isize { } } +fn f4d() { + match tri::a(3) { + tri::a(i) | tri::b(i) | tri::c(i) if i == 0 => {} + _ => {} + } +} + fn f5a() { for x in 1..10 { } //~^ ERROR unused variable: `x` @@ -138,10 +165,92 @@ fn f7() { drop(a); } +fn f8(a: u32) { + let _ = a; +} + +fn f9() { + let mut a = 10; + //~^ ERROR variable `a` is assigned to, but never used + let b = 13; + let c = 13; + let d = 13; + let e = 13; + let f = 13; + let g = 13; + let h = 13; + + a += b; + //~^ ERROR value assigned to `a` is never read + a -= c; + //~^ ERROR value assigned to `a` is never read + a *= d; + //~^ ERROR value assigned to `a` is never read + a /= e; + //~^ ERROR value assigned to `a` is never read + a |= f; + //~^ ERROR value assigned to `a` is never read + a &= g; + //~^ ERROR value assigned to `a` is never read + a %= h; + //~^ ERROR value assigned to `a` is never read +} + +fn f9b() { + let mut a = 10; + let b = 13; + let c = 13; + let d = 13; + let e = 13; + let f = 13; + let g = 13; + let h = 13; + + a += b; + a -= c; + a *= d; + a /= e; + a |= f; + a &= g; + a %= h; + + let _ = a; +} + +fn f9c() { + let mut a = 10.; + //~^ ERROR variable `a` is assigned to, but never used + let b = 13.; + let c = 13.; + let d = 13.; + let e = 13.; + let f = 13.; + + a += b; + //~^ ERROR value assigned to `a` is never read + a -= c; + //~^ ERROR value assigned to `a` is never read + a *= d; + //~^ ERROR value assigned to `a` is never read + a /= e; + //~^ ERROR value assigned to `a` is never read + a %= f; + //~^ ERROR value assigned to `a` is never read +} + +fn f10(mut a: T, b: T) { + //~^ ERROR variable `a` is assigned to, but never used + a = b; + //~^ ERROR value assigned to `a` is never read +} + +fn f10b(mut a: Box, b: Box) { + a = b; +} + // unused params warnings are not needed for intrinsic functions without bodies #[rustc_intrinsic] unsafe fn simd_shuffle(a: T, b: T, i: I) -> U; - fn main() { } diff --git a/tests/ui/liveness/liveness-unused.stderr b/tests/ui/liveness/liveness-unused.stderr index a69fc10dff271..23a26841be2fc 100644 --- a/tests/ui/liveness/liveness-unused.stderr +++ b/tests/ui/liveness/liveness-unused.stderr @@ -1,5 +1,5 @@ warning: unreachable statement - --> $DIR/liveness-unused.rs:93:9 + --> $DIR/liveness-unused.rs:120:9 | LL | continue; | -------- any code following this expression is unreachable @@ -44,10 +44,10 @@ LL | let x = 3; | ^ help: if this is intentional, prefix it with an underscore: `_x` error: variable `x` is assigned to, but never used - --> $DIR/liveness-unused.rs:31:13 + --> $DIR/liveness-unused.rs:31:9 | LL | let mut x = 3; - | ^ + | ^^^^^ | = note: consider using `_x` instead @@ -55,7 +55,7 @@ error: value assigned to `x` is never read --> $DIR/liveness-unused.rs:33:5 | LL | x += 4; - | ^ + | ^^^^^^ | = help: maybe it is overwritten before being read? note: the lint level is defined here @@ -65,39 +65,82 @@ LL | #![deny(unused_assignments)] | ^^^^^^^^^^^^^^^^^^ error: variable `z` is assigned to, but never used - --> $DIR/liveness-unused.rs:38:13 + --> $DIR/liveness-unused.rs:38:9 | LL | let mut z = 3; - | ^ + | ^^^^^ | = note: consider using `_z` instead +error: value assigned to `z` is never read + --> $DIR/liveness-unused.rs:41:9 + | +LL | z += 4; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +error: value assigned to `z` is never read + --> $DIR/liveness-unused.rs:49:12 + | +LL | loop { z += 4; } + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +error: variable `z` is assigned to, but never used + --> $DIR/liveness-unused.rs:62:9 + | +LL | let mut z = 3; + | ^^^^^ + | + = note: consider using `_z` instead + +error: value assigned to `z` is never read + --> $DIR/liveness-unused.rs:65:9 + | +LL | z += a; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + error: unused variable: `i` - --> $DIR/liveness-unused.rs:60:12 + --> $DIR/liveness-unused.rs:72:12 | LL | Some(i) => { | ^ help: if this is intentional, prefix it with an underscore: `_i` +error: unused variable: `i` + --> $DIR/liveness-unused.rs:85:14 + | +LL | tri::a(i) | tri::b(i) | tri::c(i) => { + | ^ ^ ^ + | +help: if this is intentional, prefix it with an underscore + | +LL | tri::a(_i) | tri::b(_i) | tri::c(_i) => { + | + + + + error: unused variable: `x` - --> $DIR/liveness-unused.rs:80:9 + --> $DIR/liveness-unused.rs:107:9 | LL | for x in 1..10 { } | ^ help: if this is intentional, prefix it with an underscore: `_x` error: unused variable: `x` - --> $DIR/liveness-unused.rs:85:10 + --> $DIR/liveness-unused.rs:112:10 | LL | for (x, _) in [1, 2, 3].iter().enumerate() { } | ^ help: if this is intentional, prefix it with an underscore: `_x` error: unused variable: `x` - --> $DIR/liveness-unused.rs:90:13 + --> $DIR/liveness-unused.rs:117:13 | LL | for (_, x) in [1, 2, 3].iter().enumerate() { | ^ help: if this is intentional, prefix it with an underscore: `_x` error: variable `x` is assigned to, but never used - --> $DIR/liveness-unused.rs:113:9 + --> $DIR/liveness-unused.rs:140:9 | LL | let x; | ^ @@ -105,12 +148,140 @@ LL | let x; = note: consider using `_x` instead error: value assigned to `x` is never read - --> $DIR/liveness-unused.rs:117:9 + --> $DIR/liveness-unused.rs:144:9 | LL | x = 0; - | ^ + | ^^^^^ + | + = help: maybe it is overwritten before being read? + +error: variable `a` is assigned to, but never used + --> $DIR/liveness-unused.rs:173:9 + | +LL | let mut a = 10; + | ^^^^^ + | + = note: consider using `_a` instead + +error: value assigned to `a` is never read + --> $DIR/liveness-unused.rs:183:5 + | +LL | a += b; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +error: value assigned to `a` is never read + --> $DIR/liveness-unused.rs:185:5 + | +LL | a -= c; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +error: value assigned to `a` is never read + --> $DIR/liveness-unused.rs:187:5 + | +LL | a *= d; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +error: value assigned to `a` is never read + --> $DIR/liveness-unused.rs:189:5 + | +LL | a /= e; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +error: value assigned to `a` is never read + --> $DIR/liveness-unused.rs:191:5 + | +LL | a |= f; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +error: value assigned to `a` is never read + --> $DIR/liveness-unused.rs:193:5 + | +LL | a &= g; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +error: value assigned to `a` is never read + --> $DIR/liveness-unused.rs:195:5 + | +LL | a %= h; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +error: variable `a` is assigned to, but never used + --> $DIR/liveness-unused.rs:221:9 + | +LL | let mut a = 10.; + | ^^^^^ + | + = note: consider using `_a` instead + +error: value assigned to `a` is never read + --> $DIR/liveness-unused.rs:229:5 + | +LL | a += b; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +error: value assigned to `a` is never read + --> $DIR/liveness-unused.rs:231:5 + | +LL | a -= c; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +error: value assigned to `a` is never read + --> $DIR/liveness-unused.rs:233:5 + | +LL | a *= d; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +error: value assigned to `a` is never read + --> $DIR/liveness-unused.rs:235:5 + | +LL | a /= e; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +error: value assigned to `a` is never read + --> $DIR/liveness-unused.rs:237:5 + | +LL | a %= f; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +error: variable `a` is assigned to, but never used + --> $DIR/liveness-unused.rs:241:11 + | +LL | fn f10(mut a: T, b: T) { + | ^^^^^ + | + = note: consider using `_a` instead + +error: value assigned to `a` is never read + --> $DIR/liveness-unused.rs:243:5 + | +LL | a = b; + | ^ | = help: maybe it is overwritten before being read? -error: aborting due to 13 previous errors; 1 warning emitted +error: aborting due to 34 previous errors; 1 warning emitted diff --git a/tests/ui/liveness/liveness-upvars.rs b/tests/ui/liveness/liveness-upvars.rs index f76efba3e6b3a..142cb0640bb2b 100644 --- a/tests/ui/liveness/liveness-upvars.rs +++ b/tests/ui/liveness/liveness-upvars.rs @@ -7,8 +7,8 @@ pub fn unintentional_copy_one() { let mut last = None; let mut f = move |s| { - last = Some(s); //~ WARN value assigned to `last` is never read - //~| WARN unused variable: `last` + last = Some(s); //~ WARN value captured by `last` is never read + //~| WARN value assigned to `last` is never read }; f("a"); f("b"); @@ -19,7 +19,9 @@ pub fn unintentional_copy_one() { pub fn unintentional_copy_two() { let mut sum = 0; (1..10).for_each(move |x| { - sum += x; //~ WARN unused variable: `sum` + sum += x; + //~^ WARN value captured by `sum` is never read + //~| WARN value assigned to `sum` is never read }); dbg!(sum); } @@ -39,11 +41,14 @@ pub fn f() { // Read and written to, but never actually used. let _ = move || { - c += 1; //~ WARN unused variable: `c` + c += 1; + //~^ WARN value captured by `c` is never read + //~| WARN value assigned to `c` is never read }; let _ = async move { - c += 1; //~ WARN value assigned to `c` is never read - //~| WARN unused variable: `c` + c += 1; + //~^ WARN value captured by `c` is never read + //~| WARN value assigned to `c` is never read }; let _ = move || { @@ -74,8 +79,8 @@ pub fn nested() { d = Some("d2"); }; let _ = move || { - e = Some("e1"); //~ WARN value assigned to `e` is never read - //~| WARN unused variable: `e` + e = Some("e1"); //~ WARN value captured by `e` is never read + //~| WARN value assigned to `e` is never read e = Some("e2"); //~ WARN value assigned to `e` is never read }; }; @@ -84,7 +89,8 @@ pub fn nested() { pub fn g(mut v: T) { let _ = |r| { if r { - v = T::default(); //~ WARN value assigned to `v` is never read + v = T::default(); + //~^ WARN value assigned to `v` is never read } else { drop(v); } @@ -96,8 +102,8 @@ pub fn h() { let _ = move |b| { loop { if b { - z = T::default(); //~ WARN value assigned to `z` is never read - //~| WARN unused variable: `z` + z = T::default(); //~ WARN value captured by `z` is never read + //~| WARN value assigned to `z` is never read } else { return; } @@ -123,7 +129,7 @@ pub fn async_coroutine() { let _ = async move { state = 4; //~ WARN value assigned to `state` is never read - //~| WARN unused variable: `state` + //~| WARN value captured by `state` is never read yield_now().await; state = 5; //~ WARN value assigned to `state` is never read }; diff --git a/tests/ui/liveness/liveness-upvars.stderr b/tests/ui/liveness/liveness-upvars.stderr index 82f62371ec59d..cfed2830164ad 100644 --- a/tests/ui/liveness/liveness-upvars.stderr +++ b/tests/ui/liveness/liveness-upvars.stderr @@ -1,10 +1,10 @@ -warning: value assigned to `last` is never read +warning: value captured by `last` is never read --> $DIR/liveness-upvars.rs:10:9 | LL | last = Some(s); | ^^^^ | - = help: maybe it is overwritten before being read? + = help: did you mean to capture by reference instead? note: the lint level is defined here --> $DIR/liveness-upvars.rs:4:9 | @@ -12,16 +12,15 @@ LL | #![warn(unused)] | ^^^^^^ = note: `#[warn(unused_assignments)]` implied by `#[warn(unused)]` -warning: unused variable: `last` +warning: value assigned to `last` is never read --> $DIR/liveness-upvars.rs:10:9 | LL | last = Some(s); - | ^^^^ + | ^^^^^^^^^^^^^^ | - = help: did you mean to capture by reference instead? - = note: `#[warn(unused_variables)]` implied by `#[warn(unused)]` + = help: maybe it is overwritten before being read? -warning: unused variable: `sum` +warning: value captured by `sum` is never read --> $DIR/liveness-upvars.rs:22:9 | LL | sum += x; @@ -29,24 +28,32 @@ LL | sum += x; | = help: did you mean to capture by reference instead? -warning: value captured by `c` is never read - --> $DIR/liveness-upvars.rs:32:9 +warning: value assigned to `sum` is never read + --> $DIR/liveness-upvars.rs:22:9 | -LL | c = 1; - | ^ +LL | sum += x; + | ^^^^^^^^ | - = help: did you mean to capture by reference instead? + = help: maybe it is overwritten before being read? -warning: value captured by `c` is never read - --> $DIR/liveness-upvars.rs:36:9 +warning: value assigned to `c` is never read + --> $DIR/liveness-upvars.rs:69:9 | -LL | c = 1; - | ^ +LL | c += 1; + | ^^^^^^ | - = help: did you mean to capture by reference instead? + = help: maybe it is overwritten before being read? -warning: unused variable: `c` - --> $DIR/liveness-upvars.rs:42:9 +warning: value assigned to `c` is never read + --> $DIR/liveness-upvars.rs:63:9 + | +LL | c += 1; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +warning: value captured by `c` is never read + --> $DIR/liveness-upvars.rs:49:9 | LL | c += 1; | ^ @@ -54,15 +61,15 @@ LL | c += 1; = help: did you mean to capture by reference instead? warning: value assigned to `c` is never read - --> $DIR/liveness-upvars.rs:45:9 + --> $DIR/liveness-upvars.rs:49:9 | LL | c += 1; - | ^ + | ^^^^^^ | = help: maybe it is overwritten before being read? -warning: unused variable: `c` - --> $DIR/liveness-upvars.rs:45:9 +warning: value captured by `c` is never read + --> $DIR/liveness-upvars.rs:44:9 | LL | c += 1; | ^ @@ -70,116 +77,124 @@ LL | c += 1; = help: did you mean to capture by reference instead? warning: value assigned to `c` is never read - --> $DIR/liveness-upvars.rs:58:9 + --> $DIR/liveness-upvars.rs:44:9 | LL | c += 1; - | ^ + | ^^^^^^ | = help: maybe it is overwritten before being read? -warning: value assigned to `c` is never read - --> $DIR/liveness-upvars.rs:64:9 +warning: value captured by `c` is never read + --> $DIR/liveness-upvars.rs:38:9 | -LL | c += 1; +LL | c = 1; | ^ | - = help: maybe it is overwritten before being read? + = help: did you mean to capture by reference instead? -warning: value assigned to `d` is never read - --> $DIR/liveness-upvars.rs:73:13 +warning: value captured by `c` is never read + --> $DIR/liveness-upvars.rs:34:9 | -LL | d = Some("d1"); +LL | c = 1; + | ^ + | + = help: did you mean to capture by reference instead? + +warning: value captured by `e` is never read + --> $DIR/liveness-upvars.rs:82:13 + | +LL | e = Some("e1"); | ^ | - = help: maybe it is overwritten before being read? + = help: did you mean to capture by reference instead? warning: value assigned to `e` is never read - --> $DIR/liveness-upvars.rs:77:13 + --> $DIR/liveness-upvars.rs:82:13 | LL | e = Some("e1"); - | ^ + | ^^^^^^^^^^^^^^ | = help: maybe it is overwritten before being read? warning: value assigned to `e` is never read - --> $DIR/liveness-upvars.rs:79:13 + --> $DIR/liveness-upvars.rs:84:13 | LL | e = Some("e2"); - | ^ + | ^^^^^^^^^^^^^^ | = help: maybe it is overwritten before being read? -warning: unused variable: `e` - --> $DIR/liveness-upvars.rs:77:13 +warning: value assigned to `d` is never read + --> $DIR/liveness-upvars.rs:78:13 | -LL | e = Some("e1"); - | ^ +LL | d = Some("d1"); + | ^^^^^^^^^^^^^^ | - = help: did you mean to capture by reference instead? + = help: maybe it is overwritten before being read? warning: value assigned to `v` is never read - --> $DIR/liveness-upvars.rs:87:13 + --> $DIR/liveness-upvars.rs:92:13 | LL | v = T::default(); | ^ | = help: maybe it is overwritten before being read? -warning: value assigned to `z` is never read - --> $DIR/liveness-upvars.rs:99:17 +warning: value captured by `z` is never read + --> $DIR/liveness-upvars.rs:105:17 | LL | z = T::default(); | ^ | - = help: maybe it is overwritten before being read? + = help: did you mean to capture by reference instead? -warning: unused variable: `z` - --> $DIR/liveness-upvars.rs:99:17 +warning: value assigned to `z` is never read + --> $DIR/liveness-upvars.rs:105:17 | LL | z = T::default(); - | ^ + | ^^^^^^^^^^^^^^^^ | - = help: did you mean to capture by reference instead? + = help: maybe it is overwritten before being read? -warning: value assigned to `state` is never read - --> $DIR/liveness-upvars.rs:125:9 +warning: value captured by `state` is never read + --> $DIR/liveness-upvars.rs:131:9 | LL | state = 4; | ^^^^^ | - = help: maybe it is overwritten before being read? + = help: did you mean to capture by reference instead? warning: value assigned to `state` is never read - --> $DIR/liveness-upvars.rs:128:9 + --> $DIR/liveness-upvars.rs:131:9 | -LL | state = 5; - | ^^^^^ +LL | state = 4; + | ^^^^^^^^^ | = help: maybe it is overwritten before being read? -warning: unused variable: `state` - --> $DIR/liveness-upvars.rs:125:9 +warning: value assigned to `state` is never read + --> $DIR/liveness-upvars.rs:134:9 | -LL | state = 4; - | ^^^^^ +LL | state = 5; + | ^^^^^^^^^ | - = help: did you mean to capture by reference instead? + = help: maybe it is overwritten before being read? warning: value assigned to `s` is never read - --> $DIR/liveness-upvars.rs:137:9 + --> $DIR/liveness-upvars.rs:143:9 | LL | s = 1; - | ^ + | ^^^^^ | = help: maybe it is overwritten before being read? warning: value assigned to `s` is never read - --> $DIR/liveness-upvars.rs:139:9 + --> $DIR/liveness-upvars.rs:145:9 | LL | s = yield (); - | ^ + | ^^^^^^^^^^^^ | = help: maybe it is overwritten before being read? -warning: 22 warnings emitted +warning: 24 warnings emitted diff --git a/tests/ui/object-lifetime/object-lifetime-default-default-to-static.rs b/tests/ui/object-lifetime/object-lifetime-default-default-to-static.rs index 23e5852335611..ab3887f2e4108 100644 --- a/tests/ui/object-lifetime/object-lifetime-default-default-to-static.rs +++ b/tests/ui/object-lifetime/object-lifetime-default-default-to-static.rs @@ -3,7 +3,7 @@ // fields and fn arguments. -#![allow(dead_code)] +#![allow(dead_code, unused)] trait Test { fn foo(&self) { } diff --git a/tests/ui/object-lifetime/object-lifetime-default-from-ref-struct.rs b/tests/ui/object-lifetime/object-lifetime-default-from-ref-struct.rs index 040ac1f891326..e74e67f5b6891 100644 --- a/tests/ui/object-lifetime/object-lifetime-default-from-ref-struct.rs +++ b/tests/ui/object-lifetime/object-lifetime-default-from-ref-struct.rs @@ -3,7 +3,7 @@ // lifetime bound. -#![allow(dead_code)] +#![allow(dead_code, unused)] use std::fmt::Display; diff --git a/tests/ui/object-lifetime/object-lifetime-default-from-rptr-box.rs b/tests/ui/object-lifetime/object-lifetime-default-from-rptr-box.rs index c3f3101155cf1..2402bb24d787f 100644 --- a/tests/ui/object-lifetime/object-lifetime-default-from-rptr-box.rs +++ b/tests/ui/object-lifetime/object-lifetime-default-from-rptr-box.rs @@ -3,7 +3,7 @@ // through the `Box` struct. -#![allow(dead_code)] +#![allow(dead_code, unused)] trait Test { fn foo(&self) { } diff --git a/tests/ui/object-lifetime/object-lifetime-default-from-rptr-mut.rs b/tests/ui/object-lifetime/object-lifetime-default-from-rptr-mut.rs index db4f9a40235d4..03a4cb9ec709e 100644 --- a/tests/ui/object-lifetime/object-lifetime-default-from-rptr-mut.rs +++ b/tests/ui/object-lifetime/object-lifetime-default-from-rptr-mut.rs @@ -3,7 +3,7 @@ // lifetime bound. -#![allow(dead_code)] +#![allow(dead_code, unused)] trait Test { fn foo(&self) { } diff --git a/tests/ui/object-lifetime/object-lifetime-default-from-rptr-struct.rs b/tests/ui/object-lifetime/object-lifetime-default-from-rptr-struct.rs index 5163ff1c2455a..988cef49fe03e 100644 --- a/tests/ui/object-lifetime/object-lifetime-default-from-rptr-struct.rs +++ b/tests/ui/object-lifetime/object-lifetime-default-from-rptr-struct.rs @@ -3,7 +3,7 @@ // through the `MyBox` struct. -#![allow(dead_code)] +#![allow(dead_code, unused)] trait Test { fn foo(&self) { } diff --git a/tests/ui/object-lifetime/object-lifetime-default-from-rptr.rs b/tests/ui/object-lifetime/object-lifetime-default-from-rptr.rs index 556bde784152d..d4fc295f4fc62 100644 --- a/tests/ui/object-lifetime/object-lifetime-default-from-rptr.rs +++ b/tests/ui/object-lifetime/object-lifetime-default-from-rptr.rs @@ -3,7 +3,7 @@ // lifetime bound. -#![allow(dead_code)] +#![allow(dead_code, unused)] use std::fmt::Display; diff --git a/tests/ui/object-lifetime/object-lifetime-default-inferred.rs b/tests/ui/object-lifetime/object-lifetime-default-inferred.rs index 5abe09e272920..f6300c4e25a52 100644 --- a/tests/ui/object-lifetime/object-lifetime-default-inferred.rs +++ b/tests/ui/object-lifetime/object-lifetime-default-inferred.rs @@ -3,7 +3,7 @@ // valid. -#![allow(dead_code)] +#![allow(dead_code, unused)] #![feature(generic_arg_infer)] trait Test { diff --git a/tests/ui/packed/packed-struct-drop-aligned.rs b/tests/ui/packed/packed-struct-drop-aligned.rs index ba3dcb10c61e5..e8125115d45e6 100644 --- a/tests/ui/packed/packed-struct-drop-aligned.rs +++ b/tests/ui/packed/packed-struct-drop-aligned.rs @@ -1,6 +1,8 @@ //@ run-pass #![feature(coroutines, stmt_expr_attributes)] #![feature(coroutine_trait)] +#![allow(unused_assignments, unused_variables)] + use std::cell::Cell; use std::mem; use std::ops::Coroutine; diff --git a/tests/ui/parser/intersection-patterns-1.fixed b/tests/ui/parser/intersection-patterns-1.fixed index 8ade795f7eef9..3ac9837e1a4da 100644 --- a/tests/ui/parser/intersection-patterns-1.fixed +++ b/tests/ui/parser/intersection-patterns-1.fixed @@ -9,6 +9,7 @@ //@ run-rustfix #![allow(unused_variables)] +#![allow(unused_assignments)] fn main() { let s: Option = None; diff --git a/tests/ui/parser/intersection-patterns-1.rs b/tests/ui/parser/intersection-patterns-1.rs index b5a7892fd1c5a..aab3655540505 100644 --- a/tests/ui/parser/intersection-patterns-1.rs +++ b/tests/ui/parser/intersection-patterns-1.rs @@ -9,6 +9,7 @@ //@ run-rustfix #![allow(unused_variables)] +#![allow(unused_assignments)] fn main() { let s: Option = None; diff --git a/tests/ui/parser/intersection-patterns-1.stderr b/tests/ui/parser/intersection-patterns-1.stderr index c191b46fa45db..8bcb884d8e7cb 100644 --- a/tests/ui/parser/intersection-patterns-1.stderr +++ b/tests/ui/parser/intersection-patterns-1.stderr @@ -1,5 +1,5 @@ error: pattern on wrong side of `@` - --> $DIR/intersection-patterns-1.rs:17:9 + --> $DIR/intersection-patterns-1.rs:18:9 | LL | Some(x) @ y => {} | -------^^^- @@ -14,7 +14,7 @@ LL + y @ Some(x) => {} | error: pattern on wrong side of `@` - --> $DIR/intersection-patterns-1.rs:27:9 + --> $DIR/intersection-patterns-1.rs:28:9 | LL | 1 ..= 5 @ e => {} | -------^^^- diff --git a/tests/ui/pattern/bindings-after-at/bind-by-copy.rs b/tests/ui/pattern/bindings-after-at/bind-by-copy.rs index 3d26b5e87d93f..d766411e4f980 100644 --- a/tests/ui/pattern/bindings-after-at/bind-by-copy.rs +++ b/tests/ui/pattern/bindings-after-at/bind-by-copy.rs @@ -1,5 +1,6 @@ //@ run-pass #![allow(unused)] +#![warn(unused_assignments)] // Test copy @@ -34,10 +35,12 @@ pub fn main() { let mut x@B {b, ..} = B {a: 10, b: C {c: 20}}; assert_eq!(x.a, 10); x.b.c = 30; + //~^ WARN value assigned to `x` is never read assert_eq!(b.c, 20); let mut y@D {d, ..} = D {a: 10, d: C {c: 20}}; assert_eq!(y.a, 10); y.d.c = 30; + //~^ WARN value assigned to `y` is never read assert_eq!(d.c, 20); match (E::E { a: 10, e: C { c: 20 } }) { @@ -50,7 +53,9 @@ pub fn main() { } match (E::E { a: 10, e: C { c: 20 } }) { mut x @ E::E{ a, e: C { mut c } } => { + //~^ WARN value assigned to `a` is never read x = E::NotE; + //~^ WARN value assigned to `x` is never read c += 30; assert_eq!(c, 50); } diff --git a/tests/ui/pattern/bindings-after-at/bind-by-copy.stderr b/tests/ui/pattern/bindings-after-at/bind-by-copy.stderr new file mode 100644 index 0000000000000..d775b69ef0a5e --- /dev/null +++ b/tests/ui/pattern/bindings-after-at/bind-by-copy.stderr @@ -0,0 +1,39 @@ +warning: value assigned to `x` is never read + --> $DIR/bind-by-copy.rs:37:5 + | +LL | x.b.c = 30; + | ^^^^^^^^^^ + | + = help: maybe it is overwritten before being read? +note: the lint level is defined here + --> $DIR/bind-by-copy.rs:3:9 + | +LL | #![warn(unused_assignments)] + | ^^^^^^^^^^^^^^^^^^ + +warning: value assigned to `y` is never read + --> $DIR/bind-by-copy.rs:42:5 + | +LL | y.d.c = 30; + | ^^^^^^^^^^ + | + = help: maybe it is overwritten before being read? + +warning: value assigned to `x` is never read + --> $DIR/bind-by-copy.rs:57:13 + | +LL | x = E::NotE; + | ^^^^^^^^^^^ + | + = help: maybe it is overwritten before being read? + +warning: value assigned to `a` is never read + --> $DIR/bind-by-copy.rs:55:23 + | +LL | mut x @ E::E{ a, e: C { mut c } } => { + | ^ + | + = help: maybe it is overwritten before being read? + +warning: 4 warnings emitted + diff --git a/tests/ui/rfcs/rfc-2005-default-binding-mode/general.rs b/tests/ui/rfcs/rfc-2005-default-binding-mode/general.rs index 3090f68c72b77..b3dc9e5842334 100644 --- a/tests/ui/rfcs/rfc-2005-default-binding-mode/general.rs +++ b/tests/ui/rfcs/rfc-2005-default-binding-mode/general.rs @@ -58,6 +58,7 @@ fn match_with_or() { fn nested_mixed() { match (&Some(5), &Some(6)) { (Some(a), &Some(mut b)) => { + //~^ WARN value assigned to `b` is never read // Here, the `a` will be `&i32`, because in the first half of the tuple // we hit a non-reference pattern and shift into `ref` mode. // diff --git a/tests/ui/rfcs/rfc-2005-default-binding-mode/general.stderr b/tests/ui/rfcs/rfc-2005-default-binding-mode/general.stderr new file mode 100644 index 0000000000000..aa0aed2d8d790 --- /dev/null +++ b/tests/ui/rfcs/rfc-2005-default-binding-mode/general.stderr @@ -0,0 +1,11 @@ +warning: value assigned to `b` is never read + --> $DIR/general.rs:60:25 + | +LL | (Some(a), &Some(mut b)) => { + | ^^^^^ + | + = help: maybe it is overwritten before being read? + = note: `#[warn(unused_assignments)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.default.stderr b/tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.default.stderr new file mode 100644 index 0000000000000..b210619f8d780 --- /dev/null +++ b/tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.default.stderr @@ -0,0 +1,11 @@ +warning: value assigned to `small` is never read + --> $DIR/std-panic-locations.rs:46:31 + | +LL | assert_panicked(move || { small[1] += 1; }); + | ^^^^^^^^^^^^^ + | + = help: maybe it is overwritten before being read? + = note: `#[warn(unused_assignments)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.mir-opt.stderr b/tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.mir-opt.stderr new file mode 100644 index 0000000000000..b210619f8d780 --- /dev/null +++ b/tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.mir-opt.stderr @@ -0,0 +1,11 @@ +warning: value assigned to `small` is never read + --> $DIR/std-panic-locations.rs:46:31 + | +LL | assert_panicked(move || { small[1] += 1; }); + | ^^^^^^^^^^^^^ + | + = help: maybe it is overwritten before being read? + = note: `#[warn(unused_assignments)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.rs b/tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.rs index bd62a6447851f..62338819625d4 100644 --- a/tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.rs +++ b/tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.rs @@ -44,6 +44,7 @@ fn main() { assert_panicked(move || { small[1]; }); assert_panicked(move || { small.index_mut(1); }); assert_panicked(move || { small[1] += 1; }); + //~^ WARN value assigned to `small` is never read let sorted: BTreeMap = Default::default(); assert_panicked(|| { sorted.index(&false); }); diff --git a/tests/ui/rfcs/rfc-2565-param-attrs/param-attrs-cfg.stderr b/tests/ui/rfcs/rfc-2565-param-attrs/param-attrs-cfg.stderr index ba92bc4a71d85..90282793fa284 100644 --- a/tests/ui/rfcs/rfc-2565-param-attrs/param-attrs-cfg.stderr +++ b/tests/ui/rfcs/rfc-2565-param-attrs/param-attrs-cfg.stderr @@ -10,18 +10,6 @@ note: the lint level is defined here LL | #![deny(unused_variables)] | ^^^^^^^^^^^^^^^^ -error: unused variable: `a` - --> $DIR/param-attrs-cfg.rs:40:27 - | -LL | #[cfg(something)] a: i32, - | ^ help: if this is intentional, prefix it with an underscore: `_a` - -error: unused variable: `a` - --> $DIR/param-attrs-cfg.rs:106:27 - | -LL | #[cfg(something)] a: i32, - | ^ help: if this is intentional, prefix it with an underscore: `_a` - error: unused variable: `b` --> $DIR/param-attrs-cfg.rs:29:23 | @@ -34,6 +22,12 @@ error: unused variable: `c` LL | #[cfg_attr(nothing, cfg(nothing))] c: i32, | ^ help: if this is intentional, prefix it with an underscore: `_c` +error: unused variable: `a` + --> $DIR/param-attrs-cfg.rs:40:27 + | +LL | #[cfg(something)] a: i32, + | ^ help: if this is intentional, prefix it with an underscore: `_a` + error: unused variable: `b` --> $DIR/param-attrs-cfg.rs:47:27 | @@ -118,5 +112,11 @@ error: unused variable: `c` LL | #[cfg_attr(nothing, cfg(nothing))] c: i32, | ^ help: if this is intentional, prefix it with an underscore: `_c` +error: unused variable: `a` + --> $DIR/param-attrs-cfg.rs:106:27 + | +LL | #[cfg(something)] a: i32, + | ^ help: if this is intentional, prefix it with an underscore: `_a` + error: aborting due to 19 previous errors diff --git a/tests/ui/suggestions/try-removing-the-field.rs b/tests/ui/suggestions/try-removing-the-field.rs index dc1bde082c4f1..29da1964354ec 100644 --- a/tests/ui/suggestions/try-removing-the-field.rs +++ b/tests/ui/suggestions/try-removing-the-field.rs @@ -10,7 +10,7 @@ struct Foo { fn use_foo(x: Foo) -> i32 { let Foo { foo, bar, .. } = x; //~ WARNING unused variable: `bar` - //~| help: try removing the field + //~| help: try ignoring the field return foo; } @@ -24,7 +24,7 @@ fn use_match(x: Foo) { match x { Foo { foo, .. } => { //~ WARNING unused variable - //~| help: try removing the field + //~| help: try ignoring the field } } } diff --git a/tests/ui/suggestions/try-removing-the-field.stderr b/tests/ui/suggestions/try-removing-the-field.stderr index 7a6013d4a6eab..06175c9d672d6 100644 --- a/tests/ui/suggestions/try-removing-the-field.stderr +++ b/tests/ui/suggestions/try-removing-the-field.stderr @@ -2,9 +2,7 @@ warning: unused variable: `bar` --> $DIR/try-removing-the-field.rs:12:20 | LL | let Foo { foo, bar, .. } = x; - | ^^^- - | | - | help: try removing the field + | ^^^ help: try ignoring the field: `bar: _` | = note: `#[warn(unused_variables)]` on by default @@ -18,9 +16,7 @@ warning: unused variable: `foo` --> $DIR/try-removing-the-field.rs:26:15 | LL | Foo { foo, .. } => { - | ^^^- - | | - | help: try removing the field + | ^^^ help: try ignoring the field: `foo: _` warning: 3 warnings emitted diff --git a/tests/ui/suggestions/unused-closure-argument.stderr b/tests/ui/suggestions/unused-closure-argument.stderr index 55195ce50a13e..9f70501389c43 100644 --- a/tests/ui/suggestions/unused-closure-argument.stderr +++ b/tests/ui/suggestions/unused-closure-argument.stderr @@ -1,8 +1,8 @@ error: unused variable: `x` - --> $DIR/unused-closure-argument.rs:12:23 + --> $DIR/unused-closure-argument.rs:17:15 | -LL | .map(|Point { x, y }| y) - | ^ help: try ignoring the field: `x: _` +LL | .map(|x| 4) + | ^ help: if this is intentional, prefix it with an underscore: `_x` | note: the lint level is defined here --> $DIR/unused-closure-argument.rs:1:9 @@ -11,10 +11,10 @@ LL | #![deny(unused_variables)] | ^^^^^^^^^^^^^^^^ error: unused variable: `x` - --> $DIR/unused-closure-argument.rs:17:15 + --> $DIR/unused-closure-argument.rs:12:23 | -LL | .map(|x| 4) - | ^ help: if this is intentional, prefix it with an underscore: `_x` +LL | .map(|Point { x, y }| y) + | ^ help: try ignoring the field: `x: _` error: aborting due to 2 previous errors diff --git a/tests/ui/type/issue-100584.stderr b/tests/ui/type/issue-100584.stderr index 1523bdda7614b..29b567eb74405 100644 --- a/tests/ui/type/issue-100584.stderr +++ b/tests/ui/type/issue-100584.stderr @@ -2,7 +2,7 @@ error: unused variable: `xyza` --> $DIR/issue-100584.rs:2:8 | LL | fn foo(xyza: &str) { - | ^^^^ unused variable + | ^^^^ LL | LL | let _ = "{xyza}"; | -------- you might have meant to use string interpolation in this string literal @@ -26,7 +26,7 @@ error: unused variable: `xyza` --> $DIR/issue-100584.rs:7:9 | LL | fn foo3(xyza: &str) { - | ^^^^ unused variable + | ^^^^ LL | LL | let _ = "aaa{xyza}bbb"; | -------------- you might have meant to use string interpolation in this string literal diff --git a/tests/ui/type/type-ascription.rs b/tests/ui/type/type-ascription.rs index 76d6d923affc2..3649c707f3046 100644 --- a/tests/ui/type/type-ascription.rs +++ b/tests/ui/type/type-ascription.rs @@ -1,9 +1,8 @@ //@ run-pass -#![allow(dead_code)] -#![allow(unused_variables)] -// Type ascription doesn't lead to unsoundness +#![allow(dead_code, unused_variables, unused_assignments)] +// Type ascription doesn't lead to unsoundness #![feature(type_ascription)] use std::mem; diff --git a/tests/ui/unboxed-closures/unboxed-closures-counter-not-moved.rs b/tests/ui/unboxed-closures/unboxed-closures-counter-not-moved.rs index 42287ac50701b..ee425abfd1e27 100644 --- a/tests/ui/unboxed-closures/unboxed-closures-counter-not-moved.rs +++ b/tests/ui/unboxed-closures/unboxed-closures-counter-not-moved.rs @@ -21,8 +21,9 @@ fn main() { call(move || { // this mutates a moved copy, and hence doesn't affect original - counter += 1; //~ WARN value assigned to `counter` is never read - //~| WARN unused variable: `counter` + counter += 1; + //~^ WARN value captured by `counter` is never read + //~| WARN value assigned to `counter` is never read }); assert_eq!(counter, 88); } diff --git a/tests/ui/unboxed-closures/unboxed-closures-counter-not-moved.stderr b/tests/ui/unboxed-closures/unboxed-closures-counter-not-moved.stderr index 6450cc30ac05f..3f155948866ea 100644 --- a/tests/ui/unboxed-closures/unboxed-closures-counter-not-moved.stderr +++ b/tests/ui/unboxed-closures/unboxed-closures-counter-not-moved.stderr @@ -1,27 +1,27 @@ -warning: unused variable: `item` - --> $DIR/unboxed-closures-counter-not-moved.rs:15:13 +warning: value captured by `counter` is never read + --> $DIR/unboxed-closures-counter-not-moved.rs:24:9 | -LL | for item in y { - | ^^^^ help: if this is intentional, prefix it with an underscore: `_item` +LL | counter += 1; + | ^^^^^^^ | - = note: `#[warn(unused_variables)]` on by default + = help: did you mean to capture by reference instead? + = note: `#[warn(unused_assignments)]` on by default warning: value assigned to `counter` is never read --> $DIR/unboxed-closures-counter-not-moved.rs:24:9 | LL | counter += 1; - | ^^^^^^^ + | ^^^^^^^^^^^^ | = help: maybe it is overwritten before being read? - = note: `#[warn(unused_assignments)]` on by default -warning: unused variable: `counter` - --> $DIR/unboxed-closures-counter-not-moved.rs:24:9 +warning: unused variable: `item` + --> $DIR/unboxed-closures-counter-not-moved.rs:15:13 | -LL | counter += 1; - | ^^^^^^^ +LL | for item in y { + | ^^^^ help: if this is intentional, prefix it with an underscore: `_item` | - = help: did you mean to capture by reference instead? + = note: `#[warn(unused_variables)]` on by default warning: 3 warnings emitted diff --git a/tests/ui/unboxed-closures/unboxed-closures-move-mutable.rs b/tests/ui/unboxed-closures/unboxed-closures-move-mutable.rs index f27461808c390..5710998cda799 100644 --- a/tests/ui/unboxed-closures/unboxed-closures-move-mutable.rs +++ b/tests/ui/unboxed-closures/unboxed-closures-move-mutable.rs @@ -13,11 +13,17 @@ fn set(x: &mut usize) { *x = 42; } fn main() { { let mut x = 0_usize; - move || x += 1; //~ WARN unused variable: `x` + //~^ WARN unused variable: `x` + move || x += 1; + //~^ WARN value captured by `x` is never read + //~| WARN value assigned to `x` is never read } { let mut x = 0_usize; - move || x += 1; //~ WARN unused variable: `x` + //~^ WARN unused variable: `x` + move || x += 1; + //~^ WARN value captured by `x` is never read + //~| WARN value assigned to `x` is never read } { let mut x = 0_usize; diff --git a/tests/ui/unboxed-closures/unboxed-closures-move-mutable.stderr b/tests/ui/unboxed-closures/unboxed-closures-move-mutable.stderr index 813e2eea56848..1a4106712b648 100644 --- a/tests/ui/unboxed-closures/unboxed-closures-move-mutable.stderr +++ b/tests/ui/unboxed-closures/unboxed-closures-move-mutable.stderr @@ -1,19 +1,49 @@ -warning: unused variable: `x` - --> $DIR/unboxed-closures-move-mutable.rs:16:17 +warning: value captured by `x` is never read + --> $DIR/unboxed-closures-move-mutable.rs:24:17 | LL | move || x += 1; | ^ | = help: did you mean to capture by reference instead? - = note: `#[warn(unused_variables)]` on by default + = note: `#[warn(unused_assignments)]` on by default -warning: unused variable: `x` - --> $DIR/unboxed-closures-move-mutable.rs:20:17 +warning: value assigned to `x` is never read + --> $DIR/unboxed-closures-move-mutable.rs:24:17 + | +LL | move || x += 1; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +warning: value captured by `x` is never read + --> $DIR/unboxed-closures-move-mutable.rs:17:17 | LL | move || x += 1; | ^ | = help: did you mean to capture by reference instead? -warning: 2 warnings emitted +warning: value assigned to `x` is never read + --> $DIR/unboxed-closures-move-mutable.rs:17:17 + | +LL | move || x += 1; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +warning: unused variable: `x` + --> $DIR/unboxed-closures-move-mutable.rs:15:13 + | +LL | let mut x = 0_usize; + | ^^^^^ help: if this is intentional, prefix it with an underscore: `_x` + | + = note: `#[warn(unused_variables)]` on by default + +warning: unused variable: `x` + --> $DIR/unboxed-closures-move-mutable.rs:22:13 + | +LL | let mut x = 0_usize; + | ^^^^^ help: if this is intentional, prefix it with an underscore: `_x` + +warning: 6 warnings emitted From 7104646da55387d24915984388623f1bf9cf4b2c Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Fri, 13 Jun 2025 00:57:31 +0000 Subject: [PATCH 6/7] Split main routine. --- compiler/rustc_mir_transform/src/liveness.rs | 818 ++++++++++--------- 1 file changed, 442 insertions(+), 376 deletions(-) diff --git a/compiler/rustc_mir_transform/src/liveness.rs b/compiler/rustc_mir_transform/src/liveness.rs index 9384d6dc58e35..3d46e08930ccd 100644 --- a/compiler/rustc_mir_transform/src/liveness.rs +++ b/compiler/rustc_mir_transform/src/liveness.rs @@ -33,19 +33,14 @@ enum CaptureKind { None, } -struct AssignmentResult { - /// Set of locals that are live at least once. This is used to report fully unused locals. - ever_live: DenseBitSet, - /// Set of locals that have a non-trivial drop. This is used to skip reporting unused - /// assignment if it would be used by the `Drop` impl. - ever_dropped: DenseBitSet, - /// Set of assignments for each local. Here, assignment is understood in the AST sense. Any - /// MIR that may look like an assignment (Assign, DropAndReplace, Yield, Call) are considered. - /// - /// For each local, we return a map: for each source position, whether the statement is live - /// and which kind of access it performs. When we encounter multiple statements at the same - /// location, we only increase the liveness, in order to avoid false positives. - assignments: IndexVec>, +#[derive(Copy, Clone, Debug)] +struct Access { + /// Describe the current access. + kind: AccessKind, + /// Is the accessed place is live at the current statement? + /// When we encounter multiple statements at the same location, we only increase the liveness, + /// in order to avoid false positives. + live: bool, } #[tracing::instrument(level = "debug", skip(tcx), ret)] @@ -138,269 +133,15 @@ pub fn check_liveness<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> DenseBitSe .iterate_to_fixpoint(tcx, body, None) .into_results_cursor(body); - let AssignmentResult { mut ever_live, ever_dropped, mut assignments } = - find_dead_assignments(tcx, &checked_places, &mut live, body); - - // Match guards introduce a different local to freeze the guarded value as immutable. - // Having two locals, we need to make sure that we do not report an unused_variable - // when the guard local is used but not the arm local, or vice versa, like in this example. - // - // match 5 { - // x if x > 2 => {} - // ^ ^- This is `local` - // +------ This is `arm_local` - // _ => {} - // } - // - for (index, place) in checked_places.iter() { - let local = place.local; - if let &LocalInfo::User(BindingForm::RefForGuard(arm_local)) = - body.local_decls[local].local_info() - { - debug_assert!(place.projection.is_empty()); - - // Local to use in the arm. - let Some((arm_index, _proj)) = checked_places.get(arm_local.into()) else { continue }; - debug_assert_ne!(index, arm_index); - debug_assert_eq!(_proj, &[]); - - // Mark the arm local as used if the guard local is used. - if ever_live.contains(index) { - ever_live.insert(arm_index); - } - - // Some assignments are common to both locals in the source code. - // Sadly, we can only detect this using the `source_info`. - // Therefore, we loop over all the assignments we have for the guard local: - // - if they already appeared for the arm local, the assignment is live if one of the - // two versions is live; - // - if it does not appear for the arm local, it happened inside the guard, so we add - // it as-is. - let guard_assignments = std::mem::take(&mut assignments[index]); - let arm_assignments = &mut assignments[arm_index]; - for (source_info, (live, kind)) in guard_assignments { - match arm_assignments.entry(source_info) { - IndexEntry::Vacant(v) => { - v.insert((live, kind)); - } - IndexEntry::Occupied(mut o) => { - o.get_mut().0 |= live; - } - } - } - } - } - - // Report to caller the set of dead captures. - let mut dead_captures = DenseBitSet::new_empty(num_captures); - - // First, report fully unused locals. - for (index, place) in checked_places.iter() { - if ever_live.contains(index) { - continue; - } - - // This is a capture: let the enclosing function report the unused variable. - if is_capture(*place) { - debug_assert_eq!(place.local, ty::CAPTURE_STRUCT_LOCAL); - for p in place.projection { - if let PlaceElem::Field(f, _) = p { - dead_captures.insert(*f); - break; - } - } - continue; - } - - let Some((ref name, def_span)) = checked_places.names[index] else { continue }; - if name.is_empty() || name.starts_with('_') || name == "self" { - continue; - } - - let local = place.local; - let decl = &body.local_decls[local]; - - if decl.from_compiler_desugaring() { - continue; - } - - // Only report actual user-defined binding from now on. - let LocalInfo::User(BindingForm::Var(binding)) = decl.local_info() else { continue }; - let Some(hir_id) = decl.source_info.scope.lint_root(&body.source_scopes) else { continue }; - - let introductions = &binding.introductions; - - // #117284, when `ident_span` and `def_span` have different contexts - // we can't provide a good suggestion, instead we pointed out the spans from macro - let from_macro = def_span.from_expansion() - && introductions.iter().any(|(ident_span, _)| ident_span.eq_ctxt(def_span)); - - let statements = &mut assignments[index]; - if statements.is_empty() { - let sugg = if from_macro { - errors::UnusedVariableSugg::NoSugg { span: def_span, name: name.clone() } - } else { - errors::UnusedVariableSugg::TryPrefix { spans: vec![def_span], name: name.clone() } - }; - tcx.emit_node_span_lint( - lint::builtin::UNUSED_VARIABLES, - hir_id, - def_span, - errors::UnusedVariable { - name: name.clone(), - string_interp: maybe_suggest_literal_matching_name(body, name), - sugg, - }, - ); - continue; - } - - // Idiomatic rust assigns a value to a local upon definition. However, we do not want to - // warn twice, for the unused local and for the unused assignment. Therefore, we remove - // from the list of assignments the ones that happen at the definition site. - statements.retain(|source_info, _| { - source_info.span.find_ancestor_inside(binding.pat_span).is_none() - }); - - // Extra assignments that we recognize thanks to the initialization span. We need to - // take care of macro contexts here to be accurate. - if let Some((_, initializer_span)) = binding.opt_match_place { - statements.retain(|source_info, _| { - let within = source_info.span.find_ancestor_inside(initializer_span); - let outer_initializer_span = - initializer_span.find_ancestor_in_same_ctxt(source_info.span); - within.is_none() - && outer_initializer_span.map_or(true, |s| !s.contains(source_info.span)) - }); - } - - if !statements.is_empty() { - // We have a dead local with outstanding assignments and with non-trivial drop. - // This is probably a drop-guard, so we do not issue a warning there. - if ever_dropped.contains(index) { - continue; - } - - tcx.emit_node_span_lint( - lint::builtin::UNUSED_VARIABLES, - hir_id, - def_span, - errors::UnusedVarAssignedOnly { name: name.clone() }, - ); - continue; - } - - // We do not have outstanding assignments, suggest renaming the binding. - let spans = introductions.iter().map(|(span, _)| *span).collect::>(); - - let any_shorthand = introductions.iter().any(|(_, is_shorthand)| *is_shorthand); - - let sugg = if any_shorthand { - errors::UnusedVariableSugg::TryIgnore { - name: name.clone(), - shorthands: introductions - .iter() - .filter_map( - |&(span, is_shorthand)| { - if is_shorthand { Some(span) } else { None } - }, - ) - .collect(), - non_shorthands: introductions - .iter() - .filter_map( - |&(span, is_shorthand)| { - if !is_shorthand { Some(span) } else { None } - }, - ) - .collect(), - } - } else if from_macro { - errors::UnusedVariableSugg::NoSugg { span: def_span, name: name.clone() } - } else if !introductions.is_empty() { - errors::UnusedVariableSugg::TryPrefix { - name: name.clone(), - spans: introductions.iter().map(|&(span, _)| span).collect(), - } - } else { - errors::UnusedVariableSugg::TryPrefix { name: name.clone(), spans: vec![def_span] } - }; - - tcx.emit_node_span_lint( - lint::builtin::UNUSED_VARIABLES, - hir_id, - spans, - errors::UnusedVariable { - name: name.clone(), - string_interp: maybe_suggest_literal_matching_name(body, name), - sugg, - }, - ); - } - - // Second, report unused assignments that do not correspond to initialization. - // Initializations have been removed in the previous loop reporting unused variables. - for (index, statements) in assignments.into_iter_enumerated() { - if statements.is_empty() { - continue; - } - - let Some((ref name, decl_span)) = checked_places.names[index] else { continue }; - if name.is_empty() || name.starts_with('_') || name == "self" { - continue; - } - - // We have outstanding assignments and with non-trivial drop. - // This is probably a drop-guard, so we do not issue a warning there. - if ever_dropped.contains(index) { - continue; - } + let mut assignments = + AssignmentResult::find_dead_assignments(tcx, &checked_places, &mut live, body); - // We probed MIR in reverse order for dataflow. - // We revert the vector to give a consistent order to the user. - for (source_info, (live, kind)) in statements.into_iter().rev() { - if live { - continue; - } + assignments.merge_guards(&checked_places, body); - // Report the dead assignment. - let Some(hir_id) = source_info.scope.lint_root(&body.source_scopes) else { continue }; + let dead_captures = assignments.compute_dead_captures(&checked_places, num_captures); - match kind { - AccessKind::Assign => { - let suggestion = annotate_mut_binding_to_immutable_binding( - tcx, - checked_places.places[index], - def_id, - source_info.span, - body, - ); - tcx.emit_node_span_lint( - lint::builtin::UNUSED_ASSIGNMENTS, - hir_id, - source_info.span, - errors::UnusedAssign { - name: name.clone(), - help: suggestion.is_none(), - suggestion, - }, - ) - } - AccessKind::Param => tcx.emit_node_span_lint( - lint::builtin::UNUSED_ASSIGNMENTS, - hir_id, - source_info.span, - errors::UnusedAssignPassed { name: name.clone() }, - ), - AccessKind::Capture => tcx.emit_node_span_lint( - lint::builtin::UNUSED_ASSIGNMENTS, - hir_id, - decl_span, - errors::UnusedCaptureMaybeCaptureRef { name: name.clone() }, - ), - } - } - } + assignments.report_fully_unused(tcx, &checked_places, body); + assignments.report_unused_assignments(tcx, def_id, &checked_places, body); dead_captures } @@ -795,139 +536,464 @@ impl<'tcx> PlaceSet<'tcx> { } } -/// Collect all assignments to checked locals. -/// -/// Assignments are collected, even if they are live. Dead assignments are reported, and live -/// assignments are used to make diagnostics correct for match guards. -fn find_dead_assignments<'tcx>( - tcx: TyCtxt<'tcx>, - checked_places: &PlaceSet<'tcx>, - cursor: &mut ResultsCursor<'_, 'tcx, MaybeLivePlaces<'_, 'tcx>>, - body: &Body<'tcx>, -) -> AssignmentResult { - let mut ever_live = DenseBitSet::new_empty(checked_places.len()); - let mut ever_dropped = DenseBitSet::new_empty(checked_places.len()); - let mut assignments = IndexVec::>::from_elem( - Default::default(), - &checked_places.places, - ); - - let mut check_place = - |place: Place<'tcx>, kind, source_info: SourceInfo, live: &DenseBitSet| { - if let Some((index, extra_projections)) = checked_places.get(place.as_ref()) { - if !is_indirect(extra_projections) { - match assignments[index].entry(source_info) { - IndexEntry::Vacant(v) => { - v.insert((live.contains(index), kind)); - } - IndexEntry::Occupied(mut o) => { - // There were already a sighting. Mark this statement as live if it was, - // to avoid false positives. - o.get_mut().0 |= live.contains(index); +struct AssignmentResult { + /// Set of locals that are live at least once. This is used to report fully unused locals. + ever_live: DenseBitSet, + /// Set of locals that have a non-trivial drop. This is used to skip reporting unused + /// assignment if it would be used by the `Drop` impl. + ever_dropped: DenseBitSet, + /// Set of assignments for each local. Here, assignment is understood in the AST sense. Any + /// MIR that may look like an assignment (Assign, DropAndReplace, Yield, Call) are considered. + /// + /// For each local, we return a map: for each source position, whether the statement is live + /// and which kind of access it performs. When we encounter multiple statements at the same + /// location, we only increase the liveness, in order to avoid false positives. + assignments: IndexVec>, +} + +impl AssignmentResult { + /// Collect all assignments to checked locals. + /// + /// Assignments are collected, even if they are live. Dead assignments are reported, and live + /// assignments are used to make diagnostics correct for match guards. + fn find_dead_assignments<'tcx>( + tcx: TyCtxt<'tcx>, + checked_places: &PlaceSet<'tcx>, + cursor: &mut ResultsCursor<'_, 'tcx, MaybeLivePlaces<'_, 'tcx>>, + body: &Body<'tcx>, + ) -> AssignmentResult { + let mut ever_live = DenseBitSet::new_empty(checked_places.len()); + let mut ever_dropped = DenseBitSet::new_empty(checked_places.len()); + let mut assignments = IndexVec::>::from_elem( + Default::default(), + &checked_places.places, + ); + + let mut check_place = + |place: Place<'tcx>, kind, source_info: SourceInfo, live: &DenseBitSet| { + if let Some((index, extra_projections)) = checked_places.get(place.as_ref()) { + if !is_indirect(extra_projections) { + match assignments[index].entry(source_info) { + IndexEntry::Vacant(v) => { + let access = Access { kind, live: live.contains(index) }; + v.insert(access); + } + IndexEntry::Occupied(mut o) => { + // There were already a sighting. Mark this statement as live if it + // was, to avoid false positives. + o.get_mut().live |= live.contains(index); + } } } } + }; + + let typing_env = ty::TypingEnv::post_analysis(tcx, body.source.def_id()); + let mut record_drop = |place: Place<'tcx>| { + if let Some((index, &[])) = checked_places.get(place.as_ref()) { + let ty = place.ty(&body.local_decls, tcx).ty; + let needs_drop = matches!( + ty.kind(), + ty::Closure(..) + | ty::Coroutine(..) + | ty::Tuple(..) + | ty::Adt(..) + | ty::Dynamic(..) + | ty::Array(..) + | ty::Slice(..) + | ty::Alias(ty::Opaque, ..) + ) && ty.needs_drop(tcx, typing_env); + if needs_drop { + ever_dropped.insert(index); + } } }; - let typing_env = ty::TypingEnv::post_analysis(tcx, body.source.def_id()); - let mut record_drop = |place: Place<'tcx>| { - if let Some((index, &[])) = checked_places.get(place.as_ref()) { - let ty = place.ty(&body.local_decls, tcx).ty; - let needs_drop = matches!( - ty.kind(), - ty::Closure(..) - | ty::Coroutine(..) - | ty::Tuple(..) - | ty::Adt(..) - | ty::Dynamic(..) - | ty::Array(..) - | ty::Slice(..) - | ty::Alias(ty::Opaque, ..) - ) && ty.needs_drop(tcx, typing_env); - if needs_drop { - ever_dropped.insert(index); - } - } - }; + for (bb, bb_data) in traversal::postorder(body) { + cursor.seek_to_block_end(bb); + let live = cursor.get(); + ever_live.union(live); - for (bb, bb_data) in traversal::postorder(body) { - cursor.seek_to_block_end(bb); - let live = cursor.get(); - ever_live.union(live); - - let terminator = bb_data.terminator(); - match &terminator.kind { - TerminatorKind::Call { destination: place, .. } - | TerminatorKind::Yield { resume_arg: place, .. } => { - check_place(*place, AccessKind::Assign, terminator.source_info, live); - record_drop(*place) + let terminator = bb_data.terminator(); + match &terminator.kind { + TerminatorKind::Call { destination: place, .. } + | TerminatorKind::Yield { resume_arg: place, .. } => { + check_place(*place, AccessKind::Assign, terminator.source_info, live); + record_drop(*place) + } + TerminatorKind::Drop { place, .. } => record_drop(*place), + TerminatorKind::InlineAsm { operands, .. } => { + for operand in operands { + if let InlineAsmOperand::Out { place: Some(place), .. } + | InlineAsmOperand::InOut { out_place: Some(place), .. } = operand + { + check_place(*place, AccessKind::Assign, terminator.source_info, live); + } + } + } + _ => {} } - TerminatorKind::Drop { place, .. } => record_drop(*place), - TerminatorKind::InlineAsm { operands, .. } => { - for operand in operands { - if let InlineAsmOperand::Out { place: Some(place), .. } - | InlineAsmOperand::InOut { out_place: Some(place), .. } = operand - { - check_place(*place, AccessKind::Assign, terminator.source_info, live); + + for (statement_index, statement) in bb_data.statements.iter().enumerate().rev() { + cursor.seek_before_primary_effect(Location { block: bb, statement_index }); + let live = cursor.get(); + ever_live.union(live); + match &statement.kind { + StatementKind::Assign(box (place, _)) + | StatementKind::Deinit(box place) + | StatementKind::SetDiscriminant { box place, .. } => { + check_place(*place, AccessKind::Assign, statement.source_info, live); } + StatementKind::Retag(_, _) + | StatementKind::StorageLive(_) + | StatementKind::StorageDead(_) + | StatementKind::Coverage(_) + | StatementKind::Intrinsic(_) + | StatementKind::Nop + | StatementKind::FakeRead(_) + | StatementKind::PlaceMention(_) + | StatementKind::ConstEvalCounter + | StatementKind::BackwardIncompatibleDropHint { .. } + | StatementKind::AscribeUserType(_, _) => (), } } - _ => {} } - for (statement_index, statement) in bb_data.statements.iter().enumerate().rev() { - cursor.seek_before_primary_effect(Location { block: bb, statement_index }); + // Check liveness of function arguments on entry. + { + cursor.seek_to_block_start(START_BLOCK); let live = cursor.get(); ever_live.union(live); - match &statement.kind { - StatementKind::Assign(box (place, _)) - | StatementKind::Deinit(box place) - | StatementKind::SetDiscriminant { box place, .. } => { - check_place(*place, AccessKind::Assign, statement.source_info, live); + + // Verify that arguments and captured values are useful. + for (index, place) in checked_places.iter() { + let kind = if is_capture(*place) { + // This is a by-ref capture, an assignment to it will modify surrounding + // environment, so we do not report it. + if place.projection.last() == Some(&PlaceElem::Deref) { + continue; + } + + AccessKind::Capture + } else if body.local_kind(place.local) == LocalKind::Arg { + AccessKind::Param + } else { + continue; + }; + let source_info = body.local_decls[place.local].source_info; + let access = Access { kind, live: live.contains(index) }; + assignments[index].insert(source_info, access); + } + } + + AssignmentResult { ever_live, ever_dropped, assignments } + } + + /// Match guards introduce a different local to freeze the guarded value as immutable. + /// Having two locals, we need to make sure that we do not report an unused_variable + /// when the guard local is used but not the arm local, or vice versa, like in this example. + /// + /// match 5 { + /// x if x > 2 => {} + /// ^ ^- This is `local` + /// +------ This is `arm_local` + /// _ => {} + /// } + /// + fn merge_guards<'tcx>(&mut self, checked_places: &PlaceSet<'tcx>, body: &Body<'_>) { + for (index, place) in checked_places.iter() { + let local = place.local; + if let &LocalInfo::User(BindingForm::RefForGuard(arm_local)) = + body.local_decls[local].local_info() + { + debug_assert!(place.projection.is_empty()); + + // Local to use in the arm. + let Some((arm_index, _proj)) = checked_places.get(arm_local.into()) else { + continue; + }; + debug_assert_ne!(index, arm_index); + debug_assert_eq!(_proj, &[]); + + // Mark the arm local as used if the guard local is used. + if self.ever_live.contains(index) { + self.ever_live.insert(arm_index); + } + + // Some assignments are common to both locals in the source code. + // Sadly, we can only detect this using the `source_info`. + // Therefore, we loop over all the assignments we have for the guard local: + // - if they already appeared for the arm local, the assignment is live if one of the + // two versions is live; + // - if it does not appear for the arm local, it happened inside the guard, so we add + // it as-is. + let guard_assignments = std::mem::take(&mut self.assignments[index]); + let arm_assignments = &mut self.assignments[arm_index]; + for (source_info, access) in guard_assignments { + match arm_assignments.entry(source_info) { + IndexEntry::Vacant(v) => { + v.insert(access); + } + IndexEntry::Occupied(mut o) => { + o.get_mut().live |= access.live; + } + } } - StatementKind::Retag(_, _) - | StatementKind::StorageLive(_) - | StatementKind::StorageDead(_) - | StatementKind::Coverage(_) - | StatementKind::Intrinsic(_) - | StatementKind::Nop - | StatementKind::FakeRead(_) - | StatementKind::PlaceMention(_) - | StatementKind::ConstEvalCounter - | StatementKind::BackwardIncompatibleDropHint { .. } - | StatementKind::AscribeUserType(_, _) => (), } } } - // Check liveness of function arguments on entry. - { - cursor.seek_to_block_start(START_BLOCK); - let live = cursor.get(); - ever_live.union(live); + /// Compute captures that are fully dead. + fn compute_dead_captures<'tcx>( + &self, + checked_places: &PlaceSet<'tcx>, + num_captures: usize, + ) -> DenseBitSet { + // Report to caller the set of dead captures. + let mut dead_captures = DenseBitSet::new_empty(num_captures); + for (index, place) in checked_places.iter() { + if self.ever_live.contains(index) { + continue; + } + + // This is a capture: pass information to the enclosing function. + if is_capture(*place) { + for p in place.projection { + if let PlaceElem::Field(f, _) = p { + dead_captures.insert(*f); + break; + } + } + continue; + } + } - // Verify that arguments and captured values are useful. + dead_captures + } + + /// Report fully unused locals, and forget the corresponding assignments. + fn report_fully_unused<'tcx>( + &mut self, + tcx: TyCtxt<'tcx>, + checked_places: &PlaceSet<'tcx>, + body: &Body<'tcx>, + ) { + // First, report fully unused locals. for (index, place) in checked_places.iter() { - let kind = if is_capture(*place) { - // This is a by-ref capture, an assignment to it will modify surrounding - // environment, so we do not report it. - if place.projection.last() == Some(&PlaceElem::Deref) { + if self.ever_live.contains(index) { + continue; + } + + // this is a capture: let the enclosing function report the unused variable. + if is_capture(*place) { + continue; + } + + let Some((ref name, def_span)) = checked_places.names[index] else { continue }; + if name.is_empty() || name.starts_with('_') || name == "self" { + continue; + } + + let local = place.local; + let decl = &body.local_decls[local]; + + if decl.from_compiler_desugaring() { + continue; + } + + // Only report actual user-defined binding from now on. + let LocalInfo::User(BindingForm::Var(binding)) = decl.local_info() else { continue }; + let Some(hir_id) = decl.source_info.scope.lint_root(&body.source_scopes) else { + continue; + }; + + let introductions = &binding.introductions; + + // #117284, when `ident_span` and `def_span` have different contexts + // we can't provide a good suggestion, instead we pointed out the spans from macro + let from_macro = def_span.from_expansion() + && introductions.iter().any(|(ident_span, _)| ident_span.eq_ctxt(def_span)); + + let statements = &mut self.assignments[index]; + if statements.is_empty() { + let sugg = if from_macro { + errors::UnusedVariableSugg::NoSugg { span: def_span, name: name.clone() } + } else { + errors::UnusedVariableSugg::TryPrefix { + spans: vec![def_span], + name: name.clone(), + } + }; + tcx.emit_node_span_lint( + lint::builtin::UNUSED_VARIABLES, + hir_id, + def_span, + errors::UnusedVariable { + name: name.clone(), + string_interp: maybe_suggest_literal_matching_name(body, name), + sugg, + }, + ); + continue; + } + + // Idiomatic rust assigns a value to a local upon definition. However, we do not want to + // warn twice, for the unused local and for the unused assignment. Therefore, we remove + // from the list of assignments the ones that happen at the definition site. + statements.retain(|source_info, _| { + source_info.span.find_ancestor_inside(binding.pat_span).is_none() + }); + + // Extra assignments that we recognize thanks to the initialization span. We need to + // take care of macro contexts here to be accurate. + if let Some((_, initializer_span)) = binding.opt_match_place { + statements.retain(|source_info, _| { + let within = source_info.span.find_ancestor_inside(initializer_span); + let outer_initializer_span = + initializer_span.find_ancestor_in_same_ctxt(source_info.span); + within.is_none() + && outer_initializer_span.map_or(true, |s| !s.contains(source_info.span)) + }); + } + + if !statements.is_empty() { + // We have a dead local with outstanding assignments and with non-trivial drop. + // This is probably a drop-guard, so we do not issue a warning there. + if self.ever_dropped.contains(index) { continue; } - AccessKind::Capture - } else if body.local_kind(place.local) == LocalKind::Arg { - AccessKind::Param - } else { + tcx.emit_node_span_lint( + lint::builtin::UNUSED_VARIABLES, + hir_id, + def_span, + errors::UnusedVarAssignedOnly { name: name.clone() }, + ); continue; + } + + // We do not have outstanding assignments, suggest renaming the binding. + let spans = introductions.iter().map(|(span, _)| *span).collect::>(); + + let any_shorthand = introductions.iter().any(|(_, is_shorthand)| *is_shorthand); + + let sugg = if any_shorthand { + errors::UnusedVariableSugg::TryIgnore { + name: name.clone(), + shorthands: introductions + .iter() + .filter_map( + |&(span, is_shorthand)| { + if is_shorthand { Some(span) } else { None } + }, + ) + .collect(), + non_shorthands: introductions + .iter() + .filter_map( + |&(span, is_shorthand)| { + if !is_shorthand { Some(span) } else { None } + }, + ) + .collect(), + } + } else if from_macro { + errors::UnusedVariableSugg::NoSugg { span: def_span, name: name.clone() } + } else if !introductions.is_empty() { + errors::UnusedVariableSugg::TryPrefix { + name: name.clone(), + spans: introductions.iter().map(|&(span, _)| span).collect(), + } + } else { + errors::UnusedVariableSugg::TryPrefix { name: name.clone(), spans: vec![def_span] } }; - let source_info = body.local_decls[place.local].source_info; - assignments[index].insert(source_info, (live.contains(index), kind)); + + tcx.emit_node_span_lint( + lint::builtin::UNUSED_VARIABLES, + hir_id, + spans, + errors::UnusedVariable { + name: name.clone(), + string_interp: maybe_suggest_literal_matching_name(body, name), + sugg, + }, + ); } } - AssignmentResult { ever_live, ever_dropped, assignments } + /// Second, report unused assignments that do not correspond to initialization. + /// Initializations have been removed in the previous loop reporting unused variables. + fn report_unused_assignments<'tcx>( + self, + tcx: TyCtxt<'tcx>, + body_def_id: LocalDefId, + checked_places: &PlaceSet<'tcx>, + body: &Body<'tcx>, + ) { + for (index, statements) in self.assignments.into_iter_enumerated() { + if statements.is_empty() { + continue; + } + + let Some((ref name, decl_span)) = checked_places.names[index] else { continue }; + if name.is_empty() || name.starts_with('_') || name == "self" { + continue; + } + + // We have outstanding assignments and with non-trivial drop. + // This is probably a drop-guard, so we do not issue a warning there. + if self.ever_dropped.contains(index) { + continue; + } + + // We probed MIR in reverse order for dataflow. + // We revert the vector to give a consistent order to the user. + for (source_info, Access { live, kind }) in statements.into_iter().rev() { + if live { + continue; + } + + // Report the dead assignment. + let Some(hir_id) = source_info.scope.lint_root(&body.source_scopes) else { + continue; + }; + + match kind { + AccessKind::Assign => { + let suggestion = annotate_mut_binding_to_immutable_binding( + tcx, + checked_places.places[index], + body_def_id, + source_info.span, + body, + ); + tcx.emit_node_span_lint( + lint::builtin::UNUSED_ASSIGNMENTS, + hir_id, + source_info.span, + errors::UnusedAssign { + name: name.clone(), + help: suggestion.is_none(), + suggestion, + }, + ) + } + AccessKind::Param => tcx.emit_node_span_lint( + lint::builtin::UNUSED_ASSIGNMENTS, + hir_id, + source_info.span, + errors::UnusedAssignPassed { name: name.clone() }, + ), + AccessKind::Capture => tcx.emit_node_span_lint( + lint::builtin::UNUSED_ASSIGNMENTS, + hir_id, + decl_span, + errors::UnusedCaptureMaybeCaptureRef { name: name.clone() }, + ), + } + } + } + } } rustc_index::newtype_index! { From 1d8df215597cb0ad95cc8b642994c1808454fd05 Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Fri, 13 Jun 2025 01:11:59 +0000 Subject: [PATCH 7/7] Reduce visibility. --- compiler/rustc_mir_transform/src/liveness.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_mir_transform/src/liveness.rs b/compiler/rustc_mir_transform/src/liveness.rs index 3d46e08930ccd..07299bec4a36f 100644 --- a/compiler/rustc_mir_transform/src/liveness.rs +++ b/compiler/rustc_mir_transform/src/liveness.rs @@ -44,7 +44,7 @@ struct Access { } #[tracing::instrument(level = "debug", skip(tcx), ret)] -pub fn check_liveness<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> DenseBitSet { +pub(crate) fn check_liveness<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> DenseBitSet { // Don't run on synthetic MIR, as that will ICE trying to access HIR. if tcx.is_synthetic_mir(def_id) { return DenseBitSet::new_empty(0);