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_middle::bug;9use rustc_middle::mir::visit::{10 MutatingUseContext, NonMutatingUseContext, NonUseContext, PlaceContext, Visitor,11};12use rustc_middle::mir::*;13use rustc_middle::ty::print::with_no_trimmed_paths;14use rustc_middle::ty::{self, Ty, TyCtxt};15use rustc_mir_dataflow::fmt::DebugWithContext;16use rustc_mir_dataflow::{Analysis, Backward, ResultsCursor};17use rustc_session::lint;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 matches!(239 ty.kind(),240 ty::Closure(..)241 | ty::Coroutine(..)242 | ty::Tuple(..)243 | ty::Adt(..)244 | ty::Dynamic(..)245 | ty::Array(..)246 | ty::Slice(..)247 | ty::Alias(ty::AliasTy { kind: ty::Opaque { .. }, .. })248 ) && ty.needs_drop(tcx, typing_env)249 } else {250 false251 }252}253254/// Detect the following case255///256/// ```text257/// fn change_object(mut a: &Ty) {258/// let a = Ty::new();259/// b = &a;260/// }261/// ```262///263/// where the user likely meant to modify the value behind there reference, use `a` as an out264/// parameter, instead of mutating the local binding. When encountering this we suggest:265///266/// ```text267/// fn change_object(a: &'_ mut Ty) {268/// let a = Ty::new();269/// *b = a;270/// }271/// ```272fn annotate_mut_binding_to_immutable_binding<'tcx>(273 tcx: TyCtxt<'tcx>,274 place: PlaceRef<'tcx>,275 body_def_id: LocalDefId,276 assignment_span: Span,277 body: &Body<'tcx>,278) -> Option<diagnostics::UnusedAssignSuggestion> {279 use rustc_hir as hir;280 use rustc_hir::intravisit::{self, Visitor};281282 // Verify we have a mutable argument...283 let local = place.as_local()?;284 let LocalKind::Arg = body.local_kind(local) else { return None };285 let Mutability::Mut = body.local_decls[local].mutability else { return None };286287 // ... with reference type...288 let hir_param_index =289 local.as_usize() - if tcx.is_closure_like(body_def_id.to_def_id()) { 2 } else { 1 };290 let fn_decl = tcx.hir_node_by_def_id(body_def_id).fn_decl()?;291 let ty = fn_decl.inputs[hir_param_index];292 let hir::TyKind::Ref(lt, mut_ty) = ty.kind else { return None };293294 // ... as a binding pattern.295 let hir_body = tcx.hir_maybe_body_owned_by(body_def_id)?;296 let param = hir_body.params[hir_param_index];297 let hir::PatKind::Binding(hir::BindingMode::MUT, _hir_id, ident, _) = param.pat.kind else {298 return None;299 };300301 // Find the assignment to modify.302 let mut finder = ExprFinder { assignment_span, lhs: None, rhs: None };303 finder.visit_body(hir_body);304 let lhs = finder.lhs?;305 let rhs = finder.rhs?;306307 let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _mut, inner) = rhs.kind else { return None };308309 // Changes to the parameter's type.310 let pre = if lt.ident.span.is_empty() { "" } else { " " };311 let ty_span = if mut_ty.mutbl.is_mut() {312 // Leave `&'name mut Ty` and `&mut Ty` as they are (#136028).313 None314 } else {315 // `&'name Ty` -> `&'name mut Ty` or `&Ty` -> `&mut Ty`316 Some(mut_ty.ty.span.shrink_to_lo())317 };318319 return Some(diagnostics::UnusedAssignSuggestion {320 ty_span,321 pre,322 // Span of the `mut` before the binding.323 ty_ref_span: param.pat.span.until(ident.span),324 // Where to add a `*`.325 pre_lhs_span: lhs.span.shrink_to_lo(),326 // Where to remove the borrow.327 rhs_borrow_span: rhs.span.until(inner.span),328 });329330 #[derive(Debug)]331 struct ExprFinder<'hir> {332 assignment_span: Span,333 lhs: Option<&'hir hir::Expr<'hir>>,334 rhs: Option<&'hir hir::Expr<'hir>>,335 }336 impl<'hir> Visitor<'hir> for ExprFinder<'hir> {337 fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {338 if expr.span == self.assignment_span339 && let hir::ExprKind::Assign(lhs, rhs, _) = expr.kind340 {341 self.lhs = Some(lhs);342 self.rhs = Some(rhs);343 } else {344 intravisit::walk_expr(self, expr)345 }346 }347 }348}349350/// Compute self-assignments of the form `a += b`.351///352/// MIR building generates 2 statements and 1 terminator for such assignments:353/// - _temp = CheckedBinaryOp(a, b)354/// - assert(!_temp.1)355/// - a = _temp.0356///357/// This function tries to detect this pattern in order to avoid marking statement as a definition358/// and use. This will let the analysis be dictated by the next use of `a`.359///360/// Note that we will still need to account for the use of `b`.361fn find_self_assignments<'tcx>(362 checked_places: &PlaceSet<'tcx>,363 body: &Body<'tcx>,364) -> FxHashSet<Location> {365 let mut self_assign = FxHashSet::default();366367 const FIELD_0: FieldIdx = FieldIdx::from_u32(0);368 const FIELD_1: FieldIdx = FieldIdx::from_u32(1);369370 for (bb, bb_data) in body.basic_blocks.iter_enumerated() {371 for (statement_index, stmt) in bb_data.statements.iter().enumerate() {372 let StatementKind::Assign((first_place, rvalue)) = &stmt.kind else { continue };373 match rvalue {374 // For checked binary ops, the MIR builder inserts an assertion in between.375 Rvalue::BinaryOp(376 BinOp::AddWithOverflow | BinOp::SubWithOverflow | BinOp::MulWithOverflow,377 (Operand::Copy(lhs), _),378 ) => {379 // Checked binary ops only appear at the end of the block, before the assertion.380 if statement_index + 1 != bb_data.statements.len() {381 continue;382 }383384 let TerminatorKind::Assert {385 cond, target, msg: AssertKind::Overflow(..), ..386 } = &bb_data.terminator().kind387 else {388 continue;389 };390 let Some(assign) = body.basic_blocks[*target].statements.first() else {391 continue;392 };393 let StatementKind::Assign((dest, Rvalue::Use(Operand::Move(temp), _))) =394 assign.kind395 else {396 continue;397 };398399 if dest != *lhs {400 continue;401 }402403 let Operand::Move(cond) = cond else { continue };404 let [PlaceElem::Field(FIELD_0, _)] = &temp.projection.as_slice() else {405 continue;406 };407 let [PlaceElem::Field(FIELD_1, _)] = &cond.projection.as_slice() else {408 continue;409 };410411 // We ignore indirect self-assignment, because both occurrences of `dest` are uses.412 let is_indirect = checked_places413 .get(dest.as_ref())414 .map_or(false, |(_, projections)| is_indirect(projections));415 if is_indirect {416 continue;417 }418419 if first_place.local == temp.local420 && first_place.local == cond.local421 && first_place.projection.is_empty()422 {423 // Original block424 self_assign.insert(Location {425 block: bb,426 statement_index: bb_data.statements.len() - 1,427 });428 self_assign.insert(Location {429 block: bb,430 statement_index: bb_data.statements.len(),431 });432 // Target block433 self_assign.insert(Location { block: *target, statement_index: 0 });434 }435 }436 // Straight self-assignment.437 Rvalue::BinaryOp(op, (Operand::Copy(lhs), _)) => {438 if lhs != first_place {439 continue;440 }441442 // We ignore indirect self-assignment, because both occurrences of `dest` are uses.443 let is_indirect = checked_places444 .get(first_place.as_ref())445 .map_or(false, |(_, projections)| is_indirect(projections));446 if is_indirect {447 continue;448 }449450 self_assign.insert(Location { block: bb, statement_index });451452 // Checked division verifies overflow before performing the division, so we453 // need to go and ignore this check in the predecessor block.454 if let BinOp::Div | BinOp::Rem = op455 && statement_index == 0456 && let &[pred] = body.basic_blocks.predecessors()[bb].as_slice()457 && let TerminatorKind::Assert { msg, .. } =458 &body.basic_blocks[pred].terminator().kind459 && let AssertKind::Overflow(..) = **msg460 && let len = body.basic_blocks[pred].statements.len()461 && len >= 2462 {463 // BitAnd of two checks.464 self_assign.insert(Location { block: pred, statement_index: len - 1 });465 // `lhs == MIN`.466 self_assign.insert(Location { block: pred, statement_index: len - 2 });467 }468 }469 _ => {}470 }471 }472 }473474 self_assign475}476477#[derive(Default, Debug)]478struct PlaceSet<'tcx> {479 places: IndexVec<PlaceIndex, PlaceRef<'tcx>>,480 names: IndexVec<PlaceIndex, Option<(Symbol, Span)>>,481482 /// Places corresponding to locals, common case.483 locals: IndexVec<Local, Option<PlaceIndex>>,484485 // Handling of captures.486 /// If `_1` is a reference, we need to add a `Deref` to the matched place.487 capture_field_pos: usize,488 /// Captured fields.489 captures: IndexVec<FieldIdx, (PlaceIndex, bool)>,490}491492impl<'tcx> PlaceSet<'tcx> {493 fn insert_locals(&mut self, decls: &IndexVec<Local, LocalDecl<'tcx>>) {494 self.locals = IndexVec::from_elem(None, &decls);495 for (local, decl) in decls.iter_enumerated() {496 // Record all user-written locals for the analysis.497 // We also keep the `RefForGuard` locals (more on that below).498 if let LocalInfo::User(BindingForm::Var(_) | BindingForm::RefForGuard(_)) =499 decl.local_info()500 {501 let index = self.places.push(local.into());502 self.locals[local] = Some(index);503 let _index = self.names.push(None);504 debug_assert_eq!(index, _index);505 }506 }507 }508509 fn insert_captures(510 &mut self,511 tcx: TyCtxt<'tcx>,512 self_is_ref: bool,513 captures: &[&'tcx ty::CapturedPlace<'tcx>],514 upvars: &ty::List<Ty<'tcx>>,515 ) {516 // We should not track the environment local separately.517 debug_assert_eq!(self.locals[ty::CAPTURE_STRUCT_LOCAL], None);518519 let self_place = Place {520 local: ty::CAPTURE_STRUCT_LOCAL,521 projection: tcx.mk_place_elems(if self_is_ref { &[PlaceElem::Deref] } else { &[] }),522 };523 if self_is_ref {524 self.capture_field_pos = 1;525 }526527 for (f, (capture, ty)) in std::iter::zip(captures, upvars).enumerate() {528 let f = FieldIdx::from_usize(f);529 let elem = PlaceElem::Field(f, ty);530 let by_ref = matches!(capture.info.capture_kind, ty::UpvarCapture::ByRef(..));531 let place = if by_ref {532 self_place.project_deeper(&[elem, PlaceElem::Deref], tcx)533 } else {534 self_place.project_deeper(&[elem], tcx)535 };536 let index = self.places.push(place.as_ref());537 let _f = self.captures.push((index, by_ref));538 debug_assert_eq!(_f, f);539540 // Record a variable name from the capture, because it is much friendlier than the541 // debuginfo name.542 self.names.insert(543 index,544 (Symbol::intern(&capture.to_string(tcx)), capture.get_path_span(tcx)),545 );546 }547 }548549 fn record_debuginfo(&mut self, var_debug_info: &Vec<VarDebugInfo<'tcx>>) {550 let ignore_name = |name: Symbol| {551 name == sym::empty || name == kw::SelfLower || name.as_str().starts_with('_')552 };553 for var_debug_info in var_debug_info {554 if let VarDebugInfoContents::Place(place) = var_debug_info.value555 && let Some(index) = self.locals[place.local]556 && !ignore_name(var_debug_info.name)557 {558 self.names.get_or_insert_with(index, || {559 (var_debug_info.name, var_debug_info.source_info.span)560 });561 }562 }563564 // Discard places that will not result in a diagnostic.565 for index_opt in self.locals.iter_mut() {566 if let Some(index) = *index_opt {567 let remove = match self.names[index] {568 None => true,569 Some((name, _)) => ignore_name(name),570 };571 if remove {572 *index_opt = None;573 }574 }575 }576 }577578 #[inline]579 fn get(&self, place: PlaceRef<'tcx>) -> Option<(PlaceIndex, &'tcx [PlaceElem<'tcx>])> {580 if let Some(index) = self.locals[place.local] {581 return Some((index, place.projection));582 }583 if place.local == ty::CAPTURE_STRUCT_LOCAL584 && !self.captures.is_empty()585 && self.capture_field_pos < place.projection.len()586 && let PlaceElem::Field(f, _) = place.projection[self.capture_field_pos]587 && let Some((index, by_ref)) = self.captures.get(f)588 {589 let mut start = self.capture_field_pos + 1;590 if *by_ref {591 // Account for an extra Deref.592 start += 1;593 }594 // We may have an attempt to access `_1.f` as a shallow reborrow. Just ignore it.595 if start <= place.projection.len() {596 let projection = &place.projection[start..];597 return Some((*index, projection));598 }599 }600 None601 }602603 fn iter(&self) -> impl Iterator<Item = (PlaceIndex, &PlaceRef<'tcx>)> {604 self.places.iter_enumerated()605 }606607 fn len(&self) -> usize {608 self.places.len()609 }610}611612struct AssignmentResult<'a, 'tcx> {613 tcx: TyCtxt<'tcx>,614 typing_env: ty::TypingEnv<'tcx>,615 checked_places: &'a PlaceSet<'tcx>,616 body: &'a Body<'tcx>,617 /// Set of locals that are live at least once. This is used to report fully unused locals.618 ever_live: DenseBitSet<PlaceIndex>,619 /// Set of locals that have a non-trivial drop. This is used to skip reporting unused620 /// assignment if it would be used by the `Drop` impl.621 ever_dropped: DenseBitSet<PlaceIndex>,622 /// Set of assignments for each local. Here, assignment is understood in the AST sense. Any623 /// MIR that may look like an assignment (Assign, DropAndReplace, Yield, Call) are considered.624 ///625 /// For each local, we return a map: for each source position, whether the statement is live626 /// and which kind of access it performs. When we encounter multiple statements at the same627 /// location, we only increase the liveness, in order to avoid false positives.628 assignments: IndexVec<PlaceIndex, FxIndexMap<SourceInfo, Access>>,629}630631impl<'a, 'tcx> AssignmentResult<'a, 'tcx> {632 /// Collect all assignments to checked locals.633 ///634 /// Assignments are collected, even if they are live. Dead assignments are reported, and live635 /// assignments are used to make diagnostics correct for match guards.636 fn find_dead_assignments(637 tcx: TyCtxt<'tcx>,638 typing_env: ty::TypingEnv<'tcx>,639 checked_places: &'a PlaceSet<'tcx>,640 cursor: &mut ResultsCursor<'_, 'tcx, MaybeLivePlaces<'_, 'tcx>>,641 body: &'a Body<'tcx>,642 ) -> AssignmentResult<'a, 'tcx> {643 let mut ever_live = DenseBitSet::new_empty(checked_places.len());644 let mut ever_dropped = DenseBitSet::new_empty(checked_places.len());645 let mut assignments = IndexVec::<PlaceIndex, FxIndexMap<_, _>>::from_elem(646 Default::default(),647 &checked_places.places,648 );649650 let mut check_place = |place: Place<'tcx>,651 kind,652 source_info: SourceInfo,653 location: Location,654 live: &DenseBitSet<PlaceIndex>| {655 if let Some((index, extra_projections)) = checked_places.get(place.as_ref()) {656 if !is_indirect(extra_projections) {657 let is_direct = extra_projections.is_empty();658 match assignments[index].entry(source_info) {659 IndexEntry::Vacant(v) => {660 let access =661 Access { kind, location, live: live.contains(index), is_direct };662 v.insert(access);663 }664 IndexEntry::Occupied(mut o) => {665 // There were already a sighting. Mark this statement as live if it666 // was, to avoid false positives.667 o.get_mut().live |= live.contains(index);668 o.get_mut().is_direct &= is_direct;669 }670 }671 }672 }673 };674675 let mut record_drop = |place: Place<'tcx>| {676 if let Some((index, &[])) = checked_places.get(place.as_ref()) {677 ever_dropped.insert(index);678 }679 };680681 for (bb, bb_data) in traversal::postorder(body) {682 cursor.seek_to_block_end(bb);683 let live = cursor.get();684 ever_live.union(live);685686 let terminator = bb_data.terminator();687 match &terminator.kind {688 TerminatorKind::Call { destination: place, .. }689 | TerminatorKind::Yield { resume_arg: place, .. } => {690 check_place(691 *place,692 AccessKind::Assign,693 terminator.source_info,694 body.terminator_loc(bb),695 live,696 );697 record_drop(*place)698 }699 TerminatorKind::Drop { place, .. } => record_drop(*place),700 TerminatorKind::InlineAsm { operands, .. } => {701 for operand in operands {702 if let InlineAsmOperand::Out { place: Some(place), .. }703 | InlineAsmOperand::InOut { out_place: Some(place), .. } = operand704 {705 check_place(706 *place,707 AccessKind::Assign,708 terminator.source_info,709 body.terminator_loc(bb),710 live,711 );712 }713 }714 }715 _ => {}716 }717718 for (statement_index, statement) in bb_data.statements.iter().enumerate().rev() {719 let location = Location { block: bb, statement_index };720 cursor.seek_before_primary_effect(location);721 let live = cursor.get();722 ever_live.union(live);723 match &statement.kind {724 StatementKind::Assign((place, _)) => {725 check_place(726 *place,727 AccessKind::Assign,728 statement.source_info,729 location,730 live,731 );732 }733 StatementKind::SetDiscriminant { place, .. } => {734 check_place(735 **place,736 AccessKind::Assign,737 statement.source_info,738 location,739 live,740 );741 }742 StatementKind::StorageLive(_)743 | StatementKind::StorageDead(_)744 | StatementKind::Coverage(_)745 | StatementKind::Intrinsic(_)746 | StatementKind::Nop747 | StatementKind::FakeRead(_)748 | StatementKind::PlaceMention(_)749 | StatementKind::ConstEvalCounter750 | StatementKind::BackwardIncompatibleDropHint { .. }751 | StatementKind::AscribeUserType(_, _) => (),752 }753 }754 }755756 // Check liveness of function arguments on entry.757 {758 cursor.seek_to_block_start(START_BLOCK);759 let live = cursor.get();760 ever_live.union(live);761762 // Verify that arguments and captured values are useful.763 for (index, place) in checked_places.iter() {764 let kind = if is_capture(*place) {765 // This is a by-ref capture, an assignment to it will modify surrounding766 // environment, so we do not report it.767 if place.projection.last() == Some(&PlaceElem::Deref) {768 continue;769 }770771 AccessKind::Capture772 } else if body.local_kind(place.local) == LocalKind::Arg {773 AccessKind::Param774 } else {775 continue;776 };777 let source_info = body.local_decls[place.local].source_info;778 let access = Access {779 kind,780 location: Location::START,781 live: live.contains(index),782 is_direct: true,783 };784 assignments[index].insert(source_info, access);785 }786 }787788 AssignmentResult {789 tcx,790 typing_env,791 checked_places,792 ever_live,793 ever_dropped,794 assignments,795 body,796 }797 }798799 /// Match guards introduce a different local to freeze the guarded value as immutable.800 /// Having two locals, we need to make sure that we do not report an unused_variable801 /// when the guard local is used but not the arm local, or vice versa, like in this example.802 ///803 /// match 5 {804 /// x if x > 2 => {}805 /// ^ ^- This is `local`806 /// +------ This is `arm_local`807 /// _ => {}808 /// }809 ///810 fn merge_guards(&mut self) {811 for (index, place) in self.checked_places.iter() {812 let local = place.local;813 if let &LocalInfo::User(BindingForm::RefForGuard(arm_local)) =814 self.body.local_decls[local].local_info()815 {816 debug_assert!(place.projection.is_empty());817818 // Local to use in the arm.819 let Some((arm_index, _proj)) = self.checked_places.get(arm_local.into()) else {820 continue;821 };822 debug_assert_ne!(index, arm_index);823 debug_assert_eq!(_proj, &[]);824825 // Mark the arm local as used if the guard local is used.826 if self.ever_live.contains(index) {827 self.ever_live.insert(arm_index);828 }829830 // Some assignments are common to both locals in the source code.831 // Sadly, we can only detect this using the `source_info`.832 // Therefore, we loop over all the assignments we have for the guard local:833 // - if they already appeared for the arm local, the assignment is live if one of the834 // two versions is live;835 // - if it does not appear for the arm local, it happened inside the guard, so we add836 // it as-is.837 let guard_assignments = std::mem::take(&mut self.assignments[index]);838 let arm_assignments = &mut self.assignments[arm_index];839 for (source_info, access) in guard_assignments {840 match arm_assignments.entry(source_info) {841 IndexEntry::Vacant(v) => {842 v.insert(access);843 }844 IndexEntry::Occupied(mut o) => {845 o.get_mut().live |= access.live;846 }847 }848 }849 }850 }851 }852853 /// Compute captures that are fully dead.854 fn compute_dead_captures(&self, num_captures: usize) -> DenseBitSet<FieldIdx> {855 // Report to caller the set of dead captures.856 let mut dead_captures = DenseBitSet::new_empty(num_captures);857 for (index, place) in self.checked_places.iter() {858 if self.ever_live.contains(index) {859 continue;860 }861862 // This is a capture: pass information to the enclosing function.863 if is_capture(*place) {864 for p in place.projection {865 if let PlaceElem::Field(f, _) = p {866 dead_captures.insert(*f);867 break;868 }869 }870 continue;871 }872 }873874 dead_captures875 }876877 /// Check if a local is referenced in any reachable basic block.878 /// Variables in unreachable code (e.g., after `todo!()`) should not trigger unused warnings.879 fn is_local_in_reachable_code(&self, local: Local) -> bool {880 struct LocalVisitor {881 target_local: Local,882 found: bool,883 }884885 impl<'tcx> Visitor<'tcx> for LocalVisitor {886 fn visit_local(&mut self, local: Local, _context: PlaceContext, _location: Location) {887 if local == self.target_local {888 self.found = true;889 }890 }891 }892893 let mut visitor = LocalVisitor { target_local: local, found: false };894 for (bb, bb_data) in traversal::postorder(self.body) {895 visitor.visit_basic_block_data(bb, bb_data);896 if visitor.found {897 return true;898 }899 }900901 false902 }903904 /// Report fully unused locals, and forget the corresponding assignments.905 fn report_fully_unused(&mut self) {906 let tcx = self.tcx;907908 // Give a diagnostic when any of the string constants look like a naked format string that909 // would interpolate our dead local.910 let mut string_constants_in_body = None;911 let mut maybe_suggest_literal_matching_name = |name: Symbol| {912 // Visiting MIR to enumerate string constants can be expensive, so cache the result.913 let string_constants_in_body = string_constants_in_body.get_or_insert_with(|| {914 struct LiteralFinder {915 found: Vec<(Span, String)>,916 }917918 impl<'tcx> Visitor<'tcx> for LiteralFinder {919 fn visit_const_operand(&mut self, constant: &ConstOperand<'tcx>, _: Location) {920 if let ty::Ref(_, ref_ty, _) = constant.ty().kind()921 && ref_ty.kind() == &ty::Str922 {923 let rendered_constant = constant.const_.to_string();924 self.found.push((constant.span, rendered_constant));925 }926 }927 }928929 let mut finder = LiteralFinder { found: vec![] };930 finder.visit_body(self.body);931 finder.found932 });933934 let brace_name = format!("{{{name}");935 string_constants_in_body936 .iter()937 .filter(|(_, rendered_constant)| {938 rendered_constant939 .split(&brace_name)940 .any(|c| matches!(c.chars().next(), Some('}' | ':')))941 })942 .map(|&(lit, _)| diagnostics::UnusedVariableStringInterp { lit })943 .collect::<Vec<_>>()944 };945946 // First, report fully unused locals.947 for (index, place) in self.checked_places.iter() {948 if self.ever_live.contains(index) {949 continue;950 }951952 // this is a capture: let the enclosing function report the unused variable.953 if is_capture(*place) {954 continue;955 }956957 let local = place.local;958 let decl = &self.body.local_decls[local];959960 if decl.from_compiler_desugaring() {961 continue;962 }963964 // Only report actual user-defined binding from now on.965 let LocalInfo::User(BindingForm::Var(binding)) = decl.local_info() else { continue };966 let Some(hir_id) = decl.source_info.scope.lint_root(&self.body.source_scopes) else {967 continue;968 };969970 let introductions = &binding.introductions;971972 let Some((name, def_span)) = self.checked_places.names[index] else { continue };973974 // #117284, when `ident_span` and `def_span` have different contexts975 // we can't provide a good suggestion, instead we pointed out the spans from macro976 let from_macro = def_span.from_expansion()977 && introductions.iter().any(|intro| intro.span.eq_ctxt(def_span));978979 let maybe_suggest_typo = || {980 if let LocalKind::Arg = self.body.local_kind(local) {981 None982 } else {983 maybe_suggest_unit_pattern_typo(984 tcx,985 self.body.source.def_id(),986 name,987 def_span,988 decl.ty,989 )990 }991 };992993 let statements = &mut self.assignments[index];994 if statements.is_empty() {995 if !self.is_local_in_reachable_code(local) {996 continue;997 }998999 let sugg = if from_macro {1000 diagnostics::UnusedVariableSugg::NoSugg { span: def_span, name }1001 } else {1002 let typo = maybe_suggest_typo();1003 diagnostics::UnusedVariableSugg::TryPrefix { spans: vec![def_span], name, typo }1004 };1005 tcx.emit_node_span_lint(1006 lint::builtin::UNUSED_VARIABLES,1007 hir_id,1008 def_span,1009 diagnostics::UnusedVariable {1010 name,1011 string_interp: maybe_suggest_literal_matching_name(name),1012 sugg,1013 },1014 );1015 continue;1016 }10171018 // Idiomatic rust assigns a value to a local upon definition. However, we do not want to1019 // warn twice, for the unused local and for the unused assignment. Therefore, we remove1020 // from the list of assignments the ones that happen at the definition site.1021 statements.retain(|source_info, _| {1022 !binding.introductions.iter().any(|intro| intro.span == source_info.span)1023 });10241025 // Extra assignments that we recognize thanks to the initialization span. We need to1026 // take care of macro contexts here to be accurate.1027 if let Some((_, initializer_span)) = binding.opt_match_place {1028 statements.retain(|source_info, _| {1029 let within = source_info.span.find_ancestor_inside(initializer_span);1030 let outer_initializer_span =1031 initializer_span.find_ancestor_in_same_ctxt(source_info.span);1032 within.is_none()1033 && outer_initializer_span.map_or(true, |s| !s.contains(source_info.span))1034 });1035 }10361037 if !statements.is_empty() {1038 // We have a dead local with outstanding assignments and with non-trivial drop.1039 // This is probably a drop-guard, so we do not issue a warning there.1040 if maybe_drop_guard(1041 tcx,1042 self.typing_env,1043 index,1044 &self.ever_dropped,1045 self.checked_places,1046 self.body,1047 ) {1048 statements.retain(|_, access| access.is_direct);1049 if statements.is_empty() {1050 continue;1051 }1052 }10531054 let typo = maybe_suggest_typo();1055 tcx.emit_node_span_lint(1056 lint::builtin::UNUSED_VARIABLES,1057 hir_id,1058 def_span,1059 diagnostics::UnusedVarAssignedOnly { name, typo },1060 );1061 continue;1062 }10631064 // We do not have outstanding assignments, suggest renaming the binding.1065 let spans = introductions.iter().map(|intro| intro.span).collect::<Vec<_>>();10661067 let any_shorthand = introductions.iter().any(|intro| intro.is_shorthand);10681069 let sugg = if any_shorthand {1070 diagnostics::UnusedVariableSugg::TryIgnore {1071 name: name.to_ident_string(),1072 shorthands: introductions1073 .iter()1074 .filter_map(1075 |intro| if intro.is_shorthand { Some(intro.span) } else { None },1076 )1077 .collect(),1078 non_shorthands: introductions1079 .iter()1080 .filter_map(1081 |intro| {1082 if !intro.is_shorthand { Some(intro.span) } else { None }1083 },1084 )1085 .collect(),1086 }1087 } else if from_macro {1088 diagnostics::UnusedVariableSugg::NoSugg { span: def_span, name }1089 } else if !introductions.is_empty() {1090 let typo = maybe_suggest_typo();1091 diagnostics::UnusedVariableSugg::TryPrefix { name, typo, spans: spans.clone() }1092 } else {1093 let typo = maybe_suggest_typo();1094 diagnostics::UnusedVariableSugg::TryPrefix { name, typo, spans: vec![def_span] }1095 };10961097 tcx.emit_node_span_lint(1098 lint::builtin::UNUSED_VARIABLES,1099 hir_id,1100 spans,1101 diagnostics::UnusedVariable {1102 name,1103 string_interp: maybe_suggest_literal_matching_name(name),1104 sugg,1105 },1106 );1107 }1108 }11091110 /// Second, report unused assignments that do not correspond to initialization.1111 /// Initializations have been removed in the previous loop reporting unused variables.1112 fn report_unused_assignments(self) {1113 let tcx = self.tcx;11141115 for (index, statements) in self.assignments.into_iter_enumerated() {1116 if statements.is_empty() {1117 continue;1118 }11191120 let Some((name, decl_span)) = self.checked_places.names[index] else { continue };11211122 let is_maybe_drop_guard = maybe_drop_guard(1123 tcx,1124 self.typing_env,1125 index,1126 &self.ever_dropped,1127 self.checked_places,1128 self.body,1129 );11301131 // By convention, underscore-prefixed bindings are allowed to be unused explicitly.1132 if name.as_str().starts_with('_') {1133 continue;1134 }11351136 let mut next_direct_assignments: Vec<(Span, Location)> = Vec::new();1137 let mut dead_statements = Vec::with_capacity(statements.len());11381139 for (source_info, Access { live, kind, is_direct, location }) in statements.into_iter()1140 {1141 let direct_assignment = kind == AccessKind::Assign && is_direct;1142 let should_report = !live && (is_direct || !is_maybe_drop_guard);11431144 let overwrite = if should_report && direct_assignment {1145 next_direct_assignments1146 .iter()1147 .rfind(|(_, overwrite_location)| {1148 location.is_predecessor_of(*overwrite_location, self.body)1149 })1150 .map(|&(overwrite_span, _)| diagnostics::UnusedAssignOverwrite {1151 assigned_span: source_info.span,1152 overwrite_span,1153 name,1154 })1155 } else {1156 None1157 };11581159 if direct_assignment {1160 next_direct_assignments.push((source_info.span, location));1161 }11621163 if !should_report {1164 continue;1165 }1166 dead_statements.push((source_info, kind, is_direct, overwrite));1167 }11681169 // We probed MIR in reverse order for dataflow.1170 // Emit diagnostics in source order instead.1171 for (source_info, kind, is_direct, overwrite) in dead_statements.into_iter().rev() {1172 // Report the dead assignment.1173 let Some(hir_id) = source_info.scope.lint_root(&self.body.source_scopes) else {1174 continue;1175 };11761177 match kind {1178 AccessKind::Assign => {1179 let suggestion = annotate_mut_binding_to_immutable_binding(1180 tcx,1181 self.checked_places.places[index],1182 self.body.source.def_id().expect_local(),1183 source_info.span,1184 self.body,1185 );1186 let overwrite =1187 if suggestion.is_none() && is_direct { overwrite } else { None };1188 let help = suggestion.is_none() && overwrite.is_none();1189 tcx.emit_node_span_lint(1190 lint::builtin::UNUSED_ASSIGNMENTS,1191 hir_id,1192 source_info.span,1193 diagnostics::UnusedAssign { name, overwrite, help, suggestion },1194 )1195 }1196 AccessKind::Param => tcx.emit_node_span_lint(1197 lint::builtin::UNUSED_ASSIGNMENTS,1198 hir_id,1199 source_info.span,1200 diagnostics::UnusedAssignPassed { name },1201 ),1202 AccessKind::Capture => tcx.emit_node_span_lint(1203 lint::builtin::UNUSED_ASSIGNMENTS,1204 hir_id,1205 decl_span,1206 diagnostics::UnusedCaptureMaybeCaptureRef { name },1207 ),1208 }1209 }1210 }1211 }1212}12131214rustc_index::newtype_index! {1215 pub struct PlaceIndex {}1216}12171218impl DebugWithContext<MaybeLivePlaces<'_, '_>> for PlaceIndex {1219 fn fmt_with(1220 &self,1221 ctxt: &MaybeLivePlaces<'_, '_>,1222 f: &mut std::fmt::Formatter<'_>,1223 ) -> std::fmt::Result {1224 std::fmt::Debug::fmt(&ctxt.checked_places.places[*self], f)1225 }1226}12271228pub struct MaybeLivePlaces<'a, 'tcx> {1229 tcx: TyCtxt<'tcx>,1230 checked_places: &'a PlaceSet<'tcx>,1231 capture_kind: CaptureKind,1232 self_assignment: FxHashSet<Location>,1233}12341235impl<'tcx> MaybeLivePlaces<'_, 'tcx> {1236 fn transfer_function<'a>(1237 &'a self,1238 trans: &'a mut DenseBitSet<PlaceIndex>,1239 ) -> TransferFunction<'a, 'tcx> {1240 TransferFunction {1241 tcx: self.tcx,1242 checked_places: &self.checked_places,1243 capture_kind: self.capture_kind,1244 trans,1245 self_assignment: &self.self_assignment,1246 }1247 }1248}12491250impl<'tcx> Analysis<'tcx> for MaybeLivePlaces<'_, 'tcx> {1251 type Domain = DenseBitSet<PlaceIndex>;1252 type Direction = Backward;12531254 const NAME: &'static str = "liveness-lint";12551256 fn bottom_value(&self, _: &Body<'tcx>) -> Self::Domain {1257 // bottom = not live1258 DenseBitSet::new_empty(self.checked_places.len())1259 }12601261 fn initialize_start_block(&self, _: &Body<'tcx>, _: &mut Self::Domain) {1262 // No variables are live until we observe a use1263 }12641265 fn apply_primary_statement_effect(1266 &self,1267 trans: &mut Self::Domain,1268 statement: &Statement<'tcx>,1269 location: Location,1270 ) {1271 self.transfer_function(trans).visit_statement(statement, location);1272 }12731274 fn apply_primary_terminator_effect<'mir>(1275 &self,1276 trans: &mut Self::Domain,1277 terminator: &'mir Terminator<'tcx>,1278 location: Location,1279 ) -> TerminatorEdges<'mir, 'tcx> {1280 self.transfer_function(trans).visit_terminator(terminator, location);1281 terminator.edges()1282 }12831284 fn apply_call_return_effect(1285 &self,1286 _trans: &mut Self::Domain,1287 _block: BasicBlock,1288 _return_places: CallReturnPlaces<'_, 'tcx>,1289 ) {1290 // FIXME: what should happen here?1291 }1292}12931294struct TransferFunction<'a, 'tcx> {1295 tcx: TyCtxt<'tcx>,1296 checked_places: &'a PlaceSet<'tcx>,1297 trans: &'a mut DenseBitSet<PlaceIndex>,1298 capture_kind: CaptureKind,1299 self_assignment: &'a FxHashSet<Location>,1300}13011302impl<'tcx> Visitor<'tcx> for TransferFunction<'_, 'tcx> {1303 fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {1304 match statement.kind {1305 // `ForLet(None)` and `ForGuardBinding` fake reads erroneously mark the just-assigned1306 // locals as live. This defeats the purpose of the analysis for such bindings.1307 StatementKind::FakeRead((1308 FakeReadCause::ForLet(None) | FakeReadCause::ForGuardBinding,1309 _,1310 )) => return,1311 // Handle self-assignment by restricting the read/write they do.1312 StatementKind::Assign((ref dest, ref rvalue))1313 if self.self_assignment.contains(&location) =>1314 {1315 if let Rvalue::BinaryOp(1316 BinOp::AddWithOverflow | BinOp::SubWithOverflow | BinOp::MulWithOverflow,1317 (_, rhs),1318 ) = rvalue1319 {1320 // We are computing the binary operation:1321 // - the LHS will be assigned, so we don't read it;1322 // - the RHS still needs to be read.1323 self.visit_operand(rhs, location);1324 self.visit_place(1325 dest,1326 PlaceContext::MutatingUse(MutatingUseContext::Store),1327 location,1328 );1329 } else if let Rvalue::BinaryOp(_, (_, rhs)) = rvalue {1330 // We are computing the binary operation:1331 // - the LHS is being updated, so we don't read it;1332 // - the RHS still needs to be read.1333 self.visit_operand(rhs, location);1334 } else {1335 // This is the second part of a checked self-assignment,1336 // we are assigning the result.1337 // We do not consider the write to the destination as a `def`.1338 // `self_assignment` must be false if the assignment is indirect.1339 self.visit_rvalue(rvalue, location);1340 }1341 }1342 _ => self.super_statement(statement, location),1343 }1344 }13451346 fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {1347 // By-ref captures could be read by the surrounding environment, so we mark1348 // them as live upon yield and return.1349 match terminator.kind {1350 TerminatorKind::Return1351 | TerminatorKind::Yield { .. }1352 | TerminatorKind::Goto { target: START_BLOCK } // Inserted for the `FnMut` case.1353 | TerminatorKind::Call { target: None, .. } // unwinding could be caught1354 if self.capture_kind != CaptureKind::None =>1355 {1356 // All indirect captures have an effect on the environment, so we mark them as live.1357 for (index, place) in self.checked_places.iter() {1358 if place.local == ty::CAPTURE_STRUCT_LOCAL1359 && place.projection.last() == Some(&PlaceElem::Deref)1360 {1361 self.trans.insert(index);1362 }1363 }1364 }1365 // Do not consider a drop to be a use. We whitelist interesting drops elsewhere.1366 TerminatorKind::Drop { .. } => {}1367 // Ignore assertions since they must be triggered by actual code.1368 TerminatorKind::Assert { .. } => {}1369 _ => self.super_terminator(terminator, location),1370 }1371 }13721373 fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {1374 match rvalue {1375 // When a closure/generator does not use some of its captures, do not consider these1376 // captures as live in the surrounding function. This allows to report unused variables,1377 // even if they have been (uselessly) captured.1378 Rvalue::Aggregate(1379 AggregateKind::Closure(def_id, _) | AggregateKind::Coroutine(def_id, _),1380 operands,1381 ) => {1382 if let Some(def_id) = def_id.as_local() {1383 let dead_captures = self.tcx.check_liveness(def_id);1384 for (field, operand) in1385 operands.iter_enumerated().take(dead_captures.domain_size())1386 {1387 if !dead_captures.contains(field) {1388 self.visit_operand(operand, location);1389 }1390 }1391 }1392 }1393 _ => self.super_rvalue(rvalue, location),1394 }1395 }13961397 fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, location: Location) {1398 if let Some((index, extra_projections)) = self.checked_places.get(place.as_ref()) {1399 for i in (extra_projections.len()..=place.projection.len()).rev() {1400 let place_part =1401 PlaceRef { local: place.local, projection: &place.projection[..i] };1402 let extra_projections = &place.projection[i..];14031404 if let Some(&elem) = extra_projections.get(0) {1405 self.visit_projection_elem(place_part, elem, context, location);1406 }1407 }14081409 match DefUse::for_place(extra_projections, context) {1410 Some(DefUse::Def) => {1411 self.trans.remove(index);1412 }1413 Some(DefUse::Use) => {1414 self.trans.insert(index);1415 }1416 None => {}1417 }1418 } else {1419 self.super_place(place, context, location)1420 }1421 }14221423 fn visit_local(&mut self, local: Local, context: PlaceContext, _: Location) {1424 if let Some((index, _proj)) = self.checked_places.get(local.into()) {1425 debug_assert_eq!(_proj, &[]);1426 match DefUse::for_place(&[], context) {1427 Some(DefUse::Def) => {1428 self.trans.remove(index);1429 }1430 Some(DefUse::Use) => {1431 self.trans.insert(index);1432 }1433 _ => {}1434 }1435 }1436 }1437}14381439#[derive(Eq, PartialEq, Debug, Clone)]1440enum DefUse {1441 Def,1442 Use,1443}14441445fn is_indirect(proj: &[PlaceElem<'_>]) -> bool {1446 proj.iter().any(|p| p.is_indirect())1447}14481449impl DefUse {1450 fn for_place<'tcx>(projection: &[PlaceElem<'tcx>], context: PlaceContext) -> Option<DefUse> {1451 let is_indirect = is_indirect(projection);1452 match context {1453 PlaceContext::MutatingUse(1454 MutatingUseContext::Store | MutatingUseContext::SetDiscriminant,1455 ) => {1456 if is_indirect {1457 // Treat derefs as a use of the base local. `*p = 4` is not a def of `p` but a1458 // use.1459 Some(DefUse::Use)1460 } else if projection.is_empty() {1461 Some(DefUse::Def)1462 } else {1463 None1464 }1465 }14661467 // For the associated terminators, this is only a `Def` when the terminator returns1468 // "successfully." As such, we handle this case separately in `call_return_effect`1469 // above. However, if the place looks like `*_5`, this is still unconditionally a use of1470 // `_5`.1471 PlaceContext::MutatingUse(1472 MutatingUseContext::Call1473 | MutatingUseContext::Yield1474 | MutatingUseContext::AsmOutput,1475 ) => is_indirect.then_some(DefUse::Use),14761477 // All other contexts are uses...1478 PlaceContext::MutatingUse(1479 MutatingUseContext::RawBorrow1480 | MutatingUseContext::Borrow1481 | MutatingUseContext::Drop1482 | MutatingUseContext::Retag,1483 )1484 | PlaceContext::NonMutatingUse(1485 NonMutatingUseContext::RawBorrow1486 | NonMutatingUseContext::Copy1487 | NonMutatingUseContext::Inspect1488 | NonMutatingUseContext::Move1489 | NonMutatingUseContext::FakeBorrow1490 | NonMutatingUseContext::SharedBorrow1491 | NonMutatingUseContext::PlaceMention,1492 ) => Some(DefUse::Use),14931494 PlaceContext::NonUse(1495 NonUseContext::StorageLive1496 | NonUseContext::StorageDead1497 | NonUseContext::AscribeUserTy(_)1498 | NonUseContext::BackwardIncompatibleDropHint1499 | NonUseContext::VarDebugInfo,1500 ) => None,15011502 PlaceContext::MutatingUse(MutatingUseContext::Projection)1503 | PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection) => {1504 unreachable!("A projection could be a def or a use and must be handled separately")1505 }1506 }1507 }1508}