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