mirror of
https://github.com/rust-lang/rust.git
synced 2026-04-27 18:57:42 +03:00
Auto merge of #142390 - cjgillot:mir-liveness, r=davidtwco
Perform unused assignment and unused variables lints on MIR. Rebase of https://github.com/rust-lang/rust/pull/101500 Fixes https://github.com/rust-lang/rust/issues/51003. The first commit moves detection of uninhabited types from the current liveness pass to MIR building. In order to keep the same level of diagnostics, I had to instrument MIR a little more: - keep for which original local a guard local is created; - store in the `VarBindingForm` the list of introducer places and whether this was a shorthand pattern. I am not very proud of the handling of self-assignments. The proposed scheme is in two parts: first detect probable self-assignments, by pattern matching on MIR, and second treat them specially during dataflow analysis. I welcome ideas. Please review carefully the changes in tests. There are many small changes to behaviour, and I'm not sure all of them are desirable.
This commit is contained in:
@@ -139,9 +139,7 @@ fn append_to_grouped_errors(
|
||||
// whether or not the right-hand side is a place expression
|
||||
if let LocalInfo::User(BindingForm::Var(VarBindingForm {
|
||||
opt_match_place: Some((opt_match_place, match_span)),
|
||||
binding_mode: _,
|
||||
opt_ty_info: _,
|
||||
pat_span: _,
|
||||
..
|
||||
})) = *local_decl.local_info()
|
||||
{
|
||||
let stmt_source_info = self.body.source_info(location);
|
||||
|
||||
@@ -335,8 +335,7 @@ pub(crate) fn report_mutability_error(
|
||||
LocalInfo::User(BindingForm::Var(mir::VarBindingForm {
|
||||
binding_mode: BindingMode(ByRef::No, Mutability::Not),
|
||||
opt_ty_info: Some(sp),
|
||||
opt_match_place: _,
|
||||
pat_span: _,
|
||||
..
|
||||
})) => {
|
||||
if suggest {
|
||||
err.span_note(sp, "the binding is already a mutable borrow");
|
||||
@@ -750,6 +749,7 @@ fn construct_mut_suggestion_for_local_binding_patterns(
|
||||
opt_ty_info: _,
|
||||
opt_match_place: _,
|
||||
pat_span,
|
||||
introductions: _,
|
||||
})) => pat_span,
|
||||
_ => local_decl.source_info.span,
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -882,6 +882,9 @@ pub struct VarBindingForm<'tcx> {
|
||||
pub opt_match_place: Option<(Option<Place<'tcx>>, Span)>,
|
||||
/// The span of the pattern in which this variable was bound.
|
||||
pub pat_span: Span,
|
||||
/// A binding can be introduced multiple times, with or patterns:
|
||||
/// `Foo::A { x } | Foo::B { z: x }`. This stores information for each of those introductions.
|
||||
pub introductions: Vec<VarBindingIntroduction>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, TyEncodable, TyDecodable)]
|
||||
@@ -891,7 +894,15 @@ 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)]
|
||||
pub struct VarBindingIntroduction {
|
||||
/// Where this additional introduction happened.
|
||||
pub span: Span,
|
||||
/// Is that introduction a shorthand struct pattern, i.e. `Foo { x }`.
|
||||
pub is_shorthand: bool,
|
||||
}
|
||||
|
||||
mod binding_form_impl {
|
||||
@@ -906,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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1092,12 +1103,8 @@ pub fn can_be_made_mutable(&self) -> bool {
|
||||
matches!(
|
||||
self.local_info(),
|
||||
LocalInfo::User(
|
||||
BindingForm::Var(VarBindingForm {
|
||||
binding_mode: BindingMode(ByRef::No, _),
|
||||
opt_ty_info: _,
|
||||
opt_match_place: _,
|
||||
pat_span: _,
|
||||
}) | BindingForm::ImplicitSelf(ImplicitSelfKind::Imm),
|
||||
BindingForm::Var(VarBindingForm { binding_mode: BindingMode(ByRef::No, _), .. })
|
||||
| BindingForm::ImplicitSelf(ImplicitSelfKind::Imm),
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -1109,12 +1116,8 @@ pub fn is_nonref_binding(&self) -> bool {
|
||||
matches!(
|
||||
self.local_info(),
|
||||
LocalInfo::User(
|
||||
BindingForm::Var(VarBindingForm {
|
||||
binding_mode: BindingMode(ByRef::No, _),
|
||||
opt_ty_info: _,
|
||||
opt_match_place: _,
|
||||
pat_span: _,
|
||||
}) | BindingForm::ImplicitSelf(_),
|
||||
BindingForm::Var(VarBindingForm { binding_mode: BindingMode(ByRef::No, _), .. })
|
||||
| BindingForm::ImplicitSelf(_),
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -1130,7 +1133,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
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -1191,8 +1191,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.
|
||||
|
||||
@@ -798,6 +798,8 @@ pub enum PatKind<'tcx> {
|
||||
/// (The same binding can occur multiple times in different branches of
|
||||
/// an or-pattern, but only one of them will be primary.)
|
||||
is_primary: bool,
|
||||
/// Is this binding a shorthand struct pattern, i.e. `Foo { a }`?
|
||||
is_shorthand: bool,
|
||||
},
|
||||
|
||||
/// `Foo(...)` or `Foo{...}` or `Foo`, where `Foo` is a variant name from an ADT with
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,6 +372,11 @@ mir_build_union_field_requires_unsafe_unsafe_op_in_unsafe_fn_allowed =
|
||||
mir_build_union_pattern = cannot use unions in constant patterns
|
||||
.label = can't use a `union` here
|
||||
|
||||
mir_build_unreachable_due_to_uninhabited = unreachable {$descr}
|
||||
.label = unreachable {$descr}
|
||||
.label_orig = any code following this expression is unreachable
|
||||
.note = this expression has type `{$ty}`, which is uninhabited
|
||||
|
||||
mir_build_unreachable_making_this_unreachable = collectively making this unreachable
|
||||
|
||||
mir_build_unreachable_making_this_unreachable_n_more = ...and {$covered_by_many_n_more_count} other patterns collectively make this unreachable
|
||||
|
||||
@@ -286,6 +286,7 @@ fn ast_block_stmts(
|
||||
block,
|
||||
node,
|
||||
span,
|
||||
false,
|
||||
OutsideGuard,
|
||||
ScheduleDrops::Yes,
|
||||
);
|
||||
|
||||
@@ -390,18 +390,7 @@ fn is_supported_loop_match_type(ty: Ty<'_>) -> bool {
|
||||
args,
|
||||
unwind: UnwindAction::Continue,
|
||||
destination,
|
||||
// The presence or absence of a return edge affects control-flow sensitive
|
||||
// MIR checks and ultimately whether code is accepted or not. We can only
|
||||
// omit the return edge if a return type is visibly uninhabited to a module
|
||||
// that makes the call.
|
||||
target: expr
|
||||
.ty
|
||||
.is_inhabited_from(
|
||||
this.tcx,
|
||||
this.parent_module,
|
||||
this.infcx.typing_env(this.param_env),
|
||||
)
|
||||
.then_some(success),
|
||||
target: Some(success),
|
||||
call_source: if from_hir_call {
|
||||
CallSource::Normal
|
||||
} else {
|
||||
|
||||
@@ -170,7 +170,7 @@ pub(super) fn for_pattern(
|
||||
None
|
||||
}
|
||||
|
||||
PatKind::Binding { mode, var, ref subpattern, .. } => {
|
||||
PatKind::Binding { mode, var, is_shorthand, ref subpattern, .. } => {
|
||||
// In order to please the borrow checker, when lowering a pattern
|
||||
// like `x @ subpat` we must establish any bindings in `subpat`
|
||||
// before establishing the binding for `x`.
|
||||
@@ -209,6 +209,7 @@ pub(super) fn for_pattern(
|
||||
source,
|
||||
var_id: var,
|
||||
binding_mode: mode,
|
||||
is_shorthand,
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -579,6 +579,7 @@ pub(super) fn expr_into_pattern(
|
||||
block,
|
||||
var,
|
||||
irrefutable_pat.span,
|
||||
false,
|
||||
OutsideGuard,
|
||||
ScheduleDrops::Yes,
|
||||
);
|
||||
@@ -608,6 +609,7 @@ pub(super) fn expr_into_pattern(
|
||||
block,
|
||||
var,
|
||||
irrefutable_pat.span,
|
||||
false,
|
||||
OutsideGuard,
|
||||
ScheduleDrops::Yes,
|
||||
);
|
||||
@@ -739,6 +741,8 @@ pub(crate) fn declare_bindings(
|
||||
pattern,
|
||||
&ProjectedUserTypesNode::None,
|
||||
&mut |this, name, mode, var, span, ty, user_tys| {
|
||||
let saved_scope = this.source_scope;
|
||||
this.set_correct_source_scope_for_arg(var.0, saved_scope, span);
|
||||
let vis_scope = *visibility_scope
|
||||
.get_or_insert_with(|| this.new_source_scope(scope_span, LintLevel::Inherited));
|
||||
let source_info = SourceInfo { span, scope: this.source_scope };
|
||||
@@ -756,6 +760,7 @@ pub(crate) fn declare_bindings(
|
||||
opt_match_place.map(|(x, y)| (x.cloned(), y)),
|
||||
pattern.span,
|
||||
);
|
||||
this.source_scope = saved_scope;
|
||||
},
|
||||
);
|
||||
if let Some(guard_expr) = guard {
|
||||
@@ -799,6 +804,7 @@ pub(crate) fn storage_live_binding(
|
||||
block: BasicBlock,
|
||||
var: LocalVarId,
|
||||
span: Span,
|
||||
is_shorthand: bool,
|
||||
for_guard: ForGuard,
|
||||
schedule_drop: ScheduleDrops,
|
||||
) -> Place<'tcx> {
|
||||
@@ -812,6 +818,10 @@ pub(crate) fn storage_live_binding(
|
||||
{
|
||||
self.schedule_drop(span, region_scope, local_id, DropKind::Storage);
|
||||
}
|
||||
let local_info = self.local_decls[local_id].local_info.as_mut().unwrap_crate_local();
|
||||
if let LocalInfo::User(BindingForm::Var(var_info)) = &mut **local_info {
|
||||
var_info.introductions.push(VarBindingIntroduction { span, is_shorthand });
|
||||
}
|
||||
Place::from(local_id)
|
||||
}
|
||||
|
||||
@@ -1217,6 +1227,7 @@ struct Binding<'tcx> {
|
||||
source: Place<'tcx>,
|
||||
var_id: LocalVarId,
|
||||
binding_mode: BindingMode,
|
||||
is_shorthand: bool,
|
||||
}
|
||||
|
||||
/// Indicates that the type of `source` must be a subtype of the
|
||||
@@ -2725,6 +2736,7 @@ fn bind_matched_candidate_for_guard<'b>(
|
||||
block,
|
||||
binding.var_id,
|
||||
binding.span,
|
||||
binding.is_shorthand,
|
||||
RefWithinGuard,
|
||||
ScheduleDrops::Yes,
|
||||
);
|
||||
@@ -2742,6 +2754,7 @@ fn bind_matched_candidate_for_guard<'b>(
|
||||
block,
|
||||
binding.var_id,
|
||||
binding.span,
|
||||
binding.is_shorthand,
|
||||
OutsideGuard,
|
||||
ScheduleDrops::Yes,
|
||||
);
|
||||
@@ -2775,6 +2788,7 @@ fn bind_matched_candidate_for_arm_body<'b>(
|
||||
block,
|
||||
binding.var_id,
|
||||
binding.span,
|
||||
binding.is_shorthand,
|
||||
OutsideGuard,
|
||||
schedule_drops,
|
||||
);
|
||||
@@ -2827,6 +2841,7 @@ fn declare_binding(
|
||||
opt_ty_info: None,
|
||||
opt_match_place,
|
||||
pat_span,
|
||||
introductions: Vec::new(),
|
||||
},
|
||||
)))),
|
||||
};
|
||||
@@ -2849,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) {
|
||||
|
||||
@@ -24,10 +24,12 @@
|
||||
use rustc_middle::thir::{self, ExprId, LintLevel, LocalVarId, Param, ParamId, PatKind, Thir};
|
||||
use rustc_middle::ty::{self, ScalarInt, Ty, TyCtxt, TypeVisitableExt, TypingMode};
|
||||
use rustc_middle::{bug, span_bug};
|
||||
use rustc_session::lint;
|
||||
use rustc_span::{Span, Symbol, sym};
|
||||
|
||||
use crate::builder::expr::as_place::PlaceBuilder;
|
||||
use crate::builder::scope::DropKind;
|
||||
use crate::errors;
|
||||
|
||||
pub(crate) fn closure_saved_names_of_captured_variables<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
@@ -67,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.
|
||||
@@ -531,6 +528,7 @@ fn construct_fn<'tcx>(
|
||||
return_block.unit()
|
||||
});
|
||||
|
||||
builder.lint_and_remove_uninhabited();
|
||||
let mut body = builder.finish();
|
||||
|
||||
body.spread_arg = if abi == ExternAbi::RustCall {
|
||||
@@ -588,6 +586,7 @@ fn construct_const<'a, 'tcx>(
|
||||
|
||||
builder.build_drop_trees();
|
||||
|
||||
builder.lint_and_remove_uninhabited();
|
||||
builder.finish()
|
||||
}
|
||||
|
||||
@@ -806,6 +805,78 @@ fn dump_for_debugging(&self) {
|
||||
writer.write_mir_fn(&body, &mut std::io::stdout()).unwrap();
|
||||
}
|
||||
|
||||
fn lint_and_remove_uninhabited(&mut self) {
|
||||
let mut lints = vec![];
|
||||
|
||||
for bbdata in self.cfg.basic_blocks.iter_mut() {
|
||||
let term = bbdata.terminator_mut();
|
||||
let TerminatorKind::Call { ref mut target, destination, .. } = term.kind else {
|
||||
continue;
|
||||
};
|
||||
let Some(target_bb) = *target else { continue };
|
||||
|
||||
let ty = destination.ty(&self.local_decls, self.tcx).ty;
|
||||
let ty_is_inhabited = ty.is_inhabited_from(
|
||||
self.tcx,
|
||||
self.parent_module,
|
||||
self.infcx.typing_env(self.param_env),
|
||||
);
|
||||
|
||||
if !ty_is_inhabited {
|
||||
// Unreachable code warnings are already emitted during type checking.
|
||||
// However, during type checking, full type information is being
|
||||
// calculated but not yet available, so the check for diverging
|
||||
// expressions due to uninhabited result types is pretty crude and
|
||||
// only checks whether ty.is_never(). Here, we have full type
|
||||
// information available and can issue warnings for less obviously
|
||||
// uninhabited types (e.g. empty enums). The check above is used so
|
||||
// that we do not emit the same warning twice if the uninhabited type
|
||||
// is indeed `!`.
|
||||
if !ty.is_never() {
|
||||
lints.push((target_bb, ty, term.source_info.span));
|
||||
}
|
||||
|
||||
// The presence or absence of a return edge affects control-flow sensitive
|
||||
// MIR checks and ultimately whether code is accepted or not. We can only
|
||||
// omit the return edge if a return type is visibly uninhabited to a module
|
||||
// that makes the call.
|
||||
*target = None;
|
||||
}
|
||||
}
|
||||
|
||||
for (target_bb, orig_ty, orig_span) in lints {
|
||||
if orig_span.in_external_macro(self.tcx.sess.source_map()) {
|
||||
continue;
|
||||
}
|
||||
let target_bb = &self.cfg.basic_blocks[target_bb];
|
||||
let (target_loc, descr) = target_bb
|
||||
.statements
|
||||
.iter()
|
||||
.find_map(|stmt| match stmt.kind {
|
||||
StatementKind::StorageLive(_) | StatementKind::StorageDead(_) => None,
|
||||
StatementKind::FakeRead(..) => Some((stmt.source_info, "definition")),
|
||||
_ => Some((stmt.source_info, "expression")),
|
||||
})
|
||||
.unwrap_or_else(|| (target_bb.terminator().source_info, "expression"));
|
||||
let lint_root = self.source_scopes[target_loc.scope]
|
||||
.local_data
|
||||
.as_ref()
|
||||
.unwrap_crate_local()
|
||||
.lint_root;
|
||||
self.tcx.emit_node_span_lint(
|
||||
lint::builtin::UNREACHABLE_CODE,
|
||||
lint_root,
|
||||
target_loc.span,
|
||||
errors::UnreachableDueToUninhabited {
|
||||
expr: target_loc.span,
|
||||
orig: orig_span,
|
||||
descr,
|
||||
ty: orig_ty,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(self) -> Body<'tcx> {
|
||||
let mut body = Body::new(
|
||||
MirSource::item(self.def_id.to_def_id()),
|
||||
@@ -978,6 +1049,10 @@ fn args_and_body(
|
||||
opt_ty_info: param.ty_span,
|
||||
opt_match_place: Some((None, span)),
|
||||
pat_span: span,
|
||||
introductions: vec![VarBindingIntroduction {
|
||||
span,
|
||||
is_shorthand: false,
|
||||
}],
|
||||
}))
|
||||
};
|
||||
self.var_indices.insert(var, LocalsForNode::One(local));
|
||||
|
||||
@@ -720,6 +720,18 @@ pub(crate) struct WantedConstant {
|
||||
pub(crate) const_path: String,
|
||||
}
|
||||
|
||||
#[derive(LintDiagnostic)]
|
||||
#[diag(mir_build_unreachable_due_to_uninhabited)]
|
||||
pub(crate) struct UnreachableDueToUninhabited<'desc, 'tcx> {
|
||||
pub descr: &'desc str,
|
||||
#[label]
|
||||
pub expr: Span,
|
||||
#[label(mir_build_label_orig)]
|
||||
#[note]
|
||||
pub orig: Span,
|
||||
pub ty: Ty<'tcx>,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(mir_build_const_pattern_depends_on_generic_parameter, code = E0158)]
|
||||
pub(crate) struct ConstPatternDependsOnGenericParameter {
|
||||
|
||||
@@ -369,6 +369,7 @@ fn lower_pattern_unadjusted(&mut self, pat: &'tcx hir::Pat<'tcx>) -> Box<Pat<'tc
|
||||
ty: var_ty,
|
||||
subpattern: self.lower_opt_pattern(sub),
|
||||
is_primary: id == pat.hir_id,
|
||||
is_shorthand: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,9 +387,13 @@ fn lower_pattern_unadjusted(&mut self, pat: &'tcx hir::Pat<'tcx>) -> Box<Pat<'tc
|
||||
let res = self.typeck_results.qpath_res(qpath, pat.hir_id);
|
||||
let subpatterns = fields
|
||||
.iter()
|
||||
.map(|field| FieldPat {
|
||||
field: self.typeck_results.field_index(field.hir_id),
|
||||
pattern: *self.lower_pattern(field.pat),
|
||||
.map(|field| {
|
||||
let mut pattern = *self.lower_pattern(field.pat);
|
||||
if let PatKind::Binding { ref mut is_shorthand, .. } = pattern.kind {
|
||||
*is_shorthand = field.is_shorthand;
|
||||
}
|
||||
let field = self.typeck_results.field_index(field.hir_id);
|
||||
FieldPat { field, pattern }
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
||||
@@ -705,13 +705,14 @@ fn print_pat_kind(&mut self, pat_kind: &PatKind<'tcx>, depth_lvl: usize) {
|
||||
self.print_pat(subpattern, depth_lvl + 3);
|
||||
print_indented!(self, "}", depth_lvl + 1);
|
||||
}
|
||||
PatKind::Binding { name, mode, var, ty, subpattern, is_primary } => {
|
||||
PatKind::Binding { name, mode, var, ty, subpattern, is_primary, is_shorthand } => {
|
||||
print_indented!(self, "Binding {", depth_lvl + 1);
|
||||
print_indented!(self, format!("name: {:?}", name), depth_lvl + 2);
|
||||
print_indented!(self, format!("mode: {:?}", mode), depth_lvl + 2);
|
||||
print_indented!(self, format!("var: {:?}", var), depth_lvl + 2);
|
||||
print_indented!(self, format!("ty: {:?}", ty), depth_lvl + 2);
|
||||
print_indented!(self, format!("is_primary: {:?}", is_primary), depth_lvl + 2);
|
||||
print_indented!(self, format!("is_shorthand: {:?}", is_shorthand), depth_lvl + 2);
|
||||
|
||||
if let Some(subpattern) = subpattern {
|
||||
print_indented!(self, "subpattern: Some( ", depth_lvl + 2);
|
||||
|
||||
@@ -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,28 @@ 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
|
||||
|
||||
mir_transform_unused_variable_typo = you might have meant to pattern match on the similarly named {$kind} `{$item_name}`
|
||||
|
||||
@@ -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,132 @@ 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,
|
||||
#[subdiagnostic]
|
||||
pub typo: Option<PatternTypo>,
|
||||
}
|
||||
|
||||
#[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,
|
||||
#[subdiagnostic]
|
||||
typo: Option<PatternTypo>,
|
||||
},
|
||||
|
||||
#[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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Subdiagnostic)]
|
||||
#[multipart_suggestion(
|
||||
mir_transform_unused_variable_typo,
|
||||
style = "verbose",
|
||||
applicability = "maybe-incorrect"
|
||||
)]
|
||||
pub(crate) struct PatternTypo {
|
||||
#[suggestion_part(code = "{code}")]
|
||||
pub span: Span,
|
||||
pub code: String,
|
||||
pub item_name: Symbol,
|
||||
pub kind: &'static str,
|
||||
}
|
||||
|
||||
pub(crate) struct MustNotSupend<'a, 'tcx> {
|
||||
pub tcx: TyCtxt<'tcx>,
|
||||
pub yield_sp: Span,
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
mod ffi_unwind_calls;
|
||||
mod lint;
|
||||
mod lint_tail_expr_drop_order;
|
||||
mod liveness;
|
||||
mod patch;
|
||||
mod shim;
|
||||
mod ssa;
|
||||
@@ -216,6 +217,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,
|
||||
@@ -513,6 +515,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();
|
||||
|
||||
|
||||
@@ -0,0 +1,1401 @@
|
||||
use rustc_abi::FieldIdx;
|
||||
use rustc_data_structures::fx::{FxHashSet, FxIndexMap, IndexEntry};
|
||||
use rustc_hir::attrs::AttributeKind;
|
||||
use rustc_hir::def::{CtorKind, DefKind};
|
||||
use rustc_hir::def_id::{DefId, 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::print::with_no_trimmed_paths;
|
||||
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::edit_distance::find_best_match_for_name;
|
||||
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
|
||||
}
|
||||
|
||||
/// Give a diagnostic when an unused variable may be a typo of a unit variant or a struct.
|
||||
fn maybe_suggest_unit_pattern_typo<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
body_def_id: DefId,
|
||||
name: Symbol,
|
||||
span: Span,
|
||||
ty: Ty<'tcx>,
|
||||
) -> Option<errors::PatternTypo> {
|
||||
if let ty::Adt(adt_def, _) = ty.peel_refs().kind() {
|
||||
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, _))))
|
||||
{
|
||||
return Some(errors::PatternTypo {
|
||||
span,
|
||||
code: with_no_trimmed_paths!(tcx.def_path_str(variant.def_id)),
|
||||
kind: tcx.def_descr(variant.def_id),
|
||||
item_name: variant.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Look for consts of the same type with similar names as well,
|
||||
// not just unit structs and variants.
|
||||
let constants = tcx
|
||||
.hir_body_owners()
|
||||
.filter(|&def_id| {
|
||||
matches!(tcx.def_kind(def_id), DefKind::Const)
|
||||
&& tcx.type_of(def_id).instantiate_identity() == ty
|
||||
&& tcx.visibility(def_id).is_accessible_from(body_def_id, tcx)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let names = constants.iter().map(|&def_id| tcx.item_name(def_id)).collect::<Vec<_>>();
|
||||
if let Some(item_name) = find_best_match_for_name(&names, name, None)
|
||||
&& let Some(position) = names.iter().position(|&n| n == item_name)
|
||||
&& let Some(&def_id) = constants.get(position)
|
||||
{
|
||||
return Some(errors::PatternTypo {
|
||||
span,
|
||||
code: with_no_trimmed_paths!(tcx.def_path_str(def_id)),
|
||||
kind: "constant",
|
||||
item_name,
|
||||
});
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// 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::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 maybe_suggest_typo = || {
|
||||
if let LocalKind::Arg = self.body.local_kind(local) {
|
||||
None
|
||||
} else {
|
||||
maybe_suggest_unit_pattern_typo(
|
||||
tcx,
|
||||
self.body.source.def_id(),
|
||||
name,
|
||||
def_span,
|
||||
decl.ty,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let statements = &mut self.assignments[index];
|
||||
if statements.is_empty() {
|
||||
let sugg = if from_macro {
|
||||
errors::UnusedVariableSugg::NoSugg { span: def_span, name }
|
||||
} else {
|
||||
let typo = maybe_suggest_typo();
|
||||
errors::UnusedVariableSugg::TryPrefix { spans: vec![def_span], name, typo }
|
||||
};
|
||||
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;
|
||||
}
|
||||
|
||||
let typo = maybe_suggest_typo();
|
||||
tcx.emit_node_span_lint(
|
||||
lint::builtin::UNUSED_VARIABLES,
|
||||
hir_id,
|
||||
def_span,
|
||||
errors::UnusedVarAssignedOnly { name, typo },
|
||||
);
|
||||
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() {
|
||||
let typo = maybe_suggest_typo();
|
||||
errors::UnusedVariableSugg::TryPrefix { name, typo, spans: spans.clone() }
|
||||
} else {
|
||||
let typo = maybe_suggest_typo();
|
||||
errors::UnusedVariableSugg::TryPrefix { name, typo, 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::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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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...
|
||||
@@ -619,11 +615,6 @@ passes_unnecessary_partial_stable_feature = the feature `{$feature}` has been pa
|
||||
|
||||
passes_unnecessary_stable_feature = the feature `{$feature}` has been stable since {$since} and no longer requires an attribute to enable
|
||||
|
||||
passes_unreachable_due_to_uninhabited = unreachable {$descr}
|
||||
.label = unreachable {$descr}
|
||||
.label_orig = any code following this expression is unreachable
|
||||
.note = this expression has type `{$ty}`, which is uninhabited
|
||||
|
||||
passes_unrecognized_argument =
|
||||
unrecognized argument
|
||||
|
||||
@@ -641,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
|
||||
|
||||
@@ -676,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
|
||||
|
||||
@@ -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,73 +1308,6 @@ pub(crate) struct ProcMacroBadSig {
|
||||
pub kind: ProcMacroKind,
|
||||
}
|
||||
|
||||
#[derive(LintDiagnostic)]
|
||||
#[diag(passes_unreachable_due_to_uninhabited)]
|
||||
pub(crate) struct UnreachableDueToUninhabited<'desc, 'tcx> {
|
||||
pub descr: &'desc str,
|
||||
#[label]
|
||||
pub expr: Span,
|
||||
#[label(passes_label_orig)]
|
||||
#[note]
|
||||
pub orig: Span,
|
||||
pub ty: Ty<'tcx>,
|
||||
}
|
||||
|
||||
#[derive(LintDiagnostic)]
|
||||
#[diag(passes_unused_var_maybe_capture_ref)]
|
||||
#[help]
|
||||
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 {
|
||||
@@ -1399,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]
|
||||
|
||||
@@ -23,13 +23,11 @@
|
||||
mod diagnostic_items;
|
||||
pub mod entry;
|
||||
mod errors;
|
||||
#[cfg(debug_assertions)]
|
||||
pub mod hir_id_validator;
|
||||
pub mod input_stats;
|
||||
mod lang_items;
|
||||
pub mod layout_test;
|
||||
mod lib_features;
|
||||
mod liveness;
|
||||
mod reachable;
|
||||
pub mod stability;
|
||||
mod upvars;
|
||||
@@ -45,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);
|
||||
|
||||
@@ -1,1898 +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, Ty, 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) {
|
||||
return succ;
|
||||
}
|
||||
match self.ir.lnks[succ] {
|
||||
LiveNodeKind::ExprNode(succ_span, succ_id) => {
|
||||
self.warn_about_unreachable(expr.span, ty, succ_span, succ_id, "expression");
|
||||
}
|
||||
LiveNodeKind::VarDefNode(succ_span, succ_id) => {
|
||||
self.warn_about_unreachable(expr.span, ty, succ_span, succ_id, "definition");
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
self.exit_ln
|
||||
}
|
||||
|
||||
fn warn_about_unreachable<'desc>(
|
||||
&mut self,
|
||||
orig_span: Span,
|
||||
orig_ty: Ty<'tcx>,
|
||||
expr_span: Span,
|
||||
expr_id: HirId,
|
||||
descr: &'desc str,
|
||||
) {
|
||||
if !orig_ty.is_never() {
|
||||
// Unreachable code warnings are already emitted during type checking.
|
||||
// However, during type checking, full type information is being
|
||||
// calculated but not yet available, so the check for diverging
|
||||
// expressions due to uninhabited result types is pretty crude and
|
||||
// only checks whether ty.is_never(). Here, we have full type
|
||||
// information available and can issue warnings for less obviously
|
||||
// uninhabited types (e.g. empty enums). The check above is used so
|
||||
// that we do not emit the same warning twice if the uninhabited type
|
||||
// is indeed `!`.
|
||||
|
||||
self.ir.tcx.emit_node_span_lint(
|
||||
lint::builtin::UNREACHABLE_CODE,
|
||||
expr_id,
|
||||
expr_span,
|
||||
errors::UnreachableDueToUninhabited {
|
||||
expr: expr_span,
|
||||
orig: orig_span,
|
||||
descr,
|
||||
ty: orig_ty,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// _______________________________________________________________________
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,9 @@
|
||||
//! while it is locked exclusively (write mode). If a panic occurs in any reader,
|
||||
//! then the lock will not be poisoned.
|
||||
|
||||
// If we are not unwinding, `PoisonError` is uninhabited.
|
||||
#![cfg_attr(not(panic = "unwind"), expect(unreachable_code))]
|
||||
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
pub use self::condvar::Condvar;
|
||||
#[unstable(feature = "mapped_lock_guards", issue = "117108")]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![warn(clippy::needless_match)]
|
||||
#![allow(clippy::manual_map)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
#![allow(unused)]
|
||||
#[derive(Clone, Copy)]
|
||||
enum Simple {
|
||||
A,
|
||||
|
||||
@@ -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)]
|
||||
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
//@[tree]compile-flags: -Zmiri-tree-borrows
|
||||
// Validation forces more things into memory, which we can't have here.
|
||||
//@compile-flags: -Zmiri-disable-validation
|
||||
|
||||
#![feature(custom_mir, core_intrinsics)]
|
||||
#![allow(unused)]
|
||||
|
||||
use std::intrinsics::mir::*;
|
||||
|
||||
pub struct S(i32);
|
||||
|
||||
@@ -22,6 +22,7 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(unused_variables, unused_assignments)]
|
||||
pub fn change_arg(mut x: S) {
|
||||
x.0 = 0;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(unused_variables, unused_assignments)]
|
||||
pub fn change_arg(mut x: S, ptr: *mut S) {
|
||||
x.0 = 0;
|
||||
// If `x` got passed in-place, we'd see the write through `ptr`!
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use std::mem::size_of;
|
||||
|
||||
#[expect(unused_variables, unused_assignments)]
|
||||
fn main() {
|
||||
let mut a = Params::new();
|
||||
// The array itself here happens to be quite well-aligned, but not all its elements have that
|
||||
|
||||
@@ -405,6 +405,8 @@ fn recursive_normalize(use_tree: &ast::UseTree, style: NormalizationStyle) -> Op
|
||||
} else {
|
||||
ted::replace_with_many(subtree.syntax(), elements);
|
||||
}
|
||||
// Silence unused assignment warning on `modified`.
|
||||
let _ = modified;
|
||||
modified = true;
|
||||
} else {
|
||||
modified |= recursive_normalize(&subtree, NormalizationStyle::Default).is_some();
|
||||
|
||||
@@ -383,6 +383,8 @@ pub fn parse_with_proc_macros(
|
||||
}
|
||||
}
|
||||
|
||||
let _ = file_id;
|
||||
|
||||
let root = match current_source_root_kind {
|
||||
SourceRootKind::Local => SourceRoot::new_local(mem::take(&mut file_set)),
|
||||
SourceRootKind::Library => SourceRoot::new_library(mem::take(&mut file_set)),
|
||||
|
||||
@@ -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]",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
warning: unreachable statement
|
||||
--> $DIR/goto.rs:144:9
|
||||
--> $DIR/goto.rs:145:9
|
||||
|
|
||||
LL | / asm!(
|
||||
LL | | "jmp {}",
|
||||
@@ -13,10 +13,19 @@ LL | unreachable!();
|
||||
| ^^^^^^^^^^^^^^ unreachable statement
|
||||
|
|
||||
note: the lint level is defined here
|
||||
--> $DIR/goto.rs:134:8
|
||||
--> $DIR/goto.rs:135:8
|
||||
|
|
||||
LL | #[warn(unreachable_code)]
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: 1 warning emitted
|
||||
warning: value assigned to `value` is never read
|
||||
--> $DIR/goto.rs:71:25
|
||||
|
|
||||
LL | let mut value = false;
|
||||
| ^^^^^
|
||||
|
|
||||
= help: maybe it is overwritten before being read?
|
||||
= note: `#[warn(unused_assignments)]` (part of `#[warn(unused)]`) 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
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+75
-10
@@ -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
|
||||
|
||||
|
||||
+8
-8
@@ -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>`
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#[should_panic(expected = "creating inhabited type")]
|
||||
fn test() {
|
||||
FontLanguageOverride::system_font(SystemFont::new());
|
||||
//~^ WARNING unreachable expression
|
||||
}
|
||||
|
||||
pub enum FontLanguageOverride {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
warning: unreachable expression
|
||||
--> $DIR/issue-46519.rs:9:5
|
||||
|
|
||||
LL | FontLanguageOverride::system_font(SystemFont::new());
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-----------------^
|
||||
| | |
|
||||
| | any code following this expression is unreachable
|
||||
| unreachable expression
|
||||
|
|
||||
note: this expression has type `SystemFont`, which is uninhabited
|
||||
--> $DIR/issue-46519.rs:9:39
|
||||
|
|
||||
LL | FontLanguageOverride::system_font(SystemFont::new());
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
= note: `#[warn(unreachable_code)]` (part of `#[warn(unused)]`) on by default
|
||||
|
||||
warning: 1 warning emitted
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//@ ignore-backends: gcc
|
||||
//@ edition:2024
|
||||
|
||||
#![allow(deprecated, invalid_value)]
|
||||
#![allow(deprecated, invalid_value, unreachable_code)]
|
||||
#![feature(never_type, rustc_private)]
|
||||
|
||||
use std::{
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
//@ run-pass
|
||||
|
||||
#![allow(unused_assignments)]
|
||||
|
||||
struct S {
|
||||
o: Option<String>
|
||||
}
|
||||
|
||||
@@ -17,6 +17,6 @@ fn main() {
|
||||
let s = S;
|
||||
let x = s.f();
|
||||
//~^ WARNING: unused variable: `x`
|
||||
//~| WARNING: unreachable definition
|
||||
let _y = x;
|
||||
//~^ WARNING: unreachable definition
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
warning: unreachable definition
|
||||
--> $DIR/issue-85071-2.rs:20:9
|
||||
--> $DIR/issue-85071-2.rs:18:9
|
||||
|
|
||||
LL | let x = s.f();
|
||||
| ----- any code following this expression is unreachable
|
||||
LL |
|
||||
LL | let _y = x;
|
||||
| ^^ unreachable definition
|
||||
| ^ ----- any code following this expression is unreachable
|
||||
| |
|
||||
| unreachable definition
|
||||
|
|
||||
note: this expression has type `Foo`, which is uninhabited
|
||||
--> $DIR/issue-85071-2.rs:18:13
|
||||
|
||||
@@ -14,6 +14,6 @@ fn f() -> Foo {todo!()}
|
||||
fn main() {
|
||||
let x = f();
|
||||
//~^ WARNING: unused variable: `x`
|
||||
//~| WARNING: unreachable definition
|
||||
let _ = x;
|
||||
//~^ WARNING: unreachable expression
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
warning: unreachable expression
|
||||
--> $DIR/issue-85071.rs:17:13
|
||||
warning: unreachable definition
|
||||
--> $DIR/issue-85071.rs:15:9
|
||||
|
|
||||
LL | let x = f();
|
||||
| --- any code following this expression is unreachable
|
||||
LL |
|
||||
LL | let _ = x;
|
||||
| ^ unreachable expression
|
||||
| ^ --- any code following this expression is unreachable
|
||||
| |
|
||||
| unreachable definition
|
||||
|
|
||||
note: this expression has type `Foo`, which is uninhabited
|
||||
--> $DIR/issue-85071.rs:15:13
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -24,14 +24,14 @@ 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 => {}
|
||||
| ++++++++++
|
||||
help: if this is intentional, prefix it with an underscore
|
||||
|
|
||||
LL | _Foo => {}
|
||||
| +
|
||||
|
||||
warning: unused variable: `Foo`
|
||||
--> $DIR/lint-uppercase-variables.rs:28:9
|
||||
@@ -39,14 +39,14 @@ warning: unused variable: `Foo`
|
||||
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
|
||||
|
|
||||
LL | let _Foo = foo::Foo::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
|
||||
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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?
|
||||
|
||||
|
||||
@@ -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() {
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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() {}
|
||||
|
||||
@@ -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) { }
|
||||
|
||||
@@ -16,8 +16,8 @@ fn foo(x: (Lol, Lol)) {
|
||||
//~| HELP: you might have meant to use the similarly named previously used binding `Bar`
|
||||
//~| NOTE: pattern doesn't bind `Ban`
|
||||
//~| NOTE: variable not in all patterns
|
||||
//~| ERROR: variable `Ban` is assigned to, but never used
|
||||
//~| NOTE: consider using `_Ban` instead
|
||||
//~| ERROR: unused variable: `Ban`
|
||||
//~| HELP: if this is intentional, prefix it with an underscore
|
||||
//~| HELP: you might have meant to pattern match on the similarly named
|
||||
_ => {}
|
||||
}
|
||||
@@ -27,8 +27,8 @@ fn foo(x: (Lol, Lol)) {
|
||||
//~| HELP: you might have meant to use the similarly named unit variant `Bar`
|
||||
//~| NOTE: pattern doesn't bind `Ban`
|
||||
//~| NOTE: variable not in all patterns
|
||||
//~| ERROR: variable `Ban` is assigned to, but never used
|
||||
//~| NOTE: consider using `_Ban` instead
|
||||
//~| ERROR: unused variable: `Ban`
|
||||
//~| HELP: if this is intentional, prefix it with an underscore
|
||||
//~| HELP: you might have meant to pattern match on the similarly named
|
||||
_ => {}
|
||||
}
|
||||
@@ -73,8 +73,8 @@ fn bar(x: (Lol, Lol)) {
|
||||
//~| HELP: you might have meant to use the similarly named constant `Bat`
|
||||
//~| NOTE: pattern doesn't bind `Ban`
|
||||
//~| NOTE: variable not in all patterns
|
||||
//~| ERROR: variable `Ban` is assigned to, but never used
|
||||
//~| NOTE: consider using `_Ban` instead
|
||||
//~| ERROR: unused variable: `Ban`
|
||||
//~| HELP: if this is intentional, prefix it with an underscore
|
||||
//~| HELP: you might have meant to pattern match on the similarly named
|
||||
_ => {}
|
||||
}
|
||||
@@ -89,8 +89,8 @@ fn baz(x: (Lol, Lol)) {
|
||||
//~| HELP: you might have meant to use the similarly named constant `Bat`
|
||||
//~| NOTE: pattern doesn't bind `Ban`
|
||||
//~| NOTE: variable not in all patterns
|
||||
//~| ERROR: variable `Ban` is assigned to, but never used
|
||||
//~| NOTE: consider using `_Ban` instead
|
||||
//~| ERROR: unused variable: `Ban`
|
||||
//~| HELP: if this is intentional, prefix it with an underscore
|
||||
//~| HELP: you might have meant to pattern match on the similarly named
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -97,13 +97,12 @@ 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) => {}
|
||||
| ^^^
|
||||
|
|
||||
= note: consider using `_Ban` instead
|
||||
note: the lint level is defined here
|
||||
--> $DIR/binding-typo-2.rs:2:9
|
||||
|
|
||||
@@ -114,19 +113,26 @@ 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) => {}
|
||||
|
|
||||
help: if this is intentional, prefix it with an underscore
|
||||
|
|
||||
LL | (Foo, Bar) | (_Ban, 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
|
||||
|
|
||||
LL | (Foo, _) | (_Ban, Foo) => {}
|
||||
| +
|
||||
|
||||
error: unused variable: `Non`
|
||||
--> $DIR/binding-typo-2.rs:37:9
|
||||
@@ -134,15 +140,15 @@ error: unused variable: `Non`
|
||||
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
|
||||
|
|
||||
LL | _Non => {}
|
||||
| +
|
||||
|
||||
error: unused variable: `Non`
|
||||
--> $DIR/binding-typo-2.rs:44:9
|
||||
@@ -150,15 +156,15 @@ error: unused variable: `Non`
|
||||
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
|
||||
|
|
||||
LL | _Non | None => {}
|
||||
| +
|
||||
|
||||
error: unused variable: `Non`
|
||||
--> $DIR/binding-typo-2.rs:54:9
|
||||
@@ -166,41 +172,47 @@ error: unused variable: `Non`
|
||||
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
|
||||
|
|
||||
LL | _Non | Some(_) => {}
|
||||
| +
|
||||
|
||||
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
|
||||
|
|
||||
LL | (Foo, _) | (_Ban, Foo) => {}
|
||||
| +
|
||||
|
||||
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
|
||||
|
|
||||
LL | (Foo, _) | (_Ban, Foo) => {}
|
||||
| +
|
||||
|
||||
error: unused variable: `Ban`
|
||||
--> $DIR/binding-typo-2.rs:98:10
|
||||
@@ -208,15 +220,15 @@ error: unused variable: `Ban`
|
||||
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
|
||||
|
|
||||
LL | (_Ban, _) => {}
|
||||
| +
|
||||
|
||||
error: unused variable: `Ban`
|
||||
--> $DIR/binding-typo-2.rs:104:9
|
||||
@@ -224,15 +236,15 @@ error: unused variable: `Ban`
|
||||
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
|
||||
|
|
||||
LL | _Ban => {}
|
||||
| +
|
||||
|
||||
error: unused variable: `Batery`
|
||||
--> $DIR/binding-typo-2.rs:110:9
|
||||
@@ -240,14 +252,14 @@ error: unused variable: `Batery`
|
||||
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
|
||||
|
|
||||
LL | _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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user