1use rustc_abi::FieldIdx;2use rustc_data_structures::fx::{FxHashSet, FxIndexMap, IndexEntry};3use rustc_hir::def::{CtorKind, DefKind};4use rustc_hir::def_id::{DefId, LocalDefId};5use rustc_hir::find_attr;6use rustc_index::IndexVec;7use rustc_index::bit_set::DenseBitSet;8use rustc_lint_defs::builtin::{UNUSED_ASSIGNMENTS, UNUSED_VARIABLES};9use rustc_middle::bug;10use rustc_middle::mir::visit::{11 MutatingUseContext, NonMutatingUseContext, NonUseContext, PlaceContext, Visitor,12};13use rustc_middle::mir::*;14use rustc_middle::ty::print::with_no_trimmed_paths;15use rustc_middle::ty::{self, Ty, TyCtxt};16use rustc_mir_dataflow::fmt::DebugWithContext;17use rustc_mir_dataflow::{Analysis, Backward, ResultsCursor};18use rustc_span::Span;19use rustc_span::edit_distance::find_best_match_for_name;20use rustc_span::symbol::{Symbol, kw, sym};2122use crate::diagnostics;2324#[derive(Copy, Clone, Debug, PartialEq, Eq)]25enum AccessKind {26 Param,27 Assign,28 Capture,29}3031#[derive(Copy, Clone, Debug, PartialEq, Eq)]32enum CaptureKind {33 Closure(ty::ClosureKind),34 Coroutine,35 CoroutineClosure,36 None,37}3839#[derive(Copy, Clone, Debug)]40struct Access {41 /// Describe the current access.42 kind: AccessKind,43 /// MIR location where this access happens.44 location: Location,45 /// Is the accessed place is live at the current statement?46 /// When we encounter multiple statements at the same location, we only increase the liveness,47 /// in order to avoid false positives.48 live: bool,49 /// Is this a direct access to the place itself, no projections, or to a field?50 /// This helps distinguish `x = ...` from `x.field = ...`51 is_direct: bool,52}5354#[tracing::instrument(level = "debug", skip(tcx), ret)]55pub(crate) fn check_liveness<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> DenseBitSet<FieldIdx> {56 // Don't run on synthetic MIR, as that will ICE trying to access HIR.57 if tcx.is_synthetic_mir(def_id) {58 return DenseBitSet::new_empty(0);59 }6061 // Don't run unused pass for intrinsics62 if tcx.intrinsic(def_id.to_def_id()).is_some() {63 return DenseBitSet::new_empty(0);64 }6566 // Don't run unused pass for #[naked]67 if find_attr!(tcx, def_id.to_def_id(), Naked(..)) {68 return DenseBitSet::new_empty(0);69 }7071 // Don't run unused pass for #[derive]72 let parent = tcx.local_parent(tcx.typeck_root_def_id_local(def_id));73 if let DefKind::Impl { of_trait: true } = tcx.def_kind(parent)74 && find_attr!(tcx, parent, AutomaticallyDerived)75 {76 return DenseBitSet::new_empty(0);77 }7879 let mut body = &*tcx.mir_promoted(def_id).0.borrow();80 let mut body_mem;8182 // Don't run if there are errors.83 if body.tainted_by_errors.is_some() {84 return DenseBitSet::new_empty(0);85 }8687 let mut checked_places = PlaceSet::default();88 checked_places.insert_locals(&body.local_decls);8990 // The body is the one of a closure or generator, so we also want to analyse captures.91 let (capture_kind, num_captures) = if tcx.is_closure_like(def_id.to_def_id()) {92 let mut self_ty = body.local_decls[ty::CAPTURE_STRUCT_LOCAL].ty;93 let mut self_is_ref = false;94 if let ty::Ref(_, ty, _) = self_ty.kind() {95 self_ty = *ty;96 self_is_ref = true;97 }9899 let (capture_kind, args) = match self_ty.kind() {100 ty::Closure(_, args) => {101 (CaptureKind::Closure(args.as_closure().kind()), ty::UpvarArgs::Closure(args))102 }103 &ty::Coroutine(_, args) => (CaptureKind::Coroutine, ty::UpvarArgs::Coroutine(args)),104 &ty::CoroutineClosure(_, args) => {105 (CaptureKind::CoroutineClosure, ty::UpvarArgs::CoroutineClosure(args))106 }107 _ => bug!("expected closure or generator, found {:?}", self_ty),108 };109110 let captures = tcx.closure_captures(def_id);111 checked_places.insert_captures(tcx, self_is_ref, captures, args.upvar_tys());112113 // `FnMut` closures can modify captured values and carry those114 // modified values with them in subsequent calls. To model this behaviour,115 // we consider the `FnMut` closure as jumping to `bb0` upon return.116 if let CaptureKind::Closure(ty::ClosureKind::FnMut) = capture_kind {117 // FIXME: stop cloning the body.118 body_mem = body.clone();119 for bbdata in body_mem.basic_blocks_mut() {120 // We can call a closure again, either after a normal return or an unwind.121 if let TerminatorKind::Return | TerminatorKind::UnwindResume =122 bbdata.terminator().kind123 {124 bbdata.terminator_mut().kind = TerminatorKind::Goto { target: START_BLOCK };125 }126 }127 body = &body_mem;128 }129130 (capture_kind, args.upvar_tys().len())131 } else {132 (CaptureKind::None, 0)133 };134135 // Get the remaining variables' names from debuginfo.136 checked_places.record_debuginfo(&body.var_debug_info);137138 let self_assignment = find_self_assignments(&checked_places, body);139140 let mut live =141 MaybeLivePlaces { tcx, capture_kind, checked_places: &checked_places, self_assignment }142 .iterate_to_fixpoint(tcx, body, None)143 .into_results_cursor(body);144145 let typing_env = ty::TypingEnv::post_analysis(tcx, body.source.def_id());146147 let mut assignments =148 AssignmentResult::find_dead_assignments(tcx, typing_env, &checked_places, &mut live, body);149150 assignments.merge_guards();151152 let dead_captures = assignments.compute_dead_captures(num_captures);153154 assignments.report_fully_unused();155 assignments.report_unused_assignments();156157 dead_captures158}159160/// Small helper to make semantics easier to read.161#[inline]162fn is_capture(place: PlaceRef<'_>) -> bool {163 if !place.projection.is_empty() {164 debug_assert_eq!(place.local, ty::CAPTURE_STRUCT_LOCAL);165 true166 } else {167 false168 }169}170171/// Give a diagnostic when an unused variable may be a typo of a unit variant or a struct.172fn maybe_suggest_unit_pattern_typo<'tcx>(173 tcx: TyCtxt<'tcx>,174 body_def_id: DefId,175 name: Symbol,176 span: Span,177 ty: Ty<'tcx>,178) -> Option<diagnostics::PatternTypo> {179 if let ty::Adt(adt_def, _) = ty.peel_refs().kind() {180 let variant_names: Vec<_> = adt_def181 .variants()182 .iter()183 .filter(|v| matches!(v.ctor, Some((CtorKind::Const, _))))184 .map(|v| v.name)185 .collect();186 if let Some(name) = find_best_match_for_name(&variant_names, name, None)187 && let Some(variant) = adt_def188 .variants()189 .iter()190 .find(|v| v.name == name && matches!(v.ctor, Some((CtorKind::Const, _))))191 {192 return Some(diagnostics::PatternTypo {193 span,194 code: with_no_trimmed_paths!(tcx.def_path_str(variant.def_id)),195 kind: tcx.def_descr(variant.def_id),196 item_name: variant.name,197 });198 }199 }200201 // Look for consts of the same type with similar names as well,202 // not just unit structs and variants.203 let constants = tcx204 .hir_body_owners()205 .filter(|&def_id| {206 matches!(tcx.def_kind(def_id), DefKind::Const { .. })207 && tcx.type_of(def_id).instantiate_identity().skip_norm_wip() == ty208 && tcx.visibility(def_id).is_accessible_from(body_def_id, tcx)209 })210 .collect::<Vec<_>>();211 let names = constants.iter().map(|&def_id| tcx.item_name(def_id)).collect::<Vec<_>>();212 if let Some(item_name) = find_best_match_for_name(&names, name, None)213 && let Some(position) = names.iter().position(|&n| n == item_name)214 && let Some(&def_id) = constants.get(position)215 {216 return Some(diagnostics::PatternTypo {217 span,218 code: with_no_trimmed_paths!(tcx.def_path_str(def_id)),219 kind: "constant",220 item_name,221 });222 }223224 None225}226227/// Return whether we should consider the current place as a drop guard and skip reporting.228fn maybe_drop_guard<'tcx>(229 tcx: TyCtxt<'tcx>,230 typing_env: ty::TypingEnv<'tcx>,231 index: PlaceIndex,232 ever_dropped: &DenseBitSet<PlaceIndex>,233 checked_places: &PlaceSet<'tcx>,234 body: &Body<'tcx>,235) -> bool {236 if ever_dropped.contains(index) {237 let ty = checked_places.places[index].ty(&body.local_decls, tcx).ty;238 // FIXME(#155345): Liveness uses `TypingMode::PostAnalysis`239 // even though it's run on `mir_promoted` which is still240 // in an earlier `TypingMode`. This is odd and we have to241 // manually mark aliases as non-rigid here.242 let ty = ty::set_aliases_to_non_rigid(tcx, ty).skip_norm_wip();243 matches!(244 ty.kind(),245 ty::Closure(..)246 | ty::Coroutine(..)247 | ty::Tuple(..)248 | ty::Adt(..)249 | ty::Dynamic(..)250 | ty::Array(..)251 | ty::Slice(..)252 | ty::Alias(_, ty::AliasTy { kind: ty::Opaque { .. }, .. })253 ) && ty.needs_drop(tcx, typing_env)254 } else {255 false256 }257}258259/// Detect the following case260///261/// ```text262/// fn change_object(mut a: &Ty) {263/// let a = Ty::new();264/// b = &a;265/// }266/// ```267///268/// where the user likely meant to modify the value behind there reference, use `a` as an out269/// parameter, instead of mutating the local binding. When encountering this we suggest:270///271/// ```text272/// fn change_object(a: &'_ mut Ty) {273/// let a = Ty::new();274/// *b = a;275/// }276/// ```277fn annotate_mut_binding_to_immutable_binding<'tcx>(278 tcx: TyCtxt<'tcx>,279 place: PlaceRef<'tcx>,280 body_def_id: LocalDefId,281 assignment_span: Span,282 body: &Body<'tcx>,283) -> Option<diagnostics::UnusedAssignSuggestion> {284 use rustc_hir as hir;285 use rustc_hir::intravisit::{self, Visitor};286287 // Verify we have a mutable argument...288 let local = place.as_local()?;289 let LocalKind::Arg = body.local_kind(local) else { return None };290 let Mutability::Mut = body.local_decls[local].mutability else { return None };291292 // ... with reference type...293 let hir_param_index =294 local.as_usize() - if tcx.is_closure_like(body_def_id.to_def_id()) { 2 } else { 1 };295 let fn_decl = tcx.hir_node_by_def_id(body_def_id).fn_decl()?;296 let ty = fn_decl.inputs[hir_param_index];297 let hir::TyKind::Ref(lt, mut_ty) = ty.kind else { return None };298299 // ... as a binding pattern.300 let hir_body = tcx.hir_maybe_body_owned_by(body_def_id)?;301 let param = hir_body.params[hir_param_index];302 let hir::PatKind::Binding(hir::BindingMode::MUT, _hir_id, ident, _) = param.pat.kind else {303 return None;304 };305306 // Find the assignment to modify.307 let mut finder = ExprFinder { assignment_span, lhs: None, rhs: None };308 finder.visit_body(hir_body);309 let lhs = finder.lhs?;310 let rhs = finder.rhs?;311312 let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _mut, inner) = rhs.kind else { return None };313314 // Changes to the parameter's type.315 let pre = if lt.ident.span.is_empty() { "" } else { " " };316 let ty_span = if mut_ty.mutbl.is_mut() {317 // Leave `&'name mut Ty` and `&mut Ty` as they are (#136028).318 None319 } else {320 // `&'name Ty` -> `&'name mut Ty` or `&Ty` -> `&mut Ty`321 Some(mut_ty.ty.span.shrink_to_lo())322 };323324 return Some(diagnostics::UnusedAssignSuggestion {325 ty_span,326 pre,327 // Span of the `mut` before the binding.328 ty_ref_span: param.pat.span.until(ident.span),329 // Where to add a `*`.330 pre_lhs_span: lhs.span.shrink_to_lo(),331 // Where to remove the borrow.332 rhs_borrow_span: rhs.span.until(inner.span),333 });334335 #[derive(Debug)]336 struct ExprFinder<'hir> {337 assignment_span: Span,338 lhs: Option<&'hir hir::Expr<'hir>>,339 rhs: Option<&'hir hir::Expr<'hir>>,340 }341 impl<'hir> Visitor<'hir> for ExprFinder<'hir> {342 fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {343 if expr.span == self.assignment_span344 && let hir::ExprKind::Assign(lhs, rhs, _) = expr.kind345 {346 self.lhs = Some(lhs);347 self.rhs = Some(rhs);348 } else {349 intravisit::walk_expr(self, expr)350 }351 }352 }353}354355/// Compute self-assignments of the form `a += b`.356///357/// MIR building generates 2 statements and 1 terminator for such assignments:358/// - _temp = CheckedBinaryOp(a, b)359/// - assert(!_temp.1)360/// - a = _temp.0361///362/// This function tries to detect this pattern in order to avoid marking statement as a definition363/// and use. This will let the analysis be dictated by the next use of `a`.364///365/// Note that we will still need to account for the use of `b`.366fn find_self_assignments<'tcx>(367 checked_places: &PlaceSet<'tcx>,368 body: &Body<'tcx>,369) -> FxHashSet<Location> {370 let mut self_assign = FxHashSet::default();371372 const FIELD_0: FieldIdx = FieldIdx::from_u32(0);373 const FIELD_1: FieldIdx = FieldIdx::from_u32(1);374375 for (bb, bb_data) in body.basic_blocks.iter_enumerated() {376 for (statement_index, stmt) in bb_data.statements.iter().enumerate() {377 let StatementKind::Assign((first_place, rvalue)) = &stmt.kind else { continue };378 match rvalue {379 // For checked binary ops, the MIR builder inserts an assertion in between.380 Rvalue::BinaryOp(381 BinOp::AddWithOverflow | BinOp::SubWithOverflow | BinOp::MulWithOverflow,382 (Operand::Copy(lhs), _),383 ) => {384 // Checked binary ops only appear at the end of the block, before the assertion.385 if statement_index + 1 != bb_data.statements.len() {386 continue;387 }388389 let TerminatorKind::Assert {390 cond, target, msg: AssertKind::Overflow(..), ..391 } = &bb_data.terminator().kind392 else {393 continue;394 };395 let Some(assign) = body.basic_blocks[*target].statements.first() else {396 continue;397 };398 let StatementKind::Assign((dest, Rvalue::Use(Operand::Move(temp), _))) =399 assign.kind400 else {401 continue;402 };403404 if dest != *lhs {405 continue;406 }407408 let Operand::Move(cond) = cond else { continue };409 let [PlaceElem::Field(FIELD_0, _)] = &temp.projection.as_slice() else {410 continue;411 };412 let [PlaceElem::Field(FIELD_1, _)] = &cond.projection.as_slice() else {413 continue;414 };415416 // We ignore indirect self-assignment, because both occurrences of `dest` are uses.417 let is_indirect = checked_places418 .get(dest.as_ref())419 .map_or(false, |(_, projections)| is_indirect(projections));420 if is_indirect {421 continue;422 }423424 if first_place.local == temp.local425 && first_place.local == cond.local426 && first_place.projection.is_empty()427 {428 // Original block429 self_assign.insert(Location {430 block: bb,431 statement_index: bb_data.statements.len() - 1,432 });433 self_assign.insert(Location {434 block: bb,435 statement_index: bb_data.statements.len(),436 });437 // Target block438 self_assign.insert(Location { block: *target, statement_index: 0 });439 }440 }441 // Straight self-assignment.442 Rvalue::BinaryOp(op, (Operand::Copy(lhs), _)) => {443 if lhs != first_place {444 continue;445 }446447 // We ignore indirect self-assignment, because both occurrences of `dest` are uses.448 let is_indirect = checked_places449 .get(first_place.as_ref())450 .map_or(false, |(_, projections)| is_indirect(projections));451 if is_indirect {452 continue;453 }454455 self_assign.insert(Location { block: bb, statement_index });456457 // Checked division verifies overflow before performing the division, so we458 // need to go and ignore this check in the predecessor block.459 if let BinOp::Div | BinOp::Rem = op460 && statement_index == 0461 && let &[pred] = body.basic_blocks.predecessors()[bb].as_slice()462 && let TerminatorKind::Assert { msg, .. } =463 &body.basic_blocks[pred].terminator().kind464 && let AssertKind::Overflow(..) = **msg465 && let len = body.basic_blocks[pred].statements.len()466 && len >= 2467 {468 // BitAnd of two checks.469 self_assign.insert(Location { block: pred, statement_index: len - 1 });470 // `lhs == MIN`.471 self_assign.insert(Location { block: pred, statement_index: len - 2 });472 }473 }474 _ => {}475 }476 }477 }478479 self_assign480}481482#[derive(Default, Debug)]483struct PlaceSet<'tcx> {484 places: IndexVec<PlaceIndex, PlaceRef<'tcx>>,485 names: IndexVec<PlaceIndex, Option<(Symbol, Span)>>,486487 /// Places corresponding to locals, common case.488 locals: IndexVec<Local, Option<PlaceIndex>>,489490 // Handling of captures.491 /// If `_1` is a reference, we need to add a `Deref` to the matched place.492 capture_field_pos: usize,493 /// Captured fields.494 captures: IndexVec<FieldIdx, (PlaceIndex, bool)>,495}496497impl<'tcx> PlaceSet<'tcx> {498 fn insert_locals(&mut self, decls: &IndexVec<Local, LocalDecl<'tcx>>) {499 self.locals = IndexVec::from_elem(None, &decls);500 for (local, decl) in decls.iter_enumerated() {501 // Record all user-written locals for the analysis.502 // We also keep the `RefForGuard` locals (more on that below).503 if let LocalInfo::User(BindingForm::Var(_) | BindingForm::RefForGuard(_)) =504 decl.local_info()505 {506 let index = self.places.push(local.into());507 self.locals[local] = Some(index);508 let _index = self.names.push(None);509 debug_assert_eq!(index, _index);510 }511 }512 }513514 fn insert_captures(515 &mut self,516 tcx: TyCtxt<'tcx>,517 self_is_ref: bool,518 captures: &[&'tcx ty::CapturedPlace<'tcx>],519 upvars: &ty::List<Ty<'tcx>>,520 ) {521 // We should not track the environment local separately.522 debug_assert_eq!(self.locals[ty::CAPTURE_STRUCT_LOCAL], None);523524 let self_place = Place {525 local: ty::CAPTURE_STRUCT_LOCAL,526 projection: tcx.mk_place_elems(if self_is_ref { &[PlaceElem::Deref] } else { &[] }),527 };528 if self_is_ref {529 self.capture_field_pos = 1;530 }531532 for (f, (capture, ty)) in std::iter::zip(captures, upvars).enumerate() {533 let f = FieldIdx::from_usize(f);534 let elem = PlaceElem::Field(f, ty);535 let by_ref = matches!(capture.info.capture_kind, ty::UpvarCapture::ByRef(..));536 let place = if by_ref {537 self_place.project_deeper(&[elem, PlaceElem::Deref], tcx)538 } else {539 self_place.project_deeper(&[elem], tcx)540 };541 let index = self.places.push(place.as_ref());542 let _f = self.captures.push((index, by_ref));543 debug_assert_eq!(_f, f);544545 // Record a variable name from the capture, because it is much friendlier than the546 // debuginfo name.547 self.names.insert(548 index,549 (Symbol::intern(&capture.to_string(tcx)), capture.get_path_span(tcx)),550 );551 }552 }553554 fn record_debuginfo(&mut self, var_debug_info: &Vec<VarDebugInfo<'tcx>>) {555 let ignore_name = |name: Symbol| {556 name == sym::empty || name == kw::SelfLower || name.as_str().starts_with('_')557 };558 for var_debug_info in var_debug_info {559 if let VarDebugInfoContents::Place(place) = var_debug_info.value560 && let Some(index) = self.locals[place.local]561 && !ignore_name(var_debug_info.name)562 {563 self.names.get_or_insert_with(index, || {564 (var_debug_info.name, var_debug_info.source_info.span)565 });566 }567 }568569 // Discard places that will not result in a diagnostic.570 for index_opt in self.locals.iter_mut() {571 if let Some(index) = *index_opt {572 let remove = match self.names[index] {573 None => true,574 Some((name, _)) => ignore_name(name),575 };576 if remove {577 *index_opt = None;578 }579 }580 }581 }582583 #[inline]584 fn get(&self, place: PlaceRef<'tcx>) -> Option<(PlaceIndex, &'tcx [PlaceElem<'tcx>])> {585 if let Some(index) = self.locals[place.local] {586 return Some((index, place.projection));587 }588 if place.local == ty::CAPTURE_STRUCT_LOCAL589 && !self.captures.is_empty()590 && self.capture_field_pos < place.projection.len()591 && let PlaceElem::Field(f, _) = place.projection[self.capture_field_pos]592 && let Some((index, by_ref)) = self.captures.get(f)593 {594 let mut start = self.capture_field_pos + 1;595 if *by_ref {596 // Account for an extra Deref.597 start += 1;598 }599 // We may have an attempt to access `_1.f` as a shallow reborrow. Just ignore it.600 if start <= place.projection.len() {601 let projection = &place.projection[start..];602 return Some((*index, projection));603 }604 }605 None606 }607608 fn iter(&self) -> impl Iterator<Item = (PlaceIndex, &PlaceRef<'tcx>)> {609 self.places.iter_enumerated()610 }611612 fn len(&self) -> usize {613 self.places.len()614 }615}616617struct AssignmentResult<'a, 'tcx> {618 tcx: TyCtxt<'tcx>,619 typing_env: ty::TypingEnv<'tcx>,620 checked_places: &'a PlaceSet<'tcx>,621 body: &'a Body<'tcx>,622 /// Set of locals that are live at least once. This is used to report fully unused locals.623 ever_live: DenseBitSet<PlaceIndex>,624 /// Set of locals that have a non-trivial drop. This is used to skip reporting unused625 /// assignment if it would be used by the `Drop` impl.626 ever_dropped: DenseBitSet<PlaceIndex>,627 /// Set of assignments for each local. Here, assignment is understood in the AST sense. Any628 /// MIR that may look like an assignment (Assign, DropAndReplace, Yield, Call) are considered.629 ///630 /// For each local, we return a map: for each source position, whether the statement is live631 /// and which kind of access it performs. When we encounter multiple statements at the same632 /// location, we only increase the liveness, in order to avoid false positives.633 assignments: IndexVec<PlaceIndex, FxIndexMap<SourceInfo, Access>>,634}635636impl<'a, 'tcx> AssignmentResult<'a, 'tcx> {637 /// Collect all assignments to checked locals.638 ///639 /// Assignments are collected, even if they are live. Dead assignments are reported, and live640 /// assignments are used to make diagnostics correct for match guards.641 fn find_dead_assignments(642 tcx: TyCtxt<'tcx>,643 typing_env: ty::TypingEnv<'tcx>,644 checked_places: &'a PlaceSet<'tcx>,645 cursor: &mut ResultsCursor<'_, 'tcx, MaybeLivePlaces<'_, 'tcx>>,646 body: &'a Body<'tcx>,647 ) -> AssignmentResult<'a, 'tcx> {648 let mut ever_live = DenseBitSet::new_empty(checked_places.len());649 let mut ever_dropped = DenseBitSet::new_empty(checked_places.len());650 let mut assignments = IndexVec::<PlaceIndex, FxIndexMap<_, _>>::from_elem(651 Default::default(),652 &checked_places.places,653 );654655 let mut check_place = |place: Place<'tcx>,656 kind,657 source_info: SourceInfo,658 location: Location,659 live: &DenseBitSet<PlaceIndex>| {660 if let Some((index, extra_projections)) = checked_places.get(place.as_ref()) {661 if !is_indirect(extra_projections) {662 let is_direct = extra_projections.is_empty();663 match assignments[index].entry(source_info) {664 IndexEntry::Vacant(v) => {665 let access =666 Access { kind, location, live: live.contains(index), is_direct };667 v.insert(access);668 }669 IndexEntry::Occupied(mut o) => {670 // There were already a sighting. Mark this statement as live if it671 // was, to avoid false positives.672 o.get_mut().live |= live.contains(index);673 o.get_mut().is_direct &= is_direct;674 }675 }676 }677 }678 };679680 let mut record_drop = |place: Place<'tcx>| {681 if let Some((index, &[])) = checked_places.get(place.as_ref()) {682 ever_dropped.insert(index);683 }684 };685686 for (bb, bb_data) in traversal::postorder(body) {687 cursor.seek_to_block_end(bb);688 let live = cursor.get();689 ever_live.union(live);690691 let terminator = bb_data.terminator();692 match &terminator.kind {693 TerminatorKind::Call { destination: place, .. }694 | TerminatorKind::Yield { resume_arg: place, .. } => {695 check_place(696 *place,697 AccessKind::Assign,698 terminator.source_info,699 body.terminator_loc(bb),700 live,701 );702 record_drop(*place)703 }704 TerminatorKind::Drop { place, .. } => record_drop(*place),705 TerminatorKind::InlineAsm { operands, .. } => {706 for operand in operands {707 if let InlineAsmOperand::Out { place: Some(place), .. }708 | InlineAsmOperand::InOut { out_place: Some(place), .. } = operand709 {710 check_place(711 *place,712 AccessKind::Assign,713 terminator.source_info,714 body.terminator_loc(bb),715 live,716 );717 }718 }719 }720 _ => {}721 }722723 for (statement_index, statement) in bb_data.statements.iter().enumerate().rev() {724 let location = Location { block: bb, statement_index };725 cursor.seek_before_primary_effect(location);726 let live = cursor.get();727 ever_live.union(live);728 match &statement.kind {729 StatementKind::Assign((place, _)) => {730 check_place(731 *place,732 AccessKind::Assign,733 statement.source_info,734 location,735 live,736 );737 }738 StatementKind::SetDiscriminant { place, .. } => {739 check_place(740 **place,741 AccessKind::Assign,742 statement.source_info,743 location,744 live,745 );746 }747 StatementKind::StorageLive(_)748 | StatementKind::StorageDead(_)749 | StatementKind::Coverage(_)750 | StatementKind::Intrinsic(_)751 | StatementKind::Nop752 | StatementKind::FakeRead(_)753 | StatementKind::PlaceMention(_)754 | StatementKind::ConstEvalCounter755 | StatementKind::BackwardIncompatibleDropHint { .. }756 | StatementKind::AscribeUserType(_, _) => (),757 }758 }759 }760761 // Check liveness of function arguments on entry.762 {763 cursor.seek_to_block_start(START_BLOCK);764 let live = cursor.get();765 ever_live.union(live);766767 // Verify that arguments and captured values are useful.768 for (index, place) in checked_places.iter() {769 let kind = if is_capture(*place) {770 // This is a by-ref capture, an assignment to it will modify surrounding771 // environment, so we do not report it.772 if place.projection.last() == Some(&PlaceElem::Deref) {773 continue;774 }775776 AccessKind::Capture777 } else if body.local_kind(place.local) == LocalKind::Arg {778 AccessKind::Param779 } else {780 continue;781 };782 let source_info = body.local_decls[place.local].source_info;783 let access = Access {784 kind,785 location: Location::START,786 live: live.contains(index),787 is_direct: true,788 };789 assignments[index].insert(source_info, access);790 }791 }792793 AssignmentResult {794 tcx,795 typing_env,796 checked_places,797 ever_live,798 ever_dropped,799 assignments,800 body,801 }802 }803804 /// Match guards introduce a different local to freeze the guarded value as immutable.805 /// Having two locals, we need to make sure that we do not report an unused_variable806 /// when the guard local is used but not the arm local, or vice versa, like in this example.807 ///808 /// match 5 {809 /// x if x > 2 => {}810 /// ^ ^- This is `local`811 /// +------ This is `arm_local`812 /// _ => {}813 /// }814 ///815 fn merge_guards(&mut self) {816 for (index, place) in self.checked_places.iter() {817 let local = place.local;818 if let &LocalInfo::User(BindingForm::RefForGuard(arm_local)) =819 self.body.local_decls[local].local_info()820 {821 debug_assert!(place.projection.is_empty());822823 // Local to use in the arm.824 let Some((arm_index, _proj)) = self.checked_places.get(arm_local.into()) else {825 continue;826 };827 debug_assert_ne!(index, arm_index);828 debug_assert_eq!(_proj, &[]);829830 // Mark the arm local as used if the guard local is used.831 if self.ever_live.contains(index) {832 self.ever_live.insert(arm_index);833 }834835 // Some assignments are common to both locals in the source code.836 // Sadly, we can only detect this using the `source_info`.837 // Therefore, we loop over all the assignments we have for the guard local:838 // - if they already appeared for the arm local, the assignment is live if one of the839 // two versions is live;840 // - if it does not appear for the arm local, it happened inside the guard, so we add841 // it as-is.842 let guard_assignments = std::mem::take(&mut self.assignments[index]);843 let arm_assignments = &mut self.assignments[arm_index];844 for (source_info, access) in guard_assignments {845 match arm_assignments.entry(source_info) {846 IndexEntry::Vacant(v) => {847 v.insert(access);848 }849 IndexEntry::Occupied(mut o) => {850 o.get_mut().live |= access.live;851 }852 }853 }854 }855 }856 }857858 /// Compute captures that are fully dead.859 fn compute_dead_captures(&self, num_captures: usize) -> DenseBitSet<FieldIdx> {860 // Report to caller the set of dead captures.861 let mut dead_captures = DenseBitSet::new_empty(num_captures);862 for (index, place) in self.checked_places.iter() {863 if self.ever_live.contains(index) {864 continue;865 }866867 // This is a capture: pass information to the enclosing function.868 if is_capture(*place) {869 for p in place.projection {870 if let PlaceElem::Field(f, _) = p {871 dead_captures.insert(*f);872 break;873 }874 }875 continue;876 }877 }878879 dead_captures880 }881882 /// Check if a local is referenced in any reachable basic block.883 /// Variables in unreachable code (e.g., after `todo!()`) should not trigger unused warnings.884 fn is_local_in_reachable_code(&self, local: Local) -> bool {885 struct LocalVisitor {886 target_local: Local,887 found: bool,888 }889890 impl<'tcx> Visitor<'tcx> for LocalVisitor {891 fn visit_local(&mut self, local: Local, _context: PlaceContext, _location: Location) {892 if local == self.target_local {893 self.found = true;894 }895 }896 }897898 let mut visitor = LocalVisitor { target_local: local, found: false };899 for (bb, bb_data) in traversal::postorder(self.body) {900 visitor.visit_basic_block_data(bb, bb_data);901 if visitor.found {902 return true;903 }904 }905906 false907 }908909 /// Check for source-level uses that may have been removed from reachable MIR.910 /// For example:911 /// ```rust912 /// fn example() {913 /// let x = todo!();914 /// eprintln!("{x}");915 /// }916 /// ```917 /// The use of x is unreachable, but we'll still want to know if x is used to correctly emit918 /// unused variable warning.919 fn is_local_used_in_source(&self, name: Symbol, def_span: Span) -> bool {920 use rustc_hir as hir;921 use rustc_hir::def::Res;922 use rustc_hir::intravisit::{self, Visitor};923924 let Some(body_def_id) = self.body.source.def_id().as_local() else { return false };925 let Some(hir_body) = self.tcx.hir_maybe_body_owned_by(body_def_id) else { return false };926 let typeck_results = self.tcx.typeck(body_def_id);927928 struct LocalUseVisitor<'a, 'tcx> {929 tcx: TyCtxt<'tcx>,930 typeck_results: &'a ty::TypeckResults<'tcx>,931 name: Symbol,932 def_span: Span,933 found: bool,934 }935936 impl<'a, 'tcx> Visitor<'tcx> for LocalUseVisitor<'a, 'tcx> {937 fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {938 if self.found {939 return;940 }941942 if let hir::ExprKind::Path(qpath) = &expr.kind943 && let Res::Local(hir_id) = self.typeck_results.qpath_res(qpath, expr.hir_id)944 && self.tcx.hir_name(hir_id) == self.name945 && self.tcx.hir_span(hir_id) == self.def_span946 {947 self.found = true;948 return;949 }950951 intravisit::walk_expr(self, expr);952 }953 }954955 let mut visitor =956 LocalUseVisitor { tcx: self.tcx, typeck_results, name, def_span, found: false };957 visitor.visit_body(hir_body);958 visitor.found959 }960961 /// Report fully unused locals, and forget the corresponding assignments.962 fn report_fully_unused(&mut self) {963 let tcx = self.tcx;964965 // Give a diagnostic when any of the string constants look like a naked format string that966 // would interpolate our dead local.967 let mut string_constants_in_body = None;968 let mut maybe_suggest_literal_matching_name = |name: Symbol| {969 // Visiting MIR to enumerate string constants can be expensive, so cache the result.970 let string_constants_in_body = string_constants_in_body.get_or_insert_with(|| {971 struct LiteralFinder {972 found: Vec<(Span, String)>,973 }974975 impl<'tcx> Visitor<'tcx> for LiteralFinder {976 fn visit_const_operand(&mut self, constant: &ConstOperand<'tcx>, _: Location) {977 if let ty::Ref(_, ref_ty, _) = constant.ty().kind()978 && ref_ty.kind() == &ty::Str979 {980 let rendered_constant = constant.const_.to_string();981 self.found.push((constant.span, rendered_constant));982 }983 }984 }985986 let mut finder = LiteralFinder { found: vec![] };987 finder.visit_body(self.body);988 finder.found989 });990991 let brace_name = format!("{{{name}");992 string_constants_in_body993 .iter()994 .filter(|(_, rendered_constant)| {995 rendered_constant996 .split(&brace_name)997 .any(|c| matches!(c.chars().next(), Some('}' | ':')))998 })999 .map(|&(lit, _)| diagnostics::UnusedVariableStringInterp { lit })1000 .collect::<Vec<_>>()1001 };10021003 // First, report fully unused locals.1004 for (index, place) in self.checked_places.iter() {1005 if self.ever_live.contains(index) {1006 continue;1007 }10081009 // this is a capture: let the enclosing function report the unused variable.1010 if is_capture(*place) {1011 continue;1012 }10131014 let local = place.local;1015 let decl = &self.body.local_decls[local];10161017 if decl.from_compiler_desugaring() {1018 continue;1019 }10201021 // Only report actual user-defined binding from now on.1022 let LocalInfo::User(BindingForm::Var(binding)) = decl.local_info() else { continue };1023 let Some(hir_id) = decl.source_info.scope.lint_root(&self.body.source_scopes) else {1024 continue;1025 };10261027 let introductions = &binding.introductions;10281029 let Some((name, def_span)) = self.checked_places.names[index] else { continue };10301031 // #117284, when `ident_span` and `def_span` have different contexts1032 // we can't provide a good suggestion, instead we pointed out the spans from macro1033 let from_macro = def_span.from_expansion()1034 && introductions.iter().any(|intro| intro.span.eq_ctxt(def_span));10351036 let maybe_suggest_typo = || {1037 if let LocalKind::Arg = self.body.local_kind(local) {1038 None1039 } else {1040 maybe_suggest_unit_pattern_typo(1041 tcx,1042 self.body.source.def_id(),1043 name,1044 def_span,1045 decl.ty,1046 )1047 }1048 };10491050 // the is_local_used_in_source is sufficient to check if the local is used in the source code,1051 // but we keep the local_kind check for a cheap filter to avoid heavy check1052 let is_used_after_uninitialized = self.body.local_kind(local) == LocalKind::Temp1053 && matches!(binding.opt_match_place, Some((None, _)))1054 && self.is_local_used_in_source(name, def_span);10551056 let statements = &mut self.assignments[index];1057 if statements.is_empty() {1058 if is_used_after_uninitialized {1059 // A local from `let PAT = ...` normally has an assignment recorded for the1060 // value it initializes. If no assignment was recorded in reachable MIR, the1061 // initializer did not complete. If the local still has a source-level use,1062 // that use was made unreachable by the diverging initializer.1063 continue;1064 }10651066 if !self.is_local_in_reachable_code(local) {1067 continue;1068 }10691070 let sugg = if from_macro {1071 diagnostics::UnusedVariableSugg::NoSugg { span: def_span, name }1072 } else {1073 let typo = maybe_suggest_typo();1074 diagnostics::UnusedVariableSugg::TryPrefix { spans: vec![def_span], name, typo }1075 };1076 tcx.emit_node_span_lint(1077 UNUSED_VARIABLES,1078 hir_id,1079 def_span,1080 diagnostics::UnusedVariable {1081 name,1082 string_interp: maybe_suggest_literal_matching_name(name),1083 sugg,1084 },1085 );1086 continue;1087 }10881089 // Idiomatic rust assigns a value to a local upon definition. However, we do not want to1090 // warn twice, for the unused local and for the unused assignment. Therefore, we remove1091 // from the list of assignments the ones that happen at the definition site.1092 statements.retain(|source_info, _| {1093 !binding.introductions.iter().any(|intro| intro.span == source_info.span)1094 });10951096 // Extra assignments that we recognize thanks to the initialization span. We need to1097 // take care of macro contexts here to be accurate.1098 if let Some((_, initializer_span)) = binding.opt_match_place {1099 statements.retain(|source_info, _| {1100 let within = source_info.span.find_ancestor_inside(initializer_span);1101 let outer_initializer_span =1102 initializer_span.find_ancestor_in_same_ctxt(source_info.span);1103 within.is_none()1104 && outer_initializer_span.map_or(true, |s| !s.contains(source_info.span))1105 });1106 }11071108 if !statements.is_empty() {1109 // We have a dead local with outstanding assignments and with non-trivial drop.1110 // This is probably a drop-guard, so we do not issue a warning there.1111 if maybe_drop_guard(1112 tcx,1113 self.typing_env,1114 index,1115 &self.ever_dropped,1116 self.checked_places,1117 self.body,1118 ) {1119 statements.retain(|_, access| access.is_direct);1120 if statements.is_empty() {1121 continue;1122 }1123 }11241125 let typo = maybe_suggest_typo();1126 tcx.emit_node_span_lint(1127 UNUSED_VARIABLES,1128 hir_id,1129 def_span,1130 diagnostics::UnusedVarAssignedOnly { name, typo },1131 );1132 continue;1133 }11341135 // We do not have outstanding assignments, suggest renaming the binding.1136 let spans = introductions.iter().map(|intro| intro.span).collect::<Vec<_>>();11371138 let any_shorthand = introductions.iter().any(|intro| intro.is_shorthand);11391140 let sugg = if any_shorthand {1141 diagnostics::UnusedVariableSugg::TryIgnore {1142 name: name.to_ident_string(),1143 shorthands: introductions1144 .iter()1145 .filter_map(1146 |intro| if intro.is_shorthand { Some(intro.span) } else { None },1147 )1148 .collect(),1149 non_shorthands: introductions1150 .iter()1151 .filter_map(1152 |intro| {1153 if !intro.is_shorthand { Some(intro.span) } else { None }1154 },1155 )1156 .collect(),1157 }1158 } else if from_macro {1159 diagnostics::UnusedVariableSugg::NoSugg { span: def_span, name }1160 } else if !introductions.is_empty() {1161 let typo = maybe_suggest_typo();1162 diagnostics::UnusedVariableSugg::TryPrefix { name, typo, spans: spans.clone() }1163 } else {1164 let typo = maybe_suggest_typo();1165 diagnostics::UnusedVariableSugg::TryPrefix { name, typo, spans: vec![def_span] }1166 };11671168 tcx.emit_node_span_lint(1169 UNUSED_VARIABLES,1170 hir_id,1171 spans,1172 diagnostics::UnusedVariable {1173 name,1174 string_interp: maybe_suggest_literal_matching_name(name),1175 sugg,1176 },1177 );1178 }1179 }11801181 /// Second, report unused assignments that do not correspond to initialization.1182 /// Initializations have been removed in the previous loop reporting unused variables.1183 fn report_unused_assignments(self) {1184 let tcx = self.tcx;11851186 for (index, statements) in self.assignments.into_iter_enumerated() {1187 if statements.is_empty() {1188 continue;1189 }11901191 let Some((name, decl_span)) = self.checked_places.names[index] else { continue };11921193 let is_maybe_drop_guard = maybe_drop_guard(1194 tcx,1195 self.typing_env,1196 index,1197 &self.ever_dropped,1198 self.checked_places,1199 self.body,1200 );12011202 // By convention, underscore-prefixed bindings are allowed to be unused explicitly.1203 if name.as_str().starts_with('_') {1204 continue;1205 }12061207 let mut next_direct_assignments: Vec<(Span, Location)> = Vec::new();1208 let mut dead_statements = Vec::with_capacity(statements.len());12091210 for (source_info, Access { live, kind, is_direct, location }) in statements.into_iter()1211 {1212 let direct_assignment = kind == AccessKind::Assign && is_direct;1213 let should_report = !live && (is_direct || !is_maybe_drop_guard);12141215 let overwrite = if should_report && direct_assignment {1216 next_direct_assignments1217 .iter()1218 .rfind(|(_, overwrite_location)| {1219 location.is_predecessor_of(*overwrite_location, self.body)1220 })1221 .map(|&(overwrite_span, _)| diagnostics::UnusedAssignOverwrite {1222 assigned_span: source_info.span,1223 overwrite_span,1224 name,1225 })1226 } else {1227 None1228 };12291230 if direct_assignment {1231 next_direct_assignments.push((source_info.span, location));1232 }12331234 if !should_report {1235 continue;1236 }1237 dead_statements.push((source_info, kind, is_direct, overwrite));1238 }12391240 // We probed MIR in reverse order for dataflow.1241 // Emit diagnostics in source order instead.1242 for (source_info, kind, is_direct, overwrite) in dead_statements.into_iter().rev() {1243 // Report the dead assignment.1244 let Some(hir_id) = source_info.scope.lint_root(&self.body.source_scopes) else {1245 continue;1246 };12471248 match kind {1249 AccessKind::Assign => {1250 let suggestion = annotate_mut_binding_to_immutable_binding(1251 tcx,1252 self.checked_places.places[index],1253 self.body.source.def_id().expect_local(),1254 source_info.span,1255 self.body,1256 );1257 let overwrite =1258 if suggestion.is_none() && is_direct { overwrite } else { None };1259 let help = suggestion.is_none() && overwrite.is_none();1260 tcx.emit_node_span_lint(1261 UNUSED_ASSIGNMENTS,1262 hir_id,1263 source_info.span,1264 diagnostics::UnusedAssign { name, overwrite, help, suggestion },1265 )1266 }1267 AccessKind::Param => tcx.emit_node_span_lint(1268 UNUSED_ASSIGNMENTS,1269 hir_id,1270 source_info.span,1271 diagnostics::UnusedAssignPassed { name },1272 ),1273 AccessKind::Capture => tcx.emit_node_span_lint(1274 UNUSED_ASSIGNMENTS,1275 hir_id,1276 decl_span,1277 diagnostics::UnusedCaptureMaybeCaptureRef { name },1278 ),1279 }1280 }1281 }1282 }1283}12841285rustc_index::newtype_index! {1286 pub struct PlaceIndex {}1287}12881289impl DebugWithContext<MaybeLivePlaces<'_, '_>> for PlaceIndex {1290 fn fmt_with(1291 &self,1292 ctxt: &MaybeLivePlaces<'_, '_>,1293 f: &mut std::fmt::Formatter<'_>,1294 ) -> std::fmt::Result {1295 std::fmt::Debug::fmt(&ctxt.checked_places.places[*self], f)1296 }1297}12981299pub struct MaybeLivePlaces<'a, 'tcx> {1300 tcx: TyCtxt<'tcx>,1301 checked_places: &'a PlaceSet<'tcx>,1302 capture_kind: CaptureKind,1303 self_assignment: FxHashSet<Location>,1304}13051306impl<'tcx> MaybeLivePlaces<'_, 'tcx> {1307 fn transfer_function<'a>(1308 &'a self,1309 trans: &'a mut DenseBitSet<PlaceIndex>,1310 ) -> TransferFunction<'a, 'tcx> {1311 TransferFunction {1312 tcx: self.tcx,1313 checked_places: &self.checked_places,1314 capture_kind: self.capture_kind,1315 trans,1316 self_assignment: &self.self_assignment,1317 }1318 }1319}13201321impl<'tcx> Analysis<'tcx> for MaybeLivePlaces<'_, 'tcx> {1322 type Domain = DenseBitSet<PlaceIndex>;1323 type Direction = Backward;13241325 const NAME: &'static str = "liveness-lint";13261327 fn bottom_value(&self, _: &Body<'tcx>) -> Self::Domain {1328 // bottom = not live1329 DenseBitSet::new_empty(self.checked_places.len())1330 }13311332 fn initialize_start_block(&self, _: &Body<'tcx>, _: &mut Self::Domain) {1333 // No variables are live until we observe a use1334 }13351336 fn apply_primary_statement_effect(1337 &self,1338 trans: &mut Self::Domain,1339 statement: &Statement<'tcx>,1340 location: Location,1341 ) {1342 self.transfer_function(trans).visit_statement(statement, location);1343 }13441345 fn apply_primary_terminator_effect(1346 &self,1347 trans: &mut Self::Domain,1348 terminator: &Terminator<'tcx>,1349 location: Location,1350 ) {1351 self.transfer_function(trans).visit_terminator(terminator, location);1352 }13531354 fn apply_call_return_effect(1355 &self,1356 _trans: &mut Self::Domain,1357 _block: BasicBlock,1358 _return_places: CallReturnPlaces<'_, 'tcx>,1359 ) {1360 // FIXME: what should happen here?1361 }1362}13631364struct TransferFunction<'a, 'tcx> {1365 tcx: TyCtxt<'tcx>,1366 checked_places: &'a PlaceSet<'tcx>,1367 trans: &'a mut DenseBitSet<PlaceIndex>,1368 capture_kind: CaptureKind,1369 self_assignment: &'a FxHashSet<Location>,1370}13711372impl<'tcx> Visitor<'tcx> for TransferFunction<'_, 'tcx> {1373 fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {1374 match statement.kind {1375 // `ForLet(None)` and `ForGuardBinding` fake reads erroneously mark the just-assigned1376 // locals as live. This defeats the purpose of the analysis for such bindings.1377 StatementKind::FakeRead((1378 FakeReadCause::ForLet(None) | FakeReadCause::ForGuardBinding,1379 _,1380 )) => return,1381 // Handle self-assignment by restricting the read/write they do.1382 StatementKind::Assign((ref dest, ref rvalue))1383 if self.self_assignment.contains(&location) =>1384 {1385 if let Rvalue::BinaryOp(1386 BinOp::AddWithOverflow | BinOp::SubWithOverflow | BinOp::MulWithOverflow,1387 (_, rhs),1388 ) = rvalue1389 {1390 // We are computing the binary operation:1391 // - the LHS will be assigned, so we don't read it;1392 // - the RHS still needs to be read.1393 self.visit_operand(rhs, location);1394 self.visit_place(1395 dest,1396 PlaceContext::MutatingUse(MutatingUseContext::Store),1397 location,1398 );1399 } else if let Rvalue::BinaryOp(_, (_, rhs)) = rvalue {1400 // We are computing the binary operation:1401 // - the LHS is being updated, so we don't read it;1402 // - the RHS still needs to be read.1403 self.visit_operand(rhs, location);1404 } else {1405 // This is the second part of a checked self-assignment,1406 // we are assigning the result.1407 // We do not consider the write to the destination as a `def`.1408 // `self_assignment` must be false if the assignment is indirect.1409 self.visit_rvalue(rvalue, location);1410 }1411 }1412 _ => self.super_statement(statement, location),1413 }1414 }14151416 fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {1417 // By-ref captures could be read by the surrounding environment, so we mark1418 // them as live upon yield and return.1419 match terminator.kind {1420 TerminatorKind::Return1421 | TerminatorKind::Yield { .. }1422 | TerminatorKind::Goto { target: START_BLOCK } // Inserted for the `FnMut` case.1423 | TerminatorKind::Call { target: None, .. } // unwinding could be caught1424 if self.capture_kind != CaptureKind::None =>1425 {1426 // All indirect captures have an effect on the environment, so we mark them as live.1427 for (index, place) in self.checked_places.iter() {1428 if place.local == ty::CAPTURE_STRUCT_LOCAL1429 && place.projection.last() == Some(&PlaceElem::Deref)1430 {1431 self.trans.insert(index);1432 }1433 }1434 }1435 // Do not consider a drop to be a use. We whitelist interesting drops elsewhere.1436 TerminatorKind::Drop { .. } => {}1437 // Ignore assertions since they must be triggered by actual code.1438 TerminatorKind::Assert { .. } => {}1439 _ => self.super_terminator(terminator, location),1440 }1441 }14421443 fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {1444 match rvalue {1445 // When a closure/generator does not use some of its captures, do not consider these1446 // captures as live in the surrounding function. This allows to report unused variables,1447 // even if they have been (uselessly) captured.1448 Rvalue::Aggregate(1449 AggregateKind::Closure(def_id, _) | AggregateKind::Coroutine(def_id, _),1450 operands,1451 ) => {1452 if let Some(def_id) = def_id.as_local() {1453 let dead_captures = self.tcx.check_liveness(def_id);1454 for (field, operand) in1455 operands.iter_enumerated().take(dead_captures.domain_size())1456 {1457 if !dead_captures.contains(field) {1458 self.visit_operand(operand, location);1459 }1460 }1461 }1462 }1463 _ => self.super_rvalue(rvalue, location),1464 }1465 }14661467 fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, location: Location) {1468 if let Some((index, extra_projections)) = self.checked_places.get(place.as_ref()) {1469 for i in (extra_projections.len()..=place.projection.len()).rev() {1470 let place_part =1471 PlaceRef { local: place.local, projection: &place.projection[..i] };1472 let extra_projections = &place.projection[i..];14731474 if let Some(&elem) = extra_projections.get(0) {1475 self.visit_projection_elem(place_part, elem, context, location);1476 }1477 }14781479 match DefUse::for_place(extra_projections, context) {1480 Some(DefUse::Def) => {1481 self.trans.remove(index);1482 }1483 Some(DefUse::Use) => {1484 self.trans.insert(index);1485 }1486 None => {}1487 }1488 } else {1489 self.super_place(place, context, location)1490 }1491 }14921493 fn visit_local(&mut self, local: Local, context: PlaceContext, _: Location) {1494 if let Some((index, _proj)) = self.checked_places.get(local.into()) {1495 debug_assert_eq!(_proj, &[]);1496 match DefUse::for_place(&[], context) {1497 Some(DefUse::Def) => {1498 self.trans.remove(index);1499 }1500 Some(DefUse::Use) => {1501 self.trans.insert(index);1502 }1503 _ => {}1504 }1505 }1506 }1507}15081509#[derive(Eq, PartialEq, Debug, Clone)]1510enum DefUse {1511 Def,1512 Use,1513}15141515fn is_indirect(proj: &[PlaceElem<'_>]) -> bool {1516 proj.iter().any(|p| p.is_indirect())1517}15181519impl DefUse {1520 fn for_place<'tcx>(projection: &[PlaceElem<'tcx>], context: PlaceContext) -> Option<DefUse> {1521 let is_indirect = is_indirect(projection);1522 match context {1523 PlaceContext::MutatingUse(1524 MutatingUseContext::Store | MutatingUseContext::SetDiscriminant,1525 ) => {1526 if is_indirect {1527 // Treat derefs as a use of the base local. `*p = 4` is not a def of `p` but a1528 // use.1529 Some(DefUse::Use)1530 } else if projection.is_empty() {1531 Some(DefUse::Def)1532 } else {1533 None1534 }1535 }15361537 // For the associated terminators, this is only a `Def` when the terminator returns1538 // "successfully." As such, we handle this case separately in `call_return_effect`1539 // above. However, if the place looks like `*_5`, this is still unconditionally a use of1540 // `_5`.1541 PlaceContext::MutatingUse(1542 MutatingUseContext::Call1543 | MutatingUseContext::Yield1544 | MutatingUseContext::AsmOutput,1545 ) => is_indirect.then_some(DefUse::Use),15461547 // All other contexts are uses...1548 PlaceContext::MutatingUse(1549 MutatingUseContext::RawBorrow1550 | MutatingUseContext::Borrow1551 | MutatingUseContext::Drop,1552 )1553 | PlaceContext::NonMutatingUse(1554 NonMutatingUseContext::RawBorrow1555 | NonMutatingUseContext::Copy1556 | NonMutatingUseContext::Inspect1557 | NonMutatingUseContext::Move1558 | NonMutatingUseContext::FakeBorrow1559 | NonMutatingUseContext::SharedBorrow1560 | NonMutatingUseContext::PlaceMention,1561 ) => Some(DefUse::Use),15621563 PlaceContext::NonUse(1564 NonUseContext::StorageLive1565 | NonUseContext::StorageDead1566 | NonUseContext::AscribeUserTy(_)1567 | NonUseContext::BackwardIncompatibleDropHint1568 | NonUseContext::VarDebugInfo,1569 ) => None,15701571 PlaceContext::MutatingUse(MutatingUseContext::Projection)1572 | PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection) => {1573 unreachable!("A projection could be a def or a use and must be handled separately")1574 }1575 }1576 }1577}