1use rustc_abi::FieldIdx;
2use rustc_data_structures::fx::{FxHashSet, FxIndexMap, IndexEntry};
3use rustc_hir::attrs::AttributeKind;
4use rustc_hir::def::{CtorKind, DefKind};
5use rustc_hir::def_id::{DefId, LocalDefId};
6use rustc_hir::find_attr;
7use rustc_index::IndexVec;
8use rustc_index::bit_set::DenseBitSet;
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_session::lint;
19use rustc_span::Span;
20use rustc_span::edit_distance::find_best_match_for_name;
21use rustc_span::symbol::{Symbol, kw, sym};
22
23use crate::errors;
24
25#[derive(Copy, Clone, Debug, PartialEq, Eq)]
26enum AccessKind {
27 Param,
28 Assign,
29 Capture,
30}
31
32#[derive(Copy, Clone, Debug, PartialEq, Eq)]
33enum CaptureKind {
34 Closure(ty::ClosureKind),
35 Coroutine,
36 CoroutineClosure,
37 None,
38}
39
40#[derive(Copy, Clone, Debug)]
41struct Access {
42 kind: AccessKind,
44 live: bool,
48 is_direct: bool,
51}
52
53#[tracing::instrument(level = "debug", skip(tcx), ret)]
54pub(crate) fn check_liveness<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> DenseBitSet<FieldIdx> {
55 if tcx.is_synthetic_mir(def_id) {
57 return DenseBitSet::new_empty(0);
58 }
59
60 if tcx.intrinsic(def_id.to_def_id()).is_some() {
62 return DenseBitSet::new_empty(0);
63 }
64
65 if find_attr!(tcx.get_all_attrs(def_id.to_def_id()), AttributeKind::Naked(..)) {
67 return DenseBitSet::new_empty(0);
68 }
69
70 let parent = tcx.parent(tcx.typeck_root_def_id(def_id.to_def_id()));
72 if let DefKind::Impl { of_trait: true } = tcx.def_kind(parent)
73 && find_attr!(tcx.get_all_attrs(parent), AttributeKind::AutomaticallyDerived(..))
74 {
75 return DenseBitSet::new_empty(0);
76 }
77
78 let mut body = &*tcx.mir_promoted(def_id).0.borrow();
79 let mut body_mem;
80
81 if body.tainted_by_errors.is_some() {
83 return DenseBitSet::new_empty(0);
84 }
85
86 let mut checked_places = PlaceSet::default();
87 checked_places.insert_locals(&body.local_decls);
88
89 let (capture_kind, num_captures) = if tcx.is_closure_like(def_id.to_def_id()) {
91 let mut self_ty = body.local_decls[ty::CAPTURE_STRUCT_LOCAL].ty;
92 let mut self_is_ref = false;
93 if let ty::Ref(_, ty, _) = self_ty.kind() {
94 self_ty = *ty;
95 self_is_ref = true;
96 }
97
98 let (capture_kind, args) = match self_ty.kind() {
99 ty::Closure(_, args) => {
100 (CaptureKind::Closure(args.as_closure().kind()), ty::UpvarArgs::Closure(args))
101 }
102 &ty::Coroutine(_, args) => (CaptureKind::Coroutine, ty::UpvarArgs::Coroutine(args)),
103 &ty::CoroutineClosure(_, args) => {
104 (CaptureKind::CoroutineClosure, ty::UpvarArgs::CoroutineClosure(args))
105 }
106 _ => bug!("expected closure or generator, found {:?}", self_ty),
107 };
108
109 let captures = tcx.closure_captures(def_id);
110 checked_places.insert_captures(tcx, self_is_ref, captures, args.upvar_tys());
111
112 if let CaptureKind::Closure(ty::ClosureKind::FnMut) = capture_kind {
116 body_mem = body.clone();
118 for bbdata in body_mem.basic_blocks_mut() {
119 if let TerminatorKind::Return | TerminatorKind::UnwindResume =
121 bbdata.terminator().kind
122 {
123 bbdata.terminator_mut().kind = TerminatorKind::Goto { target: START_BLOCK };
124 }
125 }
126 body = &body_mem;
127 }
128
129 (capture_kind, args.upvar_tys().len())
130 } else {
131 (CaptureKind::None, 0)
132 };
133
134 checked_places.record_debuginfo(&body.var_debug_info);
136
137 let self_assignment = find_self_assignments(&checked_places, body);
138
139 let mut live =
140 MaybeLivePlaces { tcx, capture_kind, checked_places: &checked_places, self_assignment }
141 .iterate_to_fixpoint(tcx, body, None)
142 .into_results_cursor(body);
143
144 let typing_env = ty::TypingEnv::post_analysis(tcx, body.source.def_id());
145
146 let mut assignments =
147 AssignmentResult::find_dead_assignments(tcx, typing_env, &checked_places, &mut live, body);
148
149 assignments.merge_guards();
150
151 let dead_captures = assignments.compute_dead_captures(num_captures);
152
153 assignments.report_fully_unused();
154 assignments.report_unused_assignments();
155
156 dead_captures
157}
158
159#[inline]
161fn is_capture(place: PlaceRef<'_>) -> bool {
162 if !place.projection.is_empty() {
163 debug_assert_eq!(place.local, ty::CAPTURE_STRUCT_LOCAL);
164 true
165 } else {
166 false
167 }
168}
169
170fn maybe_suggest_unit_pattern_typo<'tcx>(
172 tcx: TyCtxt<'tcx>,
173 body_def_id: DefId,
174 name: Symbol,
175 span: Span,
176 ty: Ty<'tcx>,
177) -> Option<errors::PatternTypo> {
178 if let ty::Adt(adt_def, _) = ty.peel_refs().kind() {
179 let variant_names: Vec<_> = adt_def
180 .variants()
181 .iter()
182 .filter(|v| matches!(v.ctor, Some((CtorKind::Const, _))))
183 .map(|v| v.name)
184 .collect();
185 if let Some(name) = find_best_match_for_name(&variant_names, name, None)
186 && let Some(variant) = adt_def
187 .variants()
188 .iter()
189 .find(|v| v.name == name && matches!(v.ctor, Some((CtorKind::Const, _))))
190 {
191 return Some(errors::PatternTypo {
192 span,
193 code: with_no_trimmed_paths!(tcx.def_path_str(variant.def_id)),
194 kind: tcx.def_descr(variant.def_id),
195 item_name: variant.name,
196 });
197 }
198 }
199
200 let constants = tcx
203 .hir_body_owners()
204 .filter(|&def_id| {
205 matches!(tcx.def_kind(def_id), DefKind::Const)
206 && tcx.type_of(def_id).instantiate_identity() == ty
207 && tcx.visibility(def_id).is_accessible_from(body_def_id, tcx)
208 })
209 .collect::<Vec<_>>();
210 let names = constants.iter().map(|&def_id| tcx.item_name(def_id)).collect::<Vec<_>>();
211 if let Some(item_name) = find_best_match_for_name(&names, name, None)
212 && let Some(position) = names.iter().position(|&n| n == item_name)
213 && let Some(&def_id) = constants.get(position)
214 {
215 return Some(errors::PatternTypo {
216 span,
217 code: with_no_trimmed_paths!(tcx.def_path_str(def_id)),
218 kind: "constant",
219 item_name,
220 });
221 }
222
223 None
224}
225
226fn maybe_drop_guard<'tcx>(
228 tcx: TyCtxt<'tcx>,
229 typing_env: ty::TypingEnv<'tcx>,
230 index: PlaceIndex,
231 ever_dropped: &DenseBitSet<PlaceIndex>,
232 checked_places: &PlaceSet<'tcx>,
233 body: &Body<'tcx>,
234) -> bool {
235 if ever_dropped.contains(index) {
236 let ty = checked_places.places[index].ty(&body.local_decls, tcx).ty;
237 matches!(
238 ty.kind(),
239 ty::Closure(..)
240 | ty::Coroutine(..)
241 | ty::Tuple(..)
242 | ty::Adt(..)
243 | ty::Dynamic(..)
244 | ty::Array(..)
245 | ty::Slice(..)
246 | ty::Alias(ty::Opaque, ..)
247 ) && ty.needs_drop(tcx, typing_env)
248 } else {
249 false
250 }
251}
252
253fn annotate_mut_binding_to_immutable_binding<'tcx>(
272 tcx: TyCtxt<'tcx>,
273 place: PlaceRef<'tcx>,
274 body_def_id: LocalDefId,
275 assignment_span: Span,
276 body: &Body<'tcx>,
277) -> Option<errors::UnusedAssignSuggestion> {
278 use rustc_hir as hir;
279 use rustc_hir::intravisit::{self, Visitor};
280
281 let local = place.as_local()?;
283 let LocalKind::Arg = body.local_kind(local) else { return None };
284 let Mutability::Mut = body.local_decls[local].mutability else { return None };
285
286 let hir_param_index =
288 local.as_usize() - if tcx.is_closure_like(body_def_id.to_def_id()) { 2 } else { 1 };
289 let fn_decl = tcx.hir_node_by_def_id(body_def_id).fn_decl()?;
290 let ty = fn_decl.inputs[hir_param_index];
291 let hir::TyKind::Ref(lt, mut_ty) = ty.kind else { return None };
292
293 let hir_body = tcx.hir_maybe_body_owned_by(body_def_id)?;
295 let param = hir_body.params[hir_param_index];
296 let hir::PatKind::Binding(hir::BindingMode::MUT, _hir_id, ident, _) = param.pat.kind else {
297 return None;
298 };
299
300 let mut finder = ExprFinder { assignment_span, lhs: None, rhs: None };
302 finder.visit_body(hir_body);
303 let lhs = finder.lhs?;
304 let rhs = finder.rhs?;
305
306 let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _mut, inner) = rhs.kind else { return None };
307
308 let pre = if lt.ident.span.is_empty() { "" } else { " " };
310 let ty_span = if mut_ty.mutbl.is_mut() {
311 None
313 } else {
314 Some(mut_ty.ty.span.shrink_to_lo())
316 };
317
318 return Some(errors::UnusedAssignSuggestion {
319 ty_span,
320 pre,
321 ty_ref_span: param.pat.span.until(ident.span),
323 pre_lhs_span: lhs.span.shrink_to_lo(),
325 rhs_borrow_span: rhs.span.until(inner.span),
327 });
328
329 #[derive(Debug)]
330 struct ExprFinder<'hir> {
331 assignment_span: Span,
332 lhs: Option<&'hir hir::Expr<'hir>>,
333 rhs: Option<&'hir hir::Expr<'hir>>,
334 }
335 impl<'hir> Visitor<'hir> for ExprFinder<'hir> {
336 fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
337 if expr.span == self.assignment_span
338 && let hir::ExprKind::Assign(lhs, rhs, _) = expr.kind
339 {
340 self.lhs = Some(lhs);
341 self.rhs = Some(rhs);
342 } else {
343 intravisit::walk_expr(self, expr)
344 }
345 }
346 }
347}
348
349fn find_self_assignments<'tcx>(
361 checked_places: &PlaceSet<'tcx>,
362 body: &Body<'tcx>,
363) -> FxHashSet<Location> {
364 let mut self_assign = FxHashSet::default();
365
366 const FIELD_0: FieldIdx = FieldIdx::from_u32(0);
367 const FIELD_1: FieldIdx = FieldIdx::from_u32(1);
368
369 for (bb, bb_data) in body.basic_blocks.iter_enumerated() {
370 for (statement_index, stmt) in bb_data.statements.iter().enumerate() {
371 let StatementKind::Assign(box (first_place, rvalue)) = &stmt.kind else { continue };
372 match rvalue {
373 Rvalue::BinaryOp(
375 BinOp::AddWithOverflow | BinOp::SubWithOverflow | BinOp::MulWithOverflow,
376 box (Operand::Copy(lhs), _),
377 ) => {
378 if statement_index + 1 != bb_data.statements.len() {
380 continue;
381 }
382
383 let TerminatorKind::Assert {
384 cond,
385 target,
386 msg: box AssertKind::Overflow(..),
387 ..
388 } = &bb_data.terminator().kind
389 else {
390 continue;
391 };
392 let Some(assign) = body.basic_blocks[*target].statements.first() else {
393 continue;
394 };
395 let StatementKind::Assign(box (dest, Rvalue::Use(Operand::Move(temp)))) =
396 assign.kind
397 else {
398 continue;
399 };
400
401 if dest != *lhs {
402 continue;
403 }
404
405 let Operand::Move(cond) = cond else { continue };
406 let [PlaceElem::Field(FIELD_0, _)] = &temp.projection.as_slice() else {
407 continue;
408 };
409 let [PlaceElem::Field(FIELD_1, _)] = &cond.projection.as_slice() else {
410 continue;
411 };
412
413 let is_indirect = checked_places
415 .get(dest.as_ref())
416 .map_or(false, |(_, projections)| is_indirect(projections));
417 if is_indirect {
418 continue;
419 }
420
421 if first_place.local == temp.local
422 && first_place.local == cond.local
423 && first_place.projection.is_empty()
424 {
425 self_assign.insert(Location {
427 block: bb,
428 statement_index: bb_data.statements.len() - 1,
429 });
430 self_assign.insert(Location {
431 block: bb,
432 statement_index: bb_data.statements.len(),
433 });
434 self_assign.insert(Location { block: *target, statement_index: 0 });
436 }
437 }
438 Rvalue::BinaryOp(op, box (Operand::Copy(lhs), _)) => {
440 if lhs != first_place {
441 continue;
442 }
443
444 let is_indirect = checked_places
446 .get(first_place.as_ref())
447 .map_or(false, |(_, projections)| is_indirect(projections));
448 if is_indirect {
449 continue;
450 }
451
452 self_assign.insert(Location { block: bb, statement_index });
453
454 if let BinOp::Div | BinOp::Rem = op
457 && statement_index == 0
458 && let &[pred] = body.basic_blocks.predecessors()[bb].as_slice()
459 && let TerminatorKind::Assert { msg, .. } =
460 &body.basic_blocks[pred].terminator().kind
461 && let AssertKind::Overflow(..) = **msg
462 && let len = body.basic_blocks[pred].statements.len()
463 && len >= 2
464 {
465 self_assign.insert(Location { block: pred, statement_index: len - 1 });
467 self_assign.insert(Location { block: pred, statement_index: len - 2 });
469 }
470 }
471 _ => {}
472 }
473 }
474 }
475
476 self_assign
477}
478
479#[derive(Default, Debug)]
480struct PlaceSet<'tcx> {
481 places: IndexVec<PlaceIndex, PlaceRef<'tcx>>,
482 names: IndexVec<PlaceIndex, Option<(Symbol, Span)>>,
483
484 locals: IndexVec<Local, Option<PlaceIndex>>,
486
487 capture_field_pos: usize,
490 captures: IndexVec<FieldIdx, (PlaceIndex, bool)>,
492}
493
494impl<'tcx> PlaceSet<'tcx> {
495 fn insert_locals(&mut self, decls: &IndexVec<Local, LocalDecl<'tcx>>) {
496 self.locals = IndexVec::from_elem(None, &decls);
497 for (local, decl) in decls.iter_enumerated() {
498 if let LocalInfo::User(BindingForm::Var(_) | BindingForm::RefForGuard(_)) =
501 decl.local_info()
502 {
503 let index = self.places.push(local.into());
504 self.locals[local] = Some(index);
505 let _index = self.names.push(None);
506 debug_assert_eq!(index, _index);
507 }
508 }
509 }
510
511 fn insert_captures(
512 &mut self,
513 tcx: TyCtxt<'tcx>,
514 self_is_ref: bool,
515 captures: &[&'tcx ty::CapturedPlace<'tcx>],
516 upvars: &ty::List<Ty<'tcx>>,
517 ) {
518 debug_assert_eq!(self.locals[ty::CAPTURE_STRUCT_LOCAL], None);
520
521 let self_place = Place {
522 local: ty::CAPTURE_STRUCT_LOCAL,
523 projection: tcx.mk_place_elems(if self_is_ref { &[PlaceElem::Deref] } else { &[] }),
524 };
525 if self_is_ref {
526 self.capture_field_pos = 1;
527 }
528
529 for (f, (capture, ty)) in std::iter::zip(captures, upvars).enumerate() {
530 let f = FieldIdx::from_usize(f);
531 let elem = PlaceElem::Field(f, ty);
532 let by_ref = matches!(capture.info.capture_kind, ty::UpvarCapture::ByRef(..));
533 let place = if by_ref {
534 self_place.project_deeper(&[elem, PlaceElem::Deref], tcx)
535 } else {
536 self_place.project_deeper(&[elem], tcx)
537 };
538 let index = self.places.push(place.as_ref());
539 let _f = self.captures.push((index, by_ref));
540 debug_assert_eq!(_f, f);
541
542 self.names.insert(
545 index,
546 (Symbol::intern(&capture.to_string(tcx)), capture.get_path_span(tcx)),
547 );
548 }
549 }
550
551 fn record_debuginfo(&mut self, var_debug_info: &Vec<VarDebugInfo<'tcx>>) {
552 let ignore_name = |name: Symbol| {
553 name == sym::empty || name == kw::SelfLower || name.as_str().starts_with('_')
554 };
555 for var_debug_info in var_debug_info {
556 if let VarDebugInfoContents::Place(place) = var_debug_info.value
557 && let Some(index) = self.locals[place.local]
558 && !ignore_name(var_debug_info.name)
559 {
560 self.names.get_or_insert_with(index, || {
561 (var_debug_info.name, var_debug_info.source_info.span)
562 });
563 }
564 }
565
566 for index_opt in self.locals.iter_mut() {
568 if let Some(index) = *index_opt {
569 let remove = match self.names[index] {
570 None => true,
571 Some((name, _)) => ignore_name(name),
572 };
573 if remove {
574 *index_opt = None;
575 }
576 }
577 }
578 }
579
580 #[inline]
581 fn get(&self, place: PlaceRef<'tcx>) -> Option<(PlaceIndex, &'tcx [PlaceElem<'tcx>])> {
582 if let Some(index) = self.locals[place.local] {
583 return Some((index, place.projection));
584 }
585 if place.local == ty::CAPTURE_STRUCT_LOCAL
586 && !self.captures.is_empty()
587 && self.capture_field_pos < place.projection.len()
588 && let PlaceElem::Field(f, _) = place.projection[self.capture_field_pos]
589 && let Some((index, by_ref)) = self.captures.get(f)
590 {
591 let mut start = self.capture_field_pos + 1;
592 if *by_ref {
593 start += 1;
595 }
596 if start <= place.projection.len() {
598 let projection = &place.projection[start..];
599 return Some((*index, projection));
600 }
601 }
602 None
603 }
604
605 fn iter(&self) -> impl Iterator<Item = (PlaceIndex, &PlaceRef<'tcx>)> {
606 self.places.iter_enumerated()
607 }
608
609 fn len(&self) -> usize {
610 self.places.len()
611 }
612}
613
614struct AssignmentResult<'a, 'tcx> {
615 tcx: TyCtxt<'tcx>,
616 typing_env: ty::TypingEnv<'tcx>,
617 checked_places: &'a PlaceSet<'tcx>,
618 body: &'a Body<'tcx>,
619 ever_live: DenseBitSet<PlaceIndex>,
621 ever_dropped: DenseBitSet<PlaceIndex>,
624 assignments: IndexVec<PlaceIndex, FxIndexMap<SourceInfo, Access>>,
631}
632
633impl<'a, 'tcx> AssignmentResult<'a, 'tcx> {
634 fn find_dead_assignments(
639 tcx: TyCtxt<'tcx>,
640 typing_env: ty::TypingEnv<'tcx>,
641 checked_places: &'a PlaceSet<'tcx>,
642 cursor: &mut ResultsCursor<'_, 'tcx, MaybeLivePlaces<'_, 'tcx>>,
643 body: &'a Body<'tcx>,
644 ) -> AssignmentResult<'a, 'tcx> {
645 let mut ever_live = DenseBitSet::new_empty(checked_places.len());
646 let mut ever_dropped = DenseBitSet::new_empty(checked_places.len());
647 let mut assignments = IndexVec::<PlaceIndex, FxIndexMap<_, _>>::from_elem(
648 Default::default(),
649 &checked_places.places,
650 );
651
652 let mut check_place =
653 |place: Place<'tcx>, kind, source_info: SourceInfo, live: &DenseBitSet<PlaceIndex>| {
654 if let Some((index, extra_projections)) = checked_places.get(place.as_ref()) {
655 if !is_indirect(extra_projections) {
656 let is_direct = extra_projections.is_empty();
657 match assignments[index].entry(source_info) {
658 IndexEntry::Vacant(v) => {
659 let access = Access { kind, live: live.contains(index), is_direct };
660 v.insert(access);
661 }
662 IndexEntry::Occupied(mut o) => {
663 o.get_mut().live |= live.contains(index);
666 o.get_mut().is_direct &= is_direct;
667 }
668 }
669 }
670 }
671 };
672
673 let mut record_drop = |place: Place<'tcx>| {
674 if let Some((index, &[])) = checked_places.get(place.as_ref()) {
675 ever_dropped.insert(index);
676 }
677 };
678
679 for (bb, bb_data) in traversal::postorder(body) {
680 cursor.seek_to_block_end(bb);
681 let live = cursor.get();
682 ever_live.union(live);
683
684 let terminator = bb_data.terminator();
685 match &terminator.kind {
686 TerminatorKind::Call { destination: place, .. }
687 | TerminatorKind::Yield { resume_arg: place, .. } => {
688 check_place(*place, AccessKind::Assign, terminator.source_info, live);
689 record_drop(*place)
690 }
691 TerminatorKind::Drop { place, .. } => record_drop(*place),
692 TerminatorKind::InlineAsm { operands, .. } => {
693 for operand in operands {
694 if let InlineAsmOperand::Out { place: Some(place), .. }
695 | InlineAsmOperand::InOut { out_place: Some(place), .. } = operand
696 {
697 check_place(*place, AccessKind::Assign, terminator.source_info, live);
698 }
699 }
700 }
701 _ => {}
702 }
703
704 for (statement_index, statement) in bb_data.statements.iter().enumerate().rev() {
705 cursor.seek_before_primary_effect(Location { block: bb, statement_index });
706 let live = cursor.get();
707 ever_live.union(live);
708 match &statement.kind {
709 StatementKind::Assign(box (place, _))
710 | StatementKind::SetDiscriminant { box place, .. } => {
711 check_place(*place, AccessKind::Assign, statement.source_info, live);
712 }
713 StatementKind::Retag(_, _)
714 | StatementKind::StorageLive(_)
715 | StatementKind::StorageDead(_)
716 | StatementKind::Coverage(_)
717 | StatementKind::Intrinsic(_)
718 | StatementKind::Nop
719 | StatementKind::FakeRead(_)
720 | StatementKind::PlaceMention(_)
721 | StatementKind::ConstEvalCounter
722 | StatementKind::BackwardIncompatibleDropHint { .. }
723 | StatementKind::AscribeUserType(_, _) => (),
724 }
725 }
726 }
727
728 {
730 cursor.seek_to_block_start(START_BLOCK);
731 let live = cursor.get();
732 ever_live.union(live);
733
734 for (index, place) in checked_places.iter() {
736 let kind = if is_capture(*place) {
737 if place.projection.last() == Some(&PlaceElem::Deref) {
740 continue;
741 }
742
743 AccessKind::Capture
744 } else if body.local_kind(place.local) == LocalKind::Arg {
745 AccessKind::Param
746 } else {
747 continue;
748 };
749 let source_info = body.local_decls[place.local].source_info;
750 let access = Access { kind, live: live.contains(index), is_direct: true };
751 assignments[index].insert(source_info, access);
752 }
753 }
754
755 AssignmentResult {
756 tcx,
757 typing_env,
758 checked_places,
759 ever_live,
760 ever_dropped,
761 assignments,
762 body,
763 }
764 }
765
766 fn merge_guards(&mut self) {
778 for (index, place) in self.checked_places.iter() {
779 let local = place.local;
780 if let &LocalInfo::User(BindingForm::RefForGuard(arm_local)) =
781 self.body.local_decls[local].local_info()
782 {
783 debug_assert!(place.projection.is_empty());
784
785 let Some((arm_index, _proj)) = self.checked_places.get(arm_local.into()) else {
787 continue;
788 };
789 debug_assert_ne!(index, arm_index);
790 debug_assert_eq!(_proj, &[]);
791
792 if self.ever_live.contains(index) {
794 self.ever_live.insert(arm_index);
795 }
796
797 let guard_assignments = std::mem::take(&mut self.assignments[index]);
805 let arm_assignments = &mut self.assignments[arm_index];
806 for (source_info, access) in guard_assignments {
807 match arm_assignments.entry(source_info) {
808 IndexEntry::Vacant(v) => {
809 v.insert(access);
810 }
811 IndexEntry::Occupied(mut o) => {
812 o.get_mut().live |= access.live;
813 }
814 }
815 }
816 }
817 }
818 }
819
820 fn compute_dead_captures(&self, num_captures: usize) -> DenseBitSet<FieldIdx> {
822 let mut dead_captures = DenseBitSet::new_empty(num_captures);
824 for (index, place) in self.checked_places.iter() {
825 if self.ever_live.contains(index) {
826 continue;
827 }
828
829 if is_capture(*place) {
831 for p in place.projection {
832 if let PlaceElem::Field(f, _) = p {
833 dead_captures.insert(*f);
834 break;
835 }
836 }
837 continue;
838 }
839 }
840
841 dead_captures
842 }
843
844 fn is_local_in_reachable_code(&self, local: Local) -> bool {
847 struct LocalVisitor {
848 target_local: Local,
849 found: bool,
850 }
851
852 impl<'tcx> Visitor<'tcx> for LocalVisitor {
853 fn visit_local(&mut self, local: Local, _context: PlaceContext, _location: Location) {
854 if local == self.target_local {
855 self.found = true;
856 }
857 }
858 }
859
860 let mut visitor = LocalVisitor { target_local: local, found: false };
861 for (bb, bb_data) in traversal::postorder(self.body) {
862 visitor.visit_basic_block_data(bb, bb_data);
863 if visitor.found {
864 return true;
865 }
866 }
867
868 false
869 }
870
871 fn report_fully_unused(&mut self) {
873 let tcx = self.tcx;
874
875 let mut string_constants_in_body = None;
878 let mut maybe_suggest_literal_matching_name = |name: Symbol| {
879 let string_constants_in_body = string_constants_in_body.get_or_insert_with(|| {
881 struct LiteralFinder {
882 found: Vec<(Span, String)>,
883 }
884
885 impl<'tcx> Visitor<'tcx> for LiteralFinder {
886 fn visit_const_operand(&mut self, constant: &ConstOperand<'tcx>, _: Location) {
887 if let ty::Ref(_, ref_ty, _) = constant.ty().kind()
888 && ref_ty.kind() == &ty::Str
889 {
890 let rendered_constant = constant.const_.to_string();
891 self.found.push((constant.span, rendered_constant));
892 }
893 }
894 }
895
896 let mut finder = LiteralFinder { found: vec![] };
897 finder.visit_body(self.body);
898 finder.found
899 });
900
901 let brace_name = format!("{{{name}");
902 string_constants_in_body
903 .iter()
904 .filter(|(_, rendered_constant)| {
905 rendered_constant
906 .split(&brace_name)
907 .any(|c| matches!(c.chars().next(), Some('}' | ':')))
908 })
909 .map(|&(lit, _)| errors::UnusedVariableStringInterp { lit })
910 .collect::<Vec<_>>()
911 };
912
913 for (index, place) in self.checked_places.iter() {
915 if self.ever_live.contains(index) {
916 continue;
917 }
918
919 if is_capture(*place) {
921 continue;
922 }
923
924 let local = place.local;
925 let decl = &self.body.local_decls[local];
926
927 if decl.from_compiler_desugaring() {
928 continue;
929 }
930
931 let LocalInfo::User(BindingForm::Var(binding)) = decl.local_info() else { continue };
933 let Some(hir_id) = decl.source_info.scope.lint_root(&self.body.source_scopes) else {
934 continue;
935 };
936
937 let introductions = &binding.introductions;
938
939 let Some((name, def_span)) = self.checked_places.names[index] else { continue };
940
941 let from_macro = def_span.from_expansion()
944 && introductions.iter().any(|intro| intro.span.eq_ctxt(def_span));
945
946 let maybe_suggest_typo = || {
947 if let LocalKind::Arg = self.body.local_kind(local) {
948 None
949 } else {
950 maybe_suggest_unit_pattern_typo(
951 tcx,
952 self.body.source.def_id(),
953 name,
954 def_span,
955 decl.ty,
956 )
957 }
958 };
959
960 let statements = &mut self.assignments[index];
961 if statements.is_empty() {
962 if !self.is_local_in_reachable_code(local) {
963 continue;
964 }
965
966 let sugg = if from_macro {
967 errors::UnusedVariableSugg::NoSugg { span: def_span, name }
968 } else {
969 let typo = maybe_suggest_typo();
970 errors::UnusedVariableSugg::TryPrefix { spans: vec![def_span], name, typo }
971 };
972 tcx.emit_node_span_lint(
973 lint::builtin::UNUSED_VARIABLES,
974 hir_id,
975 def_span,
976 errors::UnusedVariable {
977 name,
978 string_interp: maybe_suggest_literal_matching_name(name),
979 sugg,
980 },
981 );
982 continue;
983 }
984
985 statements.retain(|source_info, _| {
989 source_info.span.find_ancestor_inside(binding.pat_span).is_none()
990 });
991
992 if let Some((_, initializer_span)) = binding.opt_match_place {
995 statements.retain(|source_info, _| {
996 let within = source_info.span.find_ancestor_inside(initializer_span);
997 let outer_initializer_span =
998 initializer_span.find_ancestor_in_same_ctxt(source_info.span);
999 within.is_none()
1000 && outer_initializer_span.map_or(true, |s| !s.contains(source_info.span))
1001 });
1002 }
1003
1004 if !statements.is_empty() {
1005 if maybe_drop_guard(
1008 tcx,
1009 self.typing_env,
1010 index,
1011 &self.ever_dropped,
1012 self.checked_places,
1013 self.body,
1014 ) {
1015 statements.retain(|_, access| access.is_direct);
1016 if statements.is_empty() {
1017 continue;
1018 }
1019 }
1020
1021 let typo = maybe_suggest_typo();
1022 tcx.emit_node_span_lint(
1023 lint::builtin::UNUSED_VARIABLES,
1024 hir_id,
1025 def_span,
1026 errors::UnusedVarAssignedOnly { name, typo },
1027 );
1028 continue;
1029 }
1030
1031 let spans = introductions.iter().map(|intro| intro.span).collect::<Vec<_>>();
1033
1034 let any_shorthand = introductions.iter().any(|intro| intro.is_shorthand);
1035
1036 let sugg = if any_shorthand {
1037 errors::UnusedVariableSugg::TryIgnore {
1038 name,
1039 shorthands: introductions
1040 .iter()
1041 .filter_map(
1042 |intro| if intro.is_shorthand { Some(intro.span) } else { None },
1043 )
1044 .collect(),
1045 non_shorthands: introductions
1046 .iter()
1047 .filter_map(
1048 |intro| {
1049 if !intro.is_shorthand { Some(intro.span) } else { None }
1050 },
1051 )
1052 .collect(),
1053 }
1054 } else if from_macro {
1055 errors::UnusedVariableSugg::NoSugg { span: def_span, name }
1056 } else if !introductions.is_empty() {
1057 let typo = maybe_suggest_typo();
1058 errors::UnusedVariableSugg::TryPrefix { name, typo, spans: spans.clone() }
1059 } else {
1060 let typo = maybe_suggest_typo();
1061 errors::UnusedVariableSugg::TryPrefix { name, typo, spans: vec![def_span] }
1062 };
1063
1064 tcx.emit_node_span_lint(
1065 lint::builtin::UNUSED_VARIABLES,
1066 hir_id,
1067 spans,
1068 errors::UnusedVariable {
1069 name,
1070 string_interp: maybe_suggest_literal_matching_name(name),
1071 sugg,
1072 },
1073 );
1074 }
1075 }
1076
1077 fn report_unused_assignments(self) {
1080 let tcx = self.tcx;
1081
1082 for (index, statements) in self.assignments.into_iter_enumerated() {
1083 if statements.is_empty() {
1084 continue;
1085 }
1086
1087 let Some((name, decl_span)) = self.checked_places.names[index] else { continue };
1088
1089 let is_maybe_drop_guard = maybe_drop_guard(
1090 tcx,
1091 self.typing_env,
1092 index,
1093 &self.ever_dropped,
1094 self.checked_places,
1095 self.body,
1096 );
1097
1098 for (source_info, Access { live, kind, is_direct }) in statements.into_iter().rev() {
1101 if live {
1102 continue;
1103 }
1104
1105 if !is_direct && is_maybe_drop_guard {
1108 continue;
1109 }
1110
1111 let Some(hir_id) = source_info.scope.lint_root(&self.body.source_scopes) else {
1113 continue;
1114 };
1115
1116 match kind {
1117 AccessKind::Assign => {
1118 let suggestion = annotate_mut_binding_to_immutable_binding(
1119 tcx,
1120 self.checked_places.places[index],
1121 self.body.source.def_id().expect_local(),
1122 source_info.span,
1123 self.body,
1124 );
1125 tcx.emit_node_span_lint(
1126 lint::builtin::UNUSED_ASSIGNMENTS,
1127 hir_id,
1128 source_info.span,
1129 errors::UnusedAssign { name, help: suggestion.is_none(), suggestion },
1130 )
1131 }
1132 AccessKind::Param => tcx.emit_node_span_lint(
1133 lint::builtin::UNUSED_ASSIGNMENTS,
1134 hir_id,
1135 source_info.span,
1136 errors::UnusedAssignPassed { name },
1137 ),
1138 AccessKind::Capture => tcx.emit_node_span_lint(
1139 lint::builtin::UNUSED_ASSIGNMENTS,
1140 hir_id,
1141 decl_span,
1142 errors::UnusedCaptureMaybeCaptureRef { name },
1143 ),
1144 }
1145 }
1146 }
1147 }
1148}
1149
1150rustc_index::newtype_index! {
1151 pub struct PlaceIndex {}
1152}
1153
1154impl DebugWithContext<MaybeLivePlaces<'_, '_>> for PlaceIndex {
1155 fn fmt_with(
1156 &self,
1157 ctxt: &MaybeLivePlaces<'_, '_>,
1158 f: &mut std::fmt::Formatter<'_>,
1159 ) -> std::fmt::Result {
1160 std::fmt::Debug::fmt(&ctxt.checked_places.places[*self], f)
1161 }
1162}
1163
1164pub struct MaybeLivePlaces<'a, 'tcx> {
1165 tcx: TyCtxt<'tcx>,
1166 checked_places: &'a PlaceSet<'tcx>,
1167 capture_kind: CaptureKind,
1168 self_assignment: FxHashSet<Location>,
1169}
1170
1171impl<'tcx> MaybeLivePlaces<'_, 'tcx> {
1172 fn transfer_function<'a>(
1173 &'a self,
1174 trans: &'a mut DenseBitSet<PlaceIndex>,
1175 ) -> TransferFunction<'a, 'tcx> {
1176 TransferFunction {
1177 tcx: self.tcx,
1178 checked_places: &self.checked_places,
1179 capture_kind: self.capture_kind,
1180 trans,
1181 self_assignment: &self.self_assignment,
1182 }
1183 }
1184}
1185
1186impl<'tcx> Analysis<'tcx> for MaybeLivePlaces<'_, 'tcx> {
1187 type Domain = DenseBitSet<PlaceIndex>;
1188 type Direction = Backward;
1189
1190 const NAME: &'static str = "liveness-lint";
1191
1192 fn bottom_value(&self, _: &Body<'tcx>) -> Self::Domain {
1193 DenseBitSet::new_empty(self.checked_places.len())
1195 }
1196
1197 fn initialize_start_block(&self, _: &Body<'tcx>, _: &mut Self::Domain) {
1198 }
1200
1201 fn apply_primary_statement_effect(
1202 &self,
1203 trans: &mut Self::Domain,
1204 statement: &Statement<'tcx>,
1205 location: Location,
1206 ) {
1207 self.transfer_function(trans).visit_statement(statement, location);
1208 }
1209
1210 fn apply_primary_terminator_effect<'mir>(
1211 &self,
1212 trans: &mut Self::Domain,
1213 terminator: &'mir Terminator<'tcx>,
1214 location: Location,
1215 ) -> TerminatorEdges<'mir, 'tcx> {
1216 self.transfer_function(trans).visit_terminator(terminator, location);
1217 terminator.edges()
1218 }
1219
1220 fn apply_call_return_effect(
1221 &self,
1222 _trans: &mut Self::Domain,
1223 _block: BasicBlock,
1224 _return_places: CallReturnPlaces<'_, 'tcx>,
1225 ) {
1226 }
1228}
1229
1230struct TransferFunction<'a, 'tcx> {
1231 tcx: TyCtxt<'tcx>,
1232 checked_places: &'a PlaceSet<'tcx>,
1233 trans: &'a mut DenseBitSet<PlaceIndex>,
1234 capture_kind: CaptureKind,
1235 self_assignment: &'a FxHashSet<Location>,
1236}
1237
1238impl<'tcx> Visitor<'tcx> for TransferFunction<'_, 'tcx> {
1239 fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
1240 match statement.kind {
1241 StatementKind::FakeRead(box (FakeReadCause::ForLet(None), _)) => return,
1244 StatementKind::Assign(box (ref dest, ref rvalue))
1246 if self.self_assignment.contains(&location) =>
1247 {
1248 if let Rvalue::BinaryOp(
1249 BinOp::AddWithOverflow | BinOp::SubWithOverflow | BinOp::MulWithOverflow,
1250 box (_, rhs),
1251 ) = rvalue
1252 {
1253 self.visit_operand(rhs, location);
1257 self.visit_place(
1258 dest,
1259 PlaceContext::MutatingUse(MutatingUseContext::Store),
1260 location,
1261 );
1262 } else if let Rvalue::BinaryOp(_, box (_, rhs)) = rvalue {
1263 self.visit_operand(rhs, location);
1267 } else {
1268 self.visit_rvalue(rvalue, location);
1273 }
1274 }
1275 _ => self.super_statement(statement, location),
1276 }
1277 }
1278
1279 fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
1280 match terminator.kind {
1283 TerminatorKind::Return
1284 | TerminatorKind::Yield { .. }
1285 | TerminatorKind::Goto { target: START_BLOCK } if self.capture_kind != CaptureKind::None =>
1287 {
1288 for (index, place) in self.checked_places.iter() {
1290 if place.local == ty::CAPTURE_STRUCT_LOCAL
1291 && place.projection.last() == Some(&PlaceElem::Deref)
1292 {
1293 self.trans.insert(index);
1294 }
1295 }
1296 }
1297 TerminatorKind::Drop { .. } => {}
1299 TerminatorKind::Assert { .. } => {}
1301 _ => self.super_terminator(terminator, location),
1302 }
1303 }
1304
1305 fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
1306 match rvalue {
1307 Rvalue::Aggregate(
1311 box AggregateKind::Closure(def_id, _) | box AggregateKind::Coroutine(def_id, _),
1312 operands,
1313 ) => {
1314 if let Some(def_id) = def_id.as_local() {
1315 let dead_captures = self.tcx.check_liveness(def_id);
1316 for (field, operand) in
1317 operands.iter_enumerated().take(dead_captures.domain_size())
1318 {
1319 if !dead_captures.contains(field) {
1320 self.visit_operand(operand, location);
1321 }
1322 }
1323 }
1324 }
1325 _ => self.super_rvalue(rvalue, location),
1326 }
1327 }
1328
1329 fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, location: Location) {
1330 if let Some((index, extra_projections)) = self.checked_places.get(place.as_ref()) {
1331 for i in (extra_projections.len()..=place.projection.len()).rev() {
1332 let place_part =
1333 PlaceRef { local: place.local, projection: &place.projection[..i] };
1334 let extra_projections = &place.projection[i..];
1335
1336 if let Some(&elem) = extra_projections.get(0) {
1337 self.visit_projection_elem(place_part, elem, context, location);
1338 }
1339 }
1340
1341 match DefUse::for_place(extra_projections, context) {
1342 Some(DefUse::Def) => {
1343 self.trans.remove(index);
1344 }
1345 Some(DefUse::Use) => {
1346 self.trans.insert(index);
1347 }
1348 None => {}
1349 }
1350 } else {
1351 self.super_place(place, context, location)
1352 }
1353 }
1354
1355 fn visit_local(&mut self, local: Local, context: PlaceContext, _: Location) {
1356 if let Some((index, _proj)) = self.checked_places.get(local.into()) {
1357 debug_assert_eq!(_proj, &[]);
1358 match DefUse::for_place(&[], context) {
1359 Some(DefUse::Def) => {
1360 self.trans.remove(index);
1361 }
1362 Some(DefUse::Use) => {
1363 self.trans.insert(index);
1364 }
1365 _ => {}
1366 }
1367 }
1368 }
1369}
1370
1371#[derive(Eq, PartialEq, Debug, Clone)]
1372enum DefUse {
1373 Def,
1374 Use,
1375}
1376
1377fn is_indirect(proj: &[PlaceElem<'_>]) -> bool {
1378 proj.iter().any(|p| p.is_indirect())
1379}
1380
1381impl DefUse {
1382 fn for_place<'tcx>(projection: &[PlaceElem<'tcx>], context: PlaceContext) -> Option<DefUse> {
1383 let is_indirect = is_indirect(projection);
1384 match context {
1385 PlaceContext::MutatingUse(
1386 MutatingUseContext::Store | MutatingUseContext::SetDiscriminant,
1387 ) => {
1388 if is_indirect {
1389 Some(DefUse::Use)
1392 } else if projection.is_empty() {
1393 Some(DefUse::Def)
1394 } else {
1395 None
1396 }
1397 }
1398
1399 PlaceContext::MutatingUse(
1404 MutatingUseContext::Call
1405 | MutatingUseContext::Yield
1406 | MutatingUseContext::AsmOutput,
1407 ) => is_indirect.then_some(DefUse::Use),
1408
1409 PlaceContext::MutatingUse(
1411 MutatingUseContext::RawBorrow
1412 | MutatingUseContext::Borrow
1413 | MutatingUseContext::Drop
1414 | MutatingUseContext::Retag,
1415 )
1416 | PlaceContext::NonMutatingUse(
1417 NonMutatingUseContext::RawBorrow
1418 | NonMutatingUseContext::Copy
1419 | NonMutatingUseContext::Inspect
1420 | NonMutatingUseContext::Move
1421 | NonMutatingUseContext::FakeBorrow
1422 | NonMutatingUseContext::SharedBorrow
1423 | NonMutatingUseContext::PlaceMention,
1424 ) => Some(DefUse::Use),
1425
1426 PlaceContext::NonUse(
1427 NonUseContext::StorageLive
1428 | NonUseContext::StorageDead
1429 | NonUseContext::AscribeUserTy(_)
1430 | NonUseContext::BackwardIncompatibleDropHint
1431 | NonUseContext::VarDebugInfo,
1432 ) => None,
1433
1434 PlaceContext::MutatingUse(MutatingUseContext::Projection)
1435 | PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection) => {
1436 unreachable!("A projection could be a def or a use and must be handled separately")
1437 }
1438 }
1439 }
1440}