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