Diagnose liveness on MIR.

This commit is contained in:
Camille GILLOT
2025-06-10 18:17:09 +00:00
committed by Camille Gillot
parent 96b70fc3a1
commit ca0379d6cd
88 changed files with 2592 additions and 2761 deletions
+1
View File
@@ -1095,6 +1095,7 @@ fn run_required_analyses(tcx: TyCtxt<'_>) {
tcx.ensure_ok().check_transmutes(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
+3 -3
View File
@@ -894,7 +894,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),
}
#[derive(Clone, Debug, TyEncodable, TyDecodable, HashStable)]
@@ -917,7 +917,7 @@ fn hash_stable(&self, hcx: &mut StableHashingContext<'a>, hasher: &mut StableHas
match self {
Var(binding) => binding.hash_stable(hcx, hasher),
ImplicitSelf(kind) => kind.hash_stable(hcx, hasher),
RefForGuard => (),
RefForGuard(local) => local.hash_stable(hcx, hasher),
}
}
}
@@ -1129,7 +1129,7 @@ pub fn is_user_variable(&self) -> bool {
/// 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
+1 -1
View File
@@ -259,7 +259,7 @@ pub fn projection_ty_core<V, T>(
impl<V, T> ProjectionElem<V, T> {
/// 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,
+4 -2
View File
@@ -1195,8 +1195,10 @@
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<abi::FieldIdx> {
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.
+5
View File
@@ -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),
}
}
@@ -2864,7 +2864,7 @@ fn declare_binding(
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) {
@@ -69,11 +69,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.
+27
View File
@@ -34,12 +34,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
@@ -77,3 +81,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
+109 -1
View File
@@ -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::query::Key;
@@ -158,6 +158,114 @@ pub(crate) struct FnItemRef {
pub ident: Ident,
}
#[derive(LintDiagnostic)]
#[diag(mir_transform_unused_capture_maybe_capture_ref)]
#[help]
pub(crate) struct UnusedCaptureMaybeCaptureRef {
pub name: Symbol,
}
#[derive(LintDiagnostic)]
#[diag(mir_transform_unused_var_assigned_only)]
#[note]
pub(crate) struct UnusedVarAssignedOnly {
pub name: Symbol,
}
#[derive(LintDiagnostic)]
#[diag(mir_transform_unused_assign)]
pub(crate) struct UnusedAssign {
pub name: Symbol,
#[subdiagnostic]
pub suggestion: Option<UnusedAssignSuggestion>,
#[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<Span>,
#[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: Symbol,
}
#[derive(LintDiagnostic)]
#[diag(mir_transform_unused_variable)]
pub(crate) struct UnusedVariable {
pub name: Symbol,
#[subdiagnostic]
pub string_interp: Vec<UnusedVariableStringInterp>,
#[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<Span>,
#[suggestion_part(code = "_")]
non_shorthands: Vec<Span>,
name: Symbol,
},
#[multipart_suggestion(
mir_transform_unused_var_underscore,
applicability = "machine-applicable"
)]
TryPrefix {
#[suggestion_part(code = "_{name}")]
spans: Vec<Span>,
name: Symbol,
},
#[help(mir_transform_unused_variable_args_in_macro)]
NoSugg {
#[primary_span]
span: Span,
name: Symbol,
},
}
pub(crate) struct UnusedVariableStringInterp {
pub lit: Span,
}
impl Subdiagnostic for UnusedVariableStringInterp {
fn add_to_diag<G: EmissionGuarantee>(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,
+4
View File
@@ -51,6 +51,7 @@
mod ffi_unwind_calls;
mod lint;
mod lint_tail_expr_drop_order;
mod liveness;
mod patch;
mod shim;
mod ssa;
@@ -215,6 +216,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_cyclic: inline::cycle::mir_callgraph_cyclic,
@@ -512,6 +514,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();
+1328
View File
@@ -0,0 +1,1328 @@
use rustc_abi::FieldIdx;
use rustc_data_structures::fx::{FxHashSet, FxIndexMap, IndexEntry};
use rustc_hir::attrs::AttributeKind;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::LocalDefId;
use rustc_hir::find_attr;
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::{Symbol, kw, 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,
}
#[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)]
pub(crate) fn check_liveness<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> DenseBitSet<FieldIdx> {
// 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 intrinsics
if tcx.intrinsic(def_id.to_def_id()).is_some() {
return DenseBitSet::new_empty(0);
}
// Don't run unused pass for #[naked]
if find_attr!(tcx.get_all_attrs(def_id.to_def_id()), AttributeKind::Naked(..)) {
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)
&& find_attr!(tcx.get_all_attrs(parent), AttributeKind::AutomaticallyDerived(..))
{
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 {
// FIXME: stop cloning the body.
body_mem = body.clone();
for bbdata in body_mem.basic_blocks_mut() {
// We can call a closure again, either after a normal return or an unwind.
if let TerminatorKind::Return | TerminatorKind::UnwindResume =
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 typing_env = ty::TypingEnv::post_analysis(tcx, body.source.def_id());
let mut assignments =
AssignmentResult::find_dead_assignments(tcx, typing_env, &checked_places, &mut live, body);
assignments.merge_guards();
let dead_captures = assignments.compute_dead_captures(num_captures);
assignments.report_fully_unused();
assignments.report_unused_assignments();
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: Symbol,
) -> Vec<errors::UnusedVariableStringInterp> {
struct LiteralFinder<'body, 'tcx> {
body: &'body Body<'tcx>,
name: String,
name_colon: String,
found: Vec<errors::UnusedVariableStringInterp>,
}
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
}
/// Return whether we should consider the current place as a drop guard and skip reporting.
fn maybe_drop_guard<'tcx>(
tcx: TyCtxt<'tcx>,
typing_env: ty::TypingEnv<'tcx>,
index: PlaceIndex,
ever_dropped: &DenseBitSet<PlaceIndex>,
checked_places: &PlaceSet<'tcx>,
body: &Body<'tcx>,
) -> bool {
if ever_dropped.contains(index) {
let ty = checked_places.places[index].ty(&body.local_decls, tcx).ty;
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)
} else {
false
}
}
/// 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<errors::UnusedAssignSuggestion> {
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<Location> {
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 occurrences 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 occurrences 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<PlaceIndex, PlaceRef<'tcx>>,
names: IndexVec<PlaceIndex, Option<(Symbol, Span)>>,
/// Places corresponding to locals, common case.
locals: IndexVec<Local, Option<PlaceIndex>>,
// 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<FieldIdx, (PlaceIndex, bool)>,
}
impl<'tcx> PlaceSet<'tcx> {
fn insert_locals(&mut self, decls: &IndexVec<Local, LocalDecl<'tcx>>) {
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<Ty<'tcx>>,
) {
// 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,
(Symbol::intern(&capture.to_string(tcx)), capture.get_path_span(tcx)),
);
}
}
fn record_debuginfo(&mut self, var_debug_info: &Vec<VarDebugInfo<'tcx>>) {
let ignore_name = |name: Symbol| {
name == sym::empty || name == kw::SelfLower || name.as_str().starts_with('_')
};
for var_debug_info in var_debug_info {
if let VarDebugInfoContents::Place(place) = var_debug_info.value
&& let Some(index) = self.locals[place.local]
&& !ignore_name(var_debug_info.name)
{
self.names.get_or_insert_with(index, || {
(var_debug_info.name, var_debug_info.source_info.span)
});
}
}
// Discard places that will not result in a diagnostic.
for index_opt in self.locals.iter_mut() {
if let Some(index) = *index_opt {
let remove = match self.names[index] {
None => true,
Some((name, _)) => ignore_name(name),
};
if remove {
*index_opt = None;
}
}
}
}
#[inline]
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<Item = (PlaceIndex, &PlaceRef<'tcx>)> {
self.places.iter_enumerated()
}
fn len(&self) -> usize {
self.places.len()
}
}
struct AssignmentResult<'a, 'tcx> {
tcx: TyCtxt<'tcx>,
typing_env: ty::TypingEnv<'tcx>,
checked_places: &'a PlaceSet<'tcx>,
body: &'a Body<'tcx>,
/// Set of locals that are live at least once. This is used to report fully unused locals.
ever_live: DenseBitSet<PlaceIndex>,
/// 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<PlaceIndex>,
/// 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<PlaceIndex, FxIndexMap<SourceInfo, Access>>,
}
impl<'a, 'tcx> AssignmentResult<'a, '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: TyCtxt<'tcx>,
typing_env: ty::TypingEnv<'tcx>,
checked_places: &'a PlaceSet<'tcx>,
cursor: &mut ResultsCursor<'_, 'tcx, MaybeLivePlaces<'_, 'tcx>>,
body: &'a Body<'tcx>,
) -> AssignmentResult<'a, 'tcx> {
let mut ever_live = DenseBitSet::new_empty(checked_places.len());
let mut ever_dropped = DenseBitSet::new_empty(checked_places.len());
let mut assignments = IndexVec::<PlaceIndex, FxIndexMap<_, _>>::from_elem(
Default::default(),
&checked_places.places,
);
let mut check_place =
|place: Place<'tcx>, kind, source_info: SourceInfo, live: &DenseBitSet<PlaceIndex>| {
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 mut record_drop = |place: Place<'tcx>| {
if let Some((index, &[])) = checked_places.get(place.as_ref()) {
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;
let access = Access { kind, live: live.contains(index) };
assignments[index].insert(source_info, access);
}
}
AssignmentResult {
tcx,
typing_env,
checked_places,
ever_live,
ever_dropped,
assignments,
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`
/// _ => {}
/// }
///
fn merge_guards(&mut self) {
for (index, place) in self.checked_places.iter() {
let local = place.local;
if let &LocalInfo::User(BindingForm::RefForGuard(arm_local)) =
self.body.local_decls[local].local_info()
{
debug_assert!(place.projection.is_empty());
// Local to use in the arm.
let Some((arm_index, _proj)) = self.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;
}
}
}
}
}
}
/// Compute captures that are fully dead.
fn compute_dead_captures(&self, num_captures: usize) -> DenseBitSet<FieldIdx> {
// Report to caller the set of dead captures.
let mut dead_captures = DenseBitSet::new_empty(num_captures);
for (index, place) in self.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;
}
}
dead_captures
}
/// Report fully unused locals, and forget the corresponding assignments.
fn report_fully_unused(&mut self) {
let tcx = self.tcx;
// First, report fully unused locals.
for (index, place) in self.checked_places.iter() {
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 local = place.local;
let decl = &self.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(&self.body.source_scopes) else {
continue;
};
let introductions = &binding.introductions;
let Some((name, def_span)) = self.checked_places.names[index] else { continue };
// #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(|intro| intro.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 }
} else {
errors::UnusedVariableSugg::TryPrefix { spans: vec![def_span], name }
};
tcx.emit_node_span_lint(
lint::builtin::UNUSED_VARIABLES,
hir_id,
def_span,
errors::UnusedVariable {
name,
string_interp: maybe_suggest_literal_matching_name(self.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 maybe_drop_guard(
tcx,
self.typing_env,
index,
&self.ever_dropped,
self.checked_places,
self.body,
) {
statements.clear();
continue;
}
tcx.emit_node_span_lint(
lint::builtin::UNUSED_VARIABLES,
hir_id,
def_span,
errors::UnusedVarAssignedOnly { name },
);
continue;
}
// We do not have outstanding assignments, suggest renaming the binding.
let spans = introductions.iter().map(|intro| intro.span).collect::<Vec<_>>();
let any_shorthand = introductions.iter().any(|intro| intro.is_shorthand);
let sugg = if any_shorthand {
errors::UnusedVariableSugg::TryIgnore {
name,
shorthands: introductions
.iter()
.filter_map(
|intro| if intro.is_shorthand { Some(intro.span) } else { None },
)
.collect(),
non_shorthands: introductions
.iter()
.filter_map(
|intro| {
if !intro.is_shorthand { Some(intro.span) } else { None }
},
)
.collect(),
}
} else if from_macro {
errors::UnusedVariableSugg::NoSugg { span: def_span, name }
} else if !introductions.is_empty() {
errors::UnusedVariableSugg::TryPrefix { name, spans: spans.clone() }
} else {
errors::UnusedVariableSugg::TryPrefix { name, spans: vec![def_span] }
};
tcx.emit_node_span_lint(
lint::builtin::UNUSED_VARIABLES,
hir_id,
spans,
errors::UnusedVariable {
name,
string_interp: maybe_suggest_literal_matching_name(self.body, name),
sugg,
},
);
}
}
/// 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(self) {
let tcx = self.tcx;
for (index, statements) in self.assignments.into_iter_enumerated() {
if statements.is_empty() {
continue;
}
let Some((name, decl_span)) = self.checked_places.names[index] else { 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 maybe_drop_guard(
tcx,
self.typing_env,
index,
&self.ever_dropped,
self.checked_places,
self.body,
) {
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(&self.body.source_scopes) else {
continue;
};
match kind {
AccessKind::Assign => {
let suggestion = annotate_mut_binding_to_immutable_binding(
tcx,
self.checked_places.places[index],
self.body.source.def_id().expect_local(),
source_info.span,
self.body,
);
tcx.emit_node_span_lint(
lint::builtin::UNUSED_ASSIGNMENTS,
hir_id,
source_info.span,
errors::UnusedAssign { name, help: suggestion.is_none(), suggestion },
)
}
AccessKind::Param => tcx.emit_node_span_lint(
lint::builtin::UNUSED_ASSIGNMENTS,
hir_id,
source_info.span,
errors::UnusedAssignPassed { name },
),
AccessKind::Capture => tcx.emit_node_span_lint(
lint::builtin::UNUSED_ASSIGNMENTS,
hir_id,
decl_span,
errors::UnusedCaptureMaybeCaptureRef { name },
),
}
}
}
}
}
rustc_index::newtype_index! {
pub struct PlaceIndex {}
}
impl DebugWithContext<MaybeLivePlaces<'_, '_>> 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<Location>,
}
impl<'tcx> MaybeLivePlaces<'_, 'tcx> {
fn transfer_function<'a>(
&'a self,
trans: &'a mut DenseBitSet<PlaceIndex>,
) -> 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<PlaceIndex>;
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<PlaceIndex>,
capture_kind: CaptureKind,
self_assignment: &'a FxHashSet<Location>,
}
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 environment, 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<DefUse> {
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")
}
}
}
}
-35
View File
@@ -395,8 +395,6 @@ passes_macro_only_attribute =
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
@@ -568,8 +566,6 @@ passes_should_be_applied_to_trait =
attribute should be applied to a trait
.label = not a trait
passes_string_interpolation_only_works = string interpolation only works in `format!` invocations
passes_trait_impl_const_stability_mismatch = const stability on the impl does not match the const stability on the trait
passes_trait_impl_const_stability_mismatch_impl_stable = this impl is (implicitly) stable...
passes_trait_impl_const_stability_mismatch_impl_unstable = this impl is unstable...
@@ -636,18 +632,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` on traits
@@ -671,25 +655,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_var_typo = you might have meant to pattern match on the similarly named {$kind} `{$item_name}`
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_useless_assignment =
useless assignment of {$is_field_assign ->
[true] field
+1 -152
View File
@@ -4,7 +4,7 @@
use rustc_errors::codes::*;
use rustc_errors::{
Applicability, Diag, DiagCtxtHandle, DiagSymbolList, Diagnostic, EmissionGuarantee, Level,
MultiSpan, Subdiagnostic,
MultiSpan,
};
use rustc_hir::Target;
use rustc_hir::attrs::{MirDialect, MirPhase};
@@ -1308,61 +1308,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<Span>,
}
#[derive(LintDiagnostic)]
#[diag(passes_unused_var_assigned_only)]
#[note]
pub(crate) struct UnusedVarAssignedOnly {
pub name: String,
#[subdiagnostic]
pub typo: Option<PatternTypo>,
}
#[derive(Subdiagnostic)]
#[multipart_suggestion(
passes_unused_var_typo,
style = "verbose",
applicability = "maybe-incorrect"
)]
pub(crate) struct PatternTypo {
#[suggestion_part(code = "{code}")]
pub span: Span,
pub code: String,
pub item_name: String,
pub kind: String,
}
#[derive(LintDiagnostic)]
#[diag(passes_unnecessary_stable_feature)]
pub(crate) struct UnnecessaryStableFeature {
@@ -1387,102 +1332,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<UnusedAssignSuggestion>,
#[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<Span>,
#[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<Span>,
#[subdiagnostic]
pub string_interp: Vec<UnusedVariableStringInterp>,
#[subdiagnostic]
pub sugg: UnusedVariableSugg,
pub name: String,
#[subdiagnostic]
pub typo: Option<PatternTypo>,
}
#[derive(Subdiagnostic)]
pub(crate) enum UnusedVariableSugg {
#[multipart_suggestion(passes_suggestion, applicability = "maybe-incorrect")]
TryPrefixSugg {
#[suggestion_part(code = "_{name}")]
spans: Vec<Span>,
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<G: EmissionGuarantee>(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 {
pub name: String,
#[subdiagnostic]
pub sugg: UnusedVarTryIgnoreSugg,
}
#[derive(Subdiagnostic)]
#[multipart_suggestion(passes_suggestion, applicability = "maybe-incorrect")]
pub(crate) struct UnusedVarTryIgnoreSugg {
#[suggestion_part(code = "{name}: _")]
pub shorthands: Vec<Span>,
#[suggestion_part(code = "_")]
pub non_shorthands: Vec<Span>,
pub name: String,
}
#[derive(LintDiagnostic)]
#[diag(passes_attr_crate_level)]
#[note]
-2
View File
@@ -28,7 +28,6 @@
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);
-1853
View File
@@ -1,1853 +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::attrs::AttributeKind;
use rustc_hir::def::*;
use rustc_hir::def_id::LocalDefId;
use rustc_hir::intravisit::{self, Visitor};
use rustc_hir::{Expr, HirId, HirIdMap, HirIdSet, find_attr};
use rustc_index::IndexVec;
use rustc_middle::query::Providers;
use rustc_middle::span_bug;
use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_middle::ty::{self, RootVariableMinCaptureList, TyCtxt};
use rustc_session::lint;
use rustc_span::edit_distance::find_best_match_for_name;
use rustc_span::{BytePos, Span, Symbol};
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)
&& find_attr!(tcx.get_all_attrs(parent), AttributeKind::AutomaticallyDerived(..))
{
return;
}
// Don't run unused pass for #[naked]
if find_attr!(tcx.get_all_attrs(def_id.to_def_id()), AttributeKind::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<LiveNode>,
variable_map: HirIdMap<Variable>,
capture_info_map: HirIdMap<Rc<Vec<CaptureInfo>>>,
var_kinds: IndexVec<Variable, VarKind>,
lnks: IndexVec<LiveNode, LiveNodeKind>,
}
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<CaptureInfo>) {
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<LiveNode, Option<LiveNode>>,
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<LiveNode>,
cont_ln: HirIdMap<LiveNode>,
}
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<F>(&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<String> {
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<LiveNode>,
opt_body: Option<&hir::Body<'_>>,
on_used_on_entry: impl Fn(Vec<Span>, 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<Symbol, (LiveNode, Variable, Vec<(HirId, Span, Span)>)> =
<_>::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, Some(_)) => {
// 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<errors::UnusedAssignSuggestion> {
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) };
let mut typo = None;
let filtered_hir_ids_and_spans = hir_ids_and_spans.iter().filter(|(hir_id, ..)| {
!matches!(self.ir.tcx.parent_hir_node(*hir_id), hir::Node::Param(_))
});
for (hir_id, _, span) in filtered_hir_ids_and_spans.clone() {
let ty = self.typeck_results.node_type(*hir_id);
if let ty::Adt(adt, _) = ty.peel_refs().kind() {
let name = Symbol::intern(&name);
let adt_def = self.ir.tcx.adt_def(adt.did());
let variant_names: Vec<_> = adt_def
.variants()
.iter()
.filter(|v| matches!(v.ctor, Some((CtorKind::Const, _))))
.map(|v| v.name)
.collect();
if let Some(name) = find_best_match_for_name(&variant_names, name, None)
&& let Some(variant) = adt_def.variants().iter().find(|v| {
v.name == name && matches!(v.ctor, Some((CtorKind::Const, _)))
})
{
typo = Some(errors::PatternTypo {
span: *span,
code: with_no_trimmed_paths!(self.ir.tcx.def_path_str(variant.def_id)),
kind: self.ir.tcx.def_descr(variant.def_id).to_string(),
item_name: variant.name.to_string(),
});
}
}
}
if typo.is_none() {
for (hir_id, _, span) in filtered_hir_ids_and_spans {
let ty = self.typeck_results.node_type(*hir_id);
// Look for consts of the same type with similar names as well, not just unit
// structs and variants.
for def_id in self.ir.tcx.hir_body_owners() {
if let DefKind::Const = self.ir.tcx.def_kind(def_id)
&& self.ir.tcx.type_of(def_id).instantiate_identity() == ty
{
typo = Some(errors::PatternTypo {
span: *span,
code: with_no_trimmed_paths!(self.ir.tcx.def_path_str(def_id)),
kind: "constant".to_string(),
item_name: self.ir.tcx.item_name(def_id).to_string(),
});
}
}
}
}
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::<Vec<_>>(),
errors::UnusedVarAssignedOnly { name, typo },
)
} 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::<Vec<_>>(),
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::<Vec<_>>(),
errors::UnusedVarTryIgnore {
name: name.clone(),
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::<Vec<_>>();
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::<Vec<_>>(),
errors::UnusedVariableTryPrefix {
label: if !suggestions.is_empty() { Some(pat.span) } else { None },
name,
sugg,
string_interp: suggestions,
typo,
},
);
}
}
}
}
fn string_interp_suggestions(
&self,
name: &str,
opt_body: Option<&hir::Body<'_>>,
) -> Vec<errors::UnusedVariableStringInterp> {
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<Span>,
hir_id: HirId,
ln: LiveNode,
var: Variable,
suggestion: Option<errors::UnusedAssignSuggestion>,
) {
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 },
);
}
}
}
@@ -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<Vec<RWU>>`. 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<u8>,
/// 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::<u8>() * 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.div_ceil(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)
}
}
@@ -1,7 +1,7 @@
#![warn(clippy::needless_match)]
#![allow(clippy::manual_map)]
#![allow(dead_code)]
#![allow(unused)]
#[derive(Clone, Copy)]
enum Simple {
A,
+1 -1
View File
@@ -1,7 +1,7 @@
#![warn(clippy::needless_match)]
#![allow(clippy::manual_map)]
#![allow(dead_code)]
#![allow(unused)]
#[derive(Clone, Copy)]
enum Simple {
A,
@@ -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)]
@@ -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)]
+1
View File
@@ -69,6 +69,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]",
+10 -1
View File
@@ -18,5 +18,14 @@ note: the lint level is defined here
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
@@ -10,6 +10,7 @@
// in <https://github.com/rust-lang/rust/pull/123350>.
#![allow(unused_mut)]
#![allow(unused_assignments)]
extern crate block_on;
@@ -1,5 +1,6 @@
//@ run-pass
#[allow(unused)]
pub fn main() {
struct A {
a: isize,
@@ -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
})();
})();
}
@@ -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 assigned to `a` is never 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 `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");
| ^
|
= help: maybe it is overwritten before being read?
warning: value assigned to `b` is never read
--> $DIR/liveness.rs:85: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
|
LL | b = Some("e2");
| ^
|
= help: maybe it is overwritten before being read?
warning: unused variable: `b`
--> $DIR/liveness.rs:85:13
|
LL | b = Some("e1");
| ^
|
= help: did you mean to capture by reference instead?
warning: 7 warnings emitted
warning: value assigned to `b` is never read
--> $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:79:13
|
LL | b = Some("e2");
| ^^^^^^^^^^^^^^
|
= help: maybe it is overwritten before being read?
warning: value assigned to `d.x` is never read
--> $DIR/liveness.rs:68:13
|
LL | d.x = Some("d1");
| ^^^^^^^^^^^^^^^^
|
= 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: 16 warnings emitted
@@ -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
});
}
@@ -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
@@ -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
@@ -7,7 +7,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
}
@@ -1,20 +1,27 @@
warning: value assigned to `x` is never read
--> $DIR/moved-upvar-mut-rebind-11958.rs:10:36
|
LL | let _thunk = Box::new(move|| { x = 2; });
| ^
|
= help: maybe it is overwritten before being read?
= note: `#[warn(unused_assignments)]` (part of `#[warn(unused)]`) on by default
warning: unused variable: `x`
--> $DIR/moved-upvar-mut-rebind-11958.rs:10:36
warning: value captured by `x` is never read
--> $DIR/moved-upvar-mut-rebind-11958.rs:11:36
|
LL | let _thunk = Box::new(move|| { x = 2; });
| ^
|
= help: did you mean to capture by reference instead?
= note: `#[warn(unused_assignments)]` (part of `#[warn(unused)]`) on by default
warning: value assigned to `x` is never read
--> $DIR/moved-upvar-mut-rebind-11958.rs:11:36
|
LL | let _thunk = Box::new(move|| { x = 2; });
| ^^^^^
|
= help: maybe it is overwritten before being read?
warning: unused variable: `x`
--> $DIR/moved-upvar-mut-rebind-11958.rs:9:9
|
LL | let mut x = 1;
| ^^^^^ help: if this is intentional, prefix it with an underscore: `_x`
|
= note: `#[warn(unused_variables)]` (part of `#[warn(unused)]`) on by default
warning: 2 warnings emitted
warning: 3 warnings emitted
@@ -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>`
+1 -1
View File
@@ -25,7 +25,7 @@ fn assert_drop_order(expected_drops: impl IntoIterator<Item = u32>, 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.
+2 -1
View File
@@ -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> {
@@ -14,25 +14,6 @@ LL | let object2 = Object;
LL ~ *object = object2;
|
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
|
LL | #![deny(unused_assignments)]
| ^^^^^^^^^^^^^^^^^^
help: you might have meant to mutate the pointed at value being passed in, instead of changing the reference in the local binding
|
LL ~ fn change_object2(object: &mut Object) {
LL |
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
|
@@ -51,17 +32,22 @@ LL | }
| - `object2` dropped here while still borrowed
error: value assigned to `object` is never read
--> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:22:5
--> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:14:4
|
LL | object = &mut object2;
| ^^^^^^
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
|
LL | #![deny(unused_assignments)]
| ^^^^^^^^^^^^^^^^^^
help: you might have meant to mutate the pointed at value being passed in, instead of changing the reference in the local binding
|
LL ~ fn change_object3(object: &mut Object) {
LL ~ fn change_object2(object: &mut Object) {
LL |
LL | let object2 = Object;
LL ~ *object = object2;
LL | let object2 = Object;
LL ~ *object = object2;
|
error[E0596]: cannot borrow `object2` as mutable, as it is not declared as mutable
@@ -75,6 +61,20 @@ help: consider changing this to be mutable
LL | let mut object2 = Object;
| +++
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
|
LL ~ fn change_object3(object: &mut Object) {
LL |
LL | let object2 = Object;
LL ~ *object = object2;
|
error: aborting due to 5 previous errors
Some errors have detailed explanations: E0308, E0596, E0597.
+3
View File
@@ -1,4 +1,7 @@
//@ run-pass
#![allow(unused_assignments)]
struct S {
o: Option<String>
}
+12 -12
View File
@@ -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
+12 -12
View File
@@ -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
@@ -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;
@@ -13,4 +13,5 @@ macro_rules! pat {
fn main() {
let pat!(value) = Value { value: () };
//~^ WARN value assigned to `value` is never read
}
@@ -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)]` (part of `#[warn(unused)]`) on by default
warning: 1 warning emitted
@@ -16,7 +16,7 @@ warning: unused variable: `Foo`
--> $DIR/lint-uppercase-variables.rs:22:9
|
LL | Foo => {}
| ^^^
| ^^^ help: if this is intentional, prefix it with an underscore: `_Foo`
|
note: the lint level is defined here
--> $DIR/lint-uppercase-variables.rs:1:9
@@ -24,29 +24,12 @@ note: the lint level is defined here
LL | #![warn(unused)]
| ^^^^^^
= note: `#[warn(unused_variables)]` implied by `#[warn(unused)]`
help: if this is intentional, prefix it with an underscore
|
LL | _Foo => {}
| +
help: you might have meant to pattern match on the similarly named variant `Foo`
|
LL | foo::Foo::Foo => {}
| ++++++++++
warning: unused variable: `Foo`
--> $DIR/lint-uppercase-variables.rs:28:9
|
LL | let Foo = foo::Foo::Foo;
| ^^^
|
help: if this is intentional, prefix it with an underscore
|
LL | let _Foo = foo::Foo::Foo;
| +
help: you might have meant to pattern match on the similarly named variant `Foo`
|
LL | let foo::Foo::Foo = foo::Foo::Foo;
| ++++++++++
| ^^^ help: if this is intentional, prefix it with an underscore: `_Foo`
error[E0170]: pattern binding `Foo` is named the same as one of the variants of the type `foo::Foo`
--> $DIR/lint-uppercase-variables.rs:33:17
@@ -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
@@ -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
|
@@ -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`
}
@@ -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
@@ -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
warning: value assigned to `hours_are_suns` is never read
--> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:48:9
|
LL | let mut mut_unused_var = 1;
| ----^^^^^^^^^^^^^^
| |
| help: remove this `mut`
LL | hours_are_suns = false;
| ^^^^^^^^^^^^^^^^^^^^^^
|
= 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`
= help: maybe it is overwritten before being read?
= note: `#[warn(unused_assignments)]` implied by `#[warn(unused)]`
warning: 16 warnings emitted
@@ -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
@@ -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
|
LL | a: i32,
| ^ help: if this is intentional, prefix it with an underscore: `_a`
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: 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: `a`
--> $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: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
+4 -4
View File
@@ -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?
+4 -2
View File
@@ -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
+30 -14
View File
@@ -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 `b` is never read
--> $DIR/liveness-consts.rs:17:5
warning: value assigned to `a` is never read
--> $DIR/liveness-consts.rs:11:9
|
LL | b += 1;
| ^
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: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?
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
+6 -6
View File
@@ -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?
+111 -2
View File
@@ -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<T>(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<T>(mut a: Box<T>, b: Box<T>) {
a = b;
}
// unused params warnings are not needed for intrinsic functions without bodies
#[rustc_intrinsic]
unsafe fn simd_shuffle<T, I, U>(a: T, b: T, i: I) -> U;
fn main() {
}
+185 -14
View File
@@ -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: aborting due to 13 previous errors; 1 warning emitted
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<T>(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 34 previous errors; 1 warning emitted
+35 -12
View File
@@ -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<T: Default>(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<T: Copy + Default + std::fmt::Debug>() {
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
};
@@ -141,4 +147,21 @@ pub fn coroutine() {
};
}
pub fn panics() {
use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
let mut panic = true;
// `a` can be called again, even if it has panicked at an earlier run.
let mut a = || {
if panic {
panic = false;
resume_unwind(Box::new(()))
}
};
catch_unwind(AssertUnwindSafe(|| a())).ok();
a();
}
fn main() {}
+106 -91
View File
@@ -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,8 +28,64 @@ LL | sum += x;
|
= help: did you mean to capture by reference instead?
warning: value assigned to `sum` is never read
--> $DIR/liveness-upvars.rs:22:9
|
LL | sum += x;
| ^^^^^^^^
|
= help: maybe it is overwritten before being read?
warning: value assigned to `c` is never read
--> $DIR/liveness-upvars.rs:69:9
|
LL | c += 1;
| ^^^^^^
|
= help: maybe it is overwritten before being read?
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:32:9
--> $DIR/liveness-upvars.rs:49:9
|
LL | c += 1;
| ^
|
= help: did you mean to capture by reference instead?
warning: value assigned to `c` is never read
--> $DIR/liveness-upvars.rs:49:9
|
LL | c += 1;
| ^^^^^^
|
= help: maybe it is overwritten before being read?
warning: value captured by `c` is never read
--> $DIR/liveness-upvars.rs:44:9
|
LL | c += 1;
| ^
|
= help: did you mean to capture by reference instead?
warning: value assigned to `c` is never read
--> $DIR/liveness-upvars.rs:44:9
|
LL | c += 1;
| ^^^^^^
|
= help: maybe it is overwritten before being read?
warning: value captured by `c` is never read
--> $DIR/liveness-upvars.rs:38:9
|
LL | c = 1;
| ^
@@ -38,148 +93,108 @@ LL | c = 1;
= help: did you mean to capture by reference instead?
warning: value captured by `c` is never read
--> $DIR/liveness-upvars.rs:36:9
--> $DIR/liveness-upvars.rs:34:9
|
LL | c = 1;
| ^
|
= help: did you mean to capture by reference instead?
warning: unused variable: `c`
--> $DIR/liveness-upvars.rs:42:9
warning: value captured by `e` is never read
--> $DIR/liveness-upvars.rs:82:13
|
LL | c += 1;
| ^
LL | e = Some("e1");
| ^
|
= help: did you mean to capture by reference instead?
warning: value assigned to `c` is never read
--> $DIR/liveness-upvars.rs:45:9
warning: value assigned to `e` is never read
--> $DIR/liveness-upvars.rs:82:13
|
LL | c += 1;
| ^
LL | e = Some("e1");
| ^^^^^^^^^^^^^^
|
= help: maybe it is overwritten before being read?
warning: unused variable: `c`
--> $DIR/liveness-upvars.rs:45:9
warning: value assigned to `e` is never read
--> $DIR/liveness-upvars.rs:84:13
|
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
|
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
|
LL | c += 1;
| ^
LL | e = Some("e2");
| ^^^^^^^^^^^^^^
|
= help: maybe it is overwritten before being read?
warning: value assigned to `d` is never read
--> $DIR/liveness-upvars.rs:73:13
--> $DIR/liveness-upvars.rs:78:13
|
LL | d = Some("d1");
| ^
| ^^^^^^^^^^^^^^
|
= help: maybe it is overwritten before being read?
warning: value assigned to `e` is never read
--> $DIR/liveness-upvars.rs:77: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
|
LL | e = Some("e2");
| ^
|
= help: maybe it is overwritten before being read?
warning: unused variable: `e`
--> $DIR/liveness-upvars.rs:77:13
|
LL | e = Some("e1");
| ^
|
= help: did you mean to capture by reference instead?
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
|
LL | z = T::default();
| ^
|
= help: maybe it is overwritten before being read?
warning: unused variable: `z`
--> $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: did you mean to capture by reference instead?
warning: value assigned to `state` is never read
--> $DIR/liveness-upvars.rs:125:9
warning: value assigned to `z` is never read
--> $DIR/liveness-upvars.rs:105:17
|
LL | z = T::default();
| ^^^^^^^^^^^^^^^^
|
= help: maybe it is overwritten before being read?
warning: value captured by `state` is never read
--> $DIR/liveness-upvars.rs:131:9
|
LL | state = 4;
| ^^^^^
|
= help: did you mean to capture by reference instead?
warning: value assigned to `state` is never read
--> $DIR/liveness-upvars.rs:131:9
|
LL | state = 4;
| ^^^^^^^^^
|
= help: maybe it is overwritten before being read?
warning: value assigned to `state` is never read
--> $DIR/liveness-upvars.rs:128:9
--> $DIR/liveness-upvars.rs:134:9
|
LL | state = 5;
| ^^^^^
| ^^^^^^^^^
|
= help: maybe it is overwritten before being read?
warning: unused variable: `state`
--> $DIR/liveness-upvars.rs:125:9
|
LL | state = 4;
| ^^^^^
|
= help: did you mean to capture by reference instead?
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
@@ -3,7 +3,7 @@
// fields and fn arguments.
#![allow(dead_code)]
#![allow(dead_code, unused)]
trait Test {
fn foo(&self) { }
@@ -3,7 +3,7 @@
// lifetime bound.
#![allow(dead_code)]
#![allow(dead_code, unused)]
use std::fmt::Display;
@@ -3,7 +3,7 @@
// through the `Box` struct.
#![allow(dead_code)]
#![allow(dead_code, unused)]
trait Test {
fn foo(&self) { }
@@ -3,7 +3,7 @@
// lifetime bound.
#![allow(dead_code)]
#![allow(dead_code, unused)]
trait Test {
fn foo(&self) { }
@@ -3,7 +3,7 @@
// through the `MyBox` struct.
#![allow(dead_code)]
#![allow(dead_code, unused)]
trait Test {
fn foo(&self) { }
@@ -3,7 +3,7 @@
// lifetime bound.
#![allow(dead_code)]
#![allow(dead_code, unused)]
use std::fmt::Display;
@@ -3,7 +3,7 @@
// valid.
#![allow(dead_code)]
#![allow(dead_code, unused)]
trait Test {
fn foo(&self) { }
+14 -100
View File
@@ -97,157 +97,71 @@ LL - (Foo, _) | (Ban, Foo) => {}
LL + (Foo, _) | (Bat, Foo) => {}
|
error: variable `Ban` is assigned to, but never used
error: unused variable: `Ban`
--> $DIR/binding-typo-2.rs:14:23
|
LL | (Foo, Bar) | (Ban, Foo) => {}
| ^^^
| ^^^ help: if this is intentional, prefix it with an underscore: `_Ban`
|
= note: consider using `_Ban` instead
note: the lint level is defined here
--> $DIR/binding-typo-2.rs:2:9
|
LL | #![deny(unused_variables)]
| ^^^^^^^^^^^^^^^^
help: you might have meant to pattern match on the similarly named variant `Bar`
|
LL - (Foo, Bar) | (Ban, Foo) => {}
LL + (Foo, Bar) | (Lol::Bar, Foo) => {}
|
error: variable `Ban` is assigned to, but never used
error: unused variable: `Ban`
--> $DIR/binding-typo-2.rs:25:21
|
LL | (Foo, _) | (Ban, Foo) => {}
| ^^^
|
= note: consider using `_Ban` instead
help: you might have meant to pattern match on the similarly named variant `Bar`
|
LL - (Foo, _) | (Ban, Foo) => {}
LL + (Foo, _) | (Lol::Bar, Foo) => {}
|
| ^^^ help: if this is intentional, prefix it with an underscore: `_Ban`
error: unused variable: `Non`
--> $DIR/binding-typo-2.rs:37:9
|
LL | Non => {}
| ^^^
|
help: if this is intentional, prefix it with an underscore
|
LL | _Non => {}
| +
help: you might have meant to pattern match on the similarly named variant `None`
|
LL - Non => {}
LL + std::prelude::v1::None => {}
|
| ^^^ help: if this is intentional, prefix it with an underscore: `_Non`
error: unused variable: `Non`
--> $DIR/binding-typo-2.rs:44:9
|
LL | Non | None => {}
| ^^^
|
help: if this is intentional, prefix it with an underscore
|
LL | _Non | None => {}
| +
help: you might have meant to pattern match on the similarly named variant `None`
|
LL - Non | None => {}
LL + std::prelude::v1::None | None => {}
|
| ^^^ help: if this is intentional, prefix it with an underscore: `_Non`
error: unused variable: `Non`
--> $DIR/binding-typo-2.rs:54:9
|
LL | Non | Some(_) => {}
| ^^^
|
help: if this is intentional, prefix it with an underscore
|
LL | _Non | Some(_) => {}
| +
help: you might have meant to pattern match on the similarly named variant `None`
|
LL - Non | Some(_) => {}
LL + std::prelude::v1::None | Some(_) => {}
|
| ^^^ help: if this is intentional, prefix it with an underscore: `_Non`
error: variable `Ban` is assigned to, but never used
error: unused variable: `Ban`
--> $DIR/binding-typo-2.rs:69:21
|
LL | (Foo, _) | (Ban, Foo) => {}
| ^^^
|
= note: consider using `_Ban` instead
help: you might have meant to pattern match on the similarly named variant `Bar`
|
LL - (Foo, _) | (Ban, Foo) => {}
LL + (Foo, _) | (Lol::Bar, Foo) => {}
|
| ^^^ help: if this is intentional, prefix it with an underscore: `_Ban`
error: variable `Ban` is assigned to, but never used
error: unused variable: `Ban`
--> $DIR/binding-typo-2.rs:86:21
|
LL | (Foo, _) | (Ban, Foo) => {}
| ^^^
|
= note: consider using `_Ban` instead
help: you might have meant to pattern match on the similarly named variant `Bar`
|
LL - (Foo, _) | (Ban, Foo) => {}
LL + (Foo, _) | (Lol::Bar, Foo) => {}
|
| ^^^ help: if this is intentional, prefix it with an underscore: `_Ban`
error: unused variable: `Ban`
--> $DIR/binding-typo-2.rs:98:10
|
LL | (Ban, _) => {}
| ^^^
|
help: if this is intentional, prefix it with an underscore
|
LL | (_Ban, _) => {}
| +
help: you might have meant to pattern match on the similarly named variant `Bar`
|
LL - (Ban, _) => {}
LL + (Lol::Bar, _) => {}
|
| ^^^ help: if this is intentional, prefix it with an underscore: `_Ban`
error: unused variable: `Ban`
--> $DIR/binding-typo-2.rs:104:9
|
LL | Ban => {}
| ^^^
|
help: if this is intentional, prefix it with an underscore
|
LL | _Ban => {}
| +
help: you might have meant to pattern match on the similarly named struct `Bay`
|
LL - Ban => {}
LL + Bay => {}
|
| ^^^ help: if this is intentional, prefix it with an underscore: `_Ban`
error: unused variable: `Batery`
--> $DIR/binding-typo-2.rs:110:9
|
LL | Batery => {}
| ^^^^^^
|
help: if this is intentional, prefix it with an underscore
|
LL | _Batery => {}
| +
help: you might have meant to pattern match on the similarly named constant `Battery`
|
LL | Battery => {}
| +
| ^^^^^^ help: if this is intentional, prefix it with an underscore: `_Batery`
error: aborting due to 16 previous errors
@@ -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;
@@ -9,6 +9,7 @@
//@ run-rustfix
#![allow(unused_variables)]
#![allow(unused_assignments)]
fn main() {
let s: Option<u8> = None;
@@ -9,6 +9,7 @@
//@ run-rustfix
#![allow(unused_variables)]
#![allow(unused_assignments)]
fn main() {
let s: Option<u8> = None;
@@ -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 => {}
| -------^^^-
@@ -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);
}
@@ -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
@@ -4,6 +4,7 @@ fn main() {
return ();
let x = ();
//~^ WARN unused variable: `x`
x
}
@@ -0,0 +1,10 @@
warning: unused variable: `x`
--> $DIR/early-return-with-unreachable-code-24353.rs:6:9
|
LL | let x = ();
| ^ help: if this is intentional, prefix it with an underscore: `_x`
|
= note: `#[warn(unused_variables)]` (part of `#[warn(unused)]`) on by default
warning: 1 warning emitted
@@ -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.
//
@@ -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)]` (part of `#[warn(unused)]`) on by default
warning: 1 warning emitted
@@ -0,0 +1,11 @@
warning: value assigned to `small` is never read
--> $DIR/std-panic-locations.rs:47:31
|
LL | assert_panicked(move || { small[1] += 1; });
| ^^^^^^^^^^^^^
|
= help: maybe it is overwritten before being read?
= note: `#[warn(unused_assignments)]` (part of `#[warn(unused)]`) on by default
warning: 1 warning emitted
@@ -0,0 +1,11 @@
warning: value assigned to `small` is never read
--> $DIR/std-panic-locations.rs:47:31
|
LL | assert_panicked(move || { small[1] += 1; });
| ^^^^^^^^^^^^^
|
= help: maybe it is overwritten before being read?
= note: `#[warn(unused_assignments)]` (part of `#[warn(unused)]`) on by default
warning: 1 warning emitted
@@ -45,6 +45,7 @@ fn assert_panicked(f: impl FnOnce() + UnwindSafe) {
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<bool, bool> = Default::default();
assert_panicked(|| { sorted.index(&false); });
@@ -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
@@ -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
}
}
}
@@ -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)]` (part of `#[warn(unused)]`) 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
@@ -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
+2 -2
View File
@@ -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
+2 -3
View File
@@ -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;
@@ -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);
}
@@ -1,3 +1,20 @@
warning: value captured by `counter` is never read
--> $DIR/unboxed-closures-counter-not-moved.rs:24:9
|
LL | counter += 1;
| ^^^^^^^
|
= help: did you mean to capture by reference instead?
= note: `#[warn(unused_assignments)]` (part of `#[warn(unused)]`) 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?
warning: unused variable: `item`
--> $DIR/unboxed-closures-counter-not-moved.rs:15:13
|
@@ -6,22 +23,5 @@ LL | for item in y {
|
= note: `#[warn(unused_variables)]` (part of `#[warn(unused)]`) 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)]` (part of `#[warn(unused)]`) on by default
warning: unused variable: `counter`
--> $DIR/unboxed-closures-counter-not-moved.rs:24:9
|
LL | counter += 1;
| ^^^^^^^
|
= help: did you mean to capture by reference instead?
warning: 3 warnings emitted
@@ -13,11 +13,17 @@
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;
@@ -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_assignments)]` (part of `#[warn(unused)]`) on by default
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: 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)]` (part of `#[warn(unused)]`) on by default
warning: unused variable: `x`
--> $DIR/unboxed-closures-move-mutable.rs:20:17
--> $DIR/unboxed-closures-move-mutable.rs:22:13
|
LL | move || x += 1;
| ^
|
= help: did you mean to capture by reference instead?
LL | let mut x = 0_usize;
| ^^^^^ help: if this is intentional, prefix it with an underscore: `_x`
warning: 2 warnings emitted
warning: 6 warnings emitted