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_literal_matching_name(
170 body: &Body<'_>,
171 name: Symbol,
172) -> Vec<errors::UnusedVariableStringInterp> {
173 struct LiteralFinder<'body, 'tcx> {
174 body: &'body Body<'tcx>,
175 name: String,
176 name_colon: String,
177 found: Vec<errors::UnusedVariableStringInterp>,
178 }
179
180 impl<'tcx> Visitor<'tcx> for LiteralFinder<'_, 'tcx> {
181 fn visit_const_operand(&mut self, constant: &ConstOperand<'tcx>, loc: Location) {
182 if let ty::Ref(_, ref_ty, _) = constant.ty().kind()
183 && ref_ty.kind() == &ty::Str
184 {
185 let rendered_constant = constant.const_.to_string();
186 if rendered_constant.contains(&self.name)
187 || rendered_constant.contains(&self.name_colon)
188 {
189 let lit = self.body.source_info(loc).span;
190 self.found.push(errors::UnusedVariableStringInterp { lit });
191 }
192 }
193 }
194 }
195
196 let mut finder = LiteralFinder {
197 body,
198 name: format!("{{{name}}}"),
199 name_colon: format!("{{{name}:"),
200 found: vec![],
201 };
202 finder.visit_body(body);
203 finder.found
204}
205
206fn maybe_suggest_unit_pattern_typo<'tcx>(
208 tcx: TyCtxt<'tcx>,
209 body_def_id: DefId,
210 name: Symbol,
211 span: Span,
212 ty: Ty<'tcx>,
213) -> Option<errors::PatternTypo> {
214 if let ty::Adt(adt_def, _) = ty.peel_refs().kind() {
215 let variant_names: Vec<_> = adt_def
216 .variants()
217 .iter()
218 .filter(|v| matches!(v.ctor, Some((CtorKind::Const, _))))
219 .map(|v| v.name)
220 .collect();
221 if let Some(name) = find_best_match_for_name(&variant_names, name, None)
222 && let Some(variant) = adt_def
223 .variants()
224 .iter()
225 .find(|v| v.name == name && matches!(v.ctor, Some((CtorKind::Const, _))))
226 {
227 return Some(errors::PatternTypo {
228 span,
229 code: with_no_trimmed_paths!(tcx.def_path_str(variant.def_id)),
230 kind: tcx.def_descr(variant.def_id),
231 item_name: variant.name,
232 });
233 }
234 }
235
236 let constants = tcx
239 .hir_body_owners()
240 .filter(|&def_id| {
241 matches!(tcx.def_kind(def_id), DefKind::Const)
242 && tcx.type_of(def_id).instantiate_identity() == ty
243 && tcx.visibility(def_id).is_accessible_from(body_def_id, tcx)
244 })
245 .collect::<Vec<_>>();
246 let names = constants.iter().map(|&def_id| tcx.item_name(def_id)).collect::<Vec<_>>();
247 if let Some(item_name) = find_best_match_for_name(&names, name, None)
248 && let Some(position) = names.iter().position(|&n| n == item_name)
249 && let Some(&def_id) = constants.get(position)
250 {
251 return Some(errors::PatternTypo {
252 span,
253 code: with_no_trimmed_paths!(tcx.def_path_str(def_id)),
254 kind: "constant",
255 item_name,
256 });
257 }
258
259 None
260}
261
262fn maybe_drop_guard<'tcx>(
264 tcx: TyCtxt<'tcx>,
265 typing_env: ty::TypingEnv<'tcx>,
266 index: PlaceIndex,
267 ever_dropped: &DenseBitSet<PlaceIndex>,
268 checked_places: &PlaceSet<'tcx>,
269 body: &Body<'tcx>,
270) -> bool {
271 if ever_dropped.contains(index) {
272 let ty = checked_places.places[index].ty(&body.local_decls, tcx).ty;
273 matches!(
274 ty.kind(),
275 ty::Closure(..)
276 | ty::Coroutine(..)
277 | ty::Tuple(..)
278 | ty::Adt(..)
279 | ty::Dynamic(..)
280 | ty::Array(..)
281 | ty::Slice(..)
282 | ty::Alias(ty::Opaque, ..)
283 ) && ty.needs_drop(tcx, typing_env)
284 } else {
285 false
286 }
287}
288
289fn annotate_mut_binding_to_immutable_binding<'tcx>(
308 tcx: TyCtxt<'tcx>,
309 place: PlaceRef<'tcx>,
310 body_def_id: LocalDefId,
311 assignment_span: Span,
312 body: &Body<'tcx>,
313) -> Option<errors::UnusedAssignSuggestion> {
314 use rustc_hir as hir;
315 use rustc_hir::intravisit::{self, Visitor};
316
317 let local = place.as_local()?;
319 let LocalKind::Arg = body.local_kind(local) else { return None };
320 let Mutability::Mut = body.local_decls[local].mutability else { return None };
321
322 let hir_param_index =
324 local.as_usize() - if tcx.is_closure_like(body_def_id.to_def_id()) { 2 } else { 1 };
325 let fn_decl = tcx.hir_node_by_def_id(body_def_id).fn_decl()?;
326 let ty = fn_decl.inputs[hir_param_index];
327 let hir::TyKind::Ref(lt, mut_ty) = ty.kind else { return None };
328
329 let hir_body = tcx.hir_maybe_body_owned_by(body_def_id)?;
331 let param = hir_body.params[hir_param_index];
332 let hir::PatKind::Binding(hir::BindingMode::MUT, _hir_id, ident, _) = param.pat.kind else {
333 return None;
334 };
335
336 let mut finder = ExprFinder { assignment_span, lhs: None, rhs: None };
338 finder.visit_body(hir_body);
339 let lhs = finder.lhs?;
340 let rhs = finder.rhs?;
341
342 let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _mut, inner) = rhs.kind else { return None };
343
344 let pre = if lt.ident.span.is_empty() { "" } else { " " };
346 let ty_span = if mut_ty.mutbl.is_mut() {
347 None
349 } else {
350 Some(mut_ty.ty.span.shrink_to_lo())
352 };
353
354 return Some(errors::UnusedAssignSuggestion {
355 ty_span,
356 pre,
357 ty_ref_span: param.pat.span.until(ident.span),
359 pre_lhs_span: lhs.span.shrink_to_lo(),
361 rhs_borrow_span: rhs.span.until(inner.span),
363 });
364
365 #[derive(Debug)]
366 struct ExprFinder<'hir> {
367 assignment_span: Span,
368 lhs: Option<&'hir hir::Expr<'hir>>,
369 rhs: Option<&'hir hir::Expr<'hir>>,
370 }
371 impl<'hir> Visitor<'hir> for ExprFinder<'hir> {
372 fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
373 if expr.span == self.assignment_span
374 && let hir::ExprKind::Assign(lhs, rhs, _) = expr.kind
375 {
376 self.lhs = Some(lhs);
377 self.rhs = Some(rhs);
378 } else {
379 intravisit::walk_expr(self, expr)
380 }
381 }
382 }
383}
384
385fn find_self_assignments<'tcx>(
397 checked_places: &PlaceSet<'tcx>,
398 body: &Body<'tcx>,
399) -> FxHashSet<Location> {
400 let mut self_assign = FxHashSet::default();
401
402 const FIELD_0: FieldIdx = FieldIdx::from_u32(0);
403 const FIELD_1: FieldIdx = FieldIdx::from_u32(1);
404
405 for (bb, bb_data) in body.basic_blocks.iter_enumerated() {
406 for (statement_index, stmt) in bb_data.statements.iter().enumerate() {
407 let StatementKind::Assign(box (first_place, rvalue)) = &stmt.kind else { continue };
408 match rvalue {
409 Rvalue::BinaryOp(
411 BinOp::AddWithOverflow | BinOp::SubWithOverflow | BinOp::MulWithOverflow,
412 box (Operand::Copy(lhs), _),
413 ) => {
414 if statement_index + 1 != bb_data.statements.len() {
416 continue;
417 }
418
419 let TerminatorKind::Assert {
420 cond,
421 target,
422 msg: box AssertKind::Overflow(..),
423 ..
424 } = &bb_data.terminator().kind
425 else {
426 continue;
427 };
428 let Some(assign) = body.basic_blocks[*target].statements.first() else {
429 continue;
430 };
431 let StatementKind::Assign(box (dest, Rvalue::Use(Operand::Move(temp)))) =
432 assign.kind
433 else {
434 continue;
435 };
436
437 if dest != *lhs {
438 continue;
439 }
440
441 let Operand::Move(cond) = cond else { continue };
442 let [PlaceElem::Field(FIELD_0, _)] = &temp.projection.as_slice() else {
443 continue;
444 };
445 let [PlaceElem::Field(FIELD_1, _)] = &cond.projection.as_slice() else {
446 continue;
447 };
448
449 let is_indirect = checked_places
451 .get(dest.as_ref())
452 .map_or(false, |(_, projections)| is_indirect(projections));
453 if is_indirect {
454 continue;
455 }
456
457 if first_place.local == temp.local
458 && first_place.local == cond.local
459 && first_place.projection.is_empty()
460 {
461 self_assign.insert(Location {
463 block: bb,
464 statement_index: bb_data.statements.len() - 1,
465 });
466 self_assign.insert(Location {
467 block: bb,
468 statement_index: bb_data.statements.len(),
469 });
470 self_assign.insert(Location { block: *target, statement_index: 0 });
472 }
473 }
474 Rvalue::BinaryOp(op, box (Operand::Copy(lhs), _)) => {
476 if lhs != first_place {
477 continue;
478 }
479
480 let is_indirect = checked_places
482 .get(first_place.as_ref())
483 .map_or(false, |(_, projections)| is_indirect(projections));
484 if is_indirect {
485 continue;
486 }
487
488 self_assign.insert(Location { block: bb, statement_index });
489
490 if let BinOp::Div | BinOp::Rem = op
493 && statement_index == 0
494 && let &[pred] = body.basic_blocks.predecessors()[bb].as_slice()
495 && let TerminatorKind::Assert { msg, .. } =
496 &body.basic_blocks[pred].terminator().kind
497 && let AssertKind::Overflow(..) = **msg
498 && let len = body.basic_blocks[pred].statements.len()
499 && len >= 2
500 {
501 self_assign.insert(Location { block: pred, statement_index: len - 1 });
503 self_assign.insert(Location { block: pred, statement_index: len - 2 });
505 }
506 }
507 _ => {}
508 }
509 }
510 }
511
512 self_assign
513}
514
515#[derive(Default, Debug)]
516struct PlaceSet<'tcx> {
517 places: IndexVec<PlaceIndex, PlaceRef<'tcx>>,
518 names: IndexVec<PlaceIndex, Option<(Symbol, Span)>>,
519
520 locals: IndexVec<Local, Option<PlaceIndex>>,
522
523 capture_field_pos: usize,
526 captures: IndexVec<FieldIdx, (PlaceIndex, bool)>,
528}
529
530impl<'tcx> PlaceSet<'tcx> {
531 fn insert_locals(&mut self, decls: &IndexVec<Local, LocalDecl<'tcx>>) {
532 self.locals = IndexVec::from_elem(None, &decls);
533 for (local, decl) in decls.iter_enumerated() {
534 if let LocalInfo::User(BindingForm::Var(_) | BindingForm::RefForGuard(_)) =
537 decl.local_info()
538 {
539 let index = self.places.push(local.into());
540 self.locals[local] = Some(index);
541 let _index = self.names.push(None);
542 debug_assert_eq!(index, _index);
543 }
544 }
545 }
546
547 fn insert_captures(
548 &mut self,
549 tcx: TyCtxt<'tcx>,
550 self_is_ref: bool,
551 captures: &[&'tcx ty::CapturedPlace<'tcx>],
552 upvars: &ty::List<Ty<'tcx>>,
553 ) {
554 debug_assert_eq!(self.locals[ty::CAPTURE_STRUCT_LOCAL], None);
556
557 let self_place = Place {
558 local: ty::CAPTURE_STRUCT_LOCAL,
559 projection: tcx.mk_place_elems(if self_is_ref { &[PlaceElem::Deref] } else { &[] }),
560 };
561 if self_is_ref {
562 self.capture_field_pos = 1;
563 }
564
565 for (f, (capture, ty)) in std::iter::zip(captures, upvars).enumerate() {
566 let f = FieldIdx::from_usize(f);
567 let elem = PlaceElem::Field(f, ty);
568 let by_ref = matches!(capture.info.capture_kind, ty::UpvarCapture::ByRef(..));
569 let place = if by_ref {
570 self_place.project_deeper(&[elem, PlaceElem::Deref], tcx)
571 } else {
572 self_place.project_deeper(&[elem], tcx)
573 };
574 let index = self.places.push(place.as_ref());
575 let _f = self.captures.push((index, by_ref));
576 debug_assert_eq!(_f, f);
577
578 self.names.insert(
581 index,
582 (Symbol::intern(&capture.to_string(tcx)), capture.get_path_span(tcx)),
583 );
584 }
585 }
586
587 fn record_debuginfo(&mut self, var_debug_info: &Vec<VarDebugInfo<'tcx>>) {
588 let ignore_name = |name: Symbol| {
589 name == sym::empty || name == kw::SelfLower || name.as_str().starts_with('_')
590 };
591 for var_debug_info in var_debug_info {
592 if let VarDebugInfoContents::Place(place) = var_debug_info.value
593 && let Some(index) = self.locals[place.local]
594 && !ignore_name(var_debug_info.name)
595 {
596 self.names.get_or_insert_with(index, || {
597 (var_debug_info.name, var_debug_info.source_info.span)
598 });
599 }
600 }
601
602 for index_opt in self.locals.iter_mut() {
604 if let Some(index) = *index_opt {
605 let remove = match self.names[index] {
606 None => true,
607 Some((name, _)) => ignore_name(name),
608 };
609 if remove {
610 *index_opt = None;
611 }
612 }
613 }
614 }
615
616 #[inline]
617 fn get(&self, place: PlaceRef<'tcx>) -> Option<(PlaceIndex, &'tcx [PlaceElem<'tcx>])> {
618 if let Some(index) = self.locals[place.local] {
619 return Some((index, place.projection));
620 }
621 if place.local == ty::CAPTURE_STRUCT_LOCAL
622 && !self.captures.is_empty()
623 && self.capture_field_pos < place.projection.len()
624 && let PlaceElem::Field(f, _) = place.projection[self.capture_field_pos]
625 && let Some((index, by_ref)) = self.captures.get(f)
626 {
627 let mut start = self.capture_field_pos + 1;
628 if *by_ref {
629 start += 1;
631 }
632 if start <= place.projection.len() {
634 let projection = &place.projection[start..];
635 return Some((*index, projection));
636 }
637 }
638 None
639 }
640
641 fn iter(&self) -> impl Iterator<Item = (PlaceIndex, &PlaceRef<'tcx>)> {
642 self.places.iter_enumerated()
643 }
644
645 fn len(&self) -> usize {
646 self.places.len()
647 }
648}
649
650struct AssignmentResult<'a, 'tcx> {
651 tcx: TyCtxt<'tcx>,
652 typing_env: ty::TypingEnv<'tcx>,
653 checked_places: &'a PlaceSet<'tcx>,
654 body: &'a Body<'tcx>,
655 ever_live: DenseBitSet<PlaceIndex>,
657 ever_dropped: DenseBitSet<PlaceIndex>,
660 assignments: IndexVec<PlaceIndex, FxIndexMap<SourceInfo, Access>>,
667}
668
669impl<'a, 'tcx> AssignmentResult<'a, 'tcx> {
670 fn find_dead_assignments(
675 tcx: TyCtxt<'tcx>,
676 typing_env: ty::TypingEnv<'tcx>,
677 checked_places: &'a PlaceSet<'tcx>,
678 cursor: &mut ResultsCursor<'_, 'tcx, MaybeLivePlaces<'_, 'tcx>>,
679 body: &'a Body<'tcx>,
680 ) -> AssignmentResult<'a, 'tcx> {
681 let mut ever_live = DenseBitSet::new_empty(checked_places.len());
682 let mut ever_dropped = DenseBitSet::new_empty(checked_places.len());
683 let mut assignments = IndexVec::<PlaceIndex, FxIndexMap<_, _>>::from_elem(
684 Default::default(),
685 &checked_places.places,
686 );
687
688 let mut check_place =
689 |place: Place<'tcx>, kind, source_info: SourceInfo, live: &DenseBitSet<PlaceIndex>| {
690 if let Some((index, extra_projections)) = checked_places.get(place.as_ref()) {
691 if !is_indirect(extra_projections) {
692 match assignments[index].entry(source_info) {
693 IndexEntry::Vacant(v) => {
694 let access = Access { kind, live: live.contains(index) };
695 v.insert(access);
696 }
697 IndexEntry::Occupied(mut o) => {
698 o.get_mut().live |= live.contains(index);
701 }
702 }
703 }
704 }
705 };
706
707 let mut record_drop = |place: Place<'tcx>| {
708 if let Some((index, &[])) = checked_places.get(place.as_ref()) {
709 ever_dropped.insert(index);
710 }
711 };
712
713 for (bb, bb_data) in traversal::postorder(body) {
714 cursor.seek_to_block_end(bb);
715 let live = cursor.get();
716 ever_live.union(live);
717
718 let terminator = bb_data.terminator();
719 match &terminator.kind {
720 TerminatorKind::Call { destination: place, .. }
721 | TerminatorKind::Yield { resume_arg: place, .. } => {
722 check_place(*place, AccessKind::Assign, terminator.source_info, live);
723 record_drop(*place)
724 }
725 TerminatorKind::Drop { place, .. } => record_drop(*place),
726 TerminatorKind::InlineAsm { operands, .. } => {
727 for operand in operands {
728 if let InlineAsmOperand::Out { place: Some(place), .. }
729 | InlineAsmOperand::InOut { out_place: Some(place), .. } = operand
730 {
731 check_place(*place, AccessKind::Assign, terminator.source_info, live);
732 }
733 }
734 }
735 _ => {}
736 }
737
738 for (statement_index, statement) in bb_data.statements.iter().enumerate().rev() {
739 cursor.seek_before_primary_effect(Location { block: bb, statement_index });
740 let live = cursor.get();
741 ever_live.union(live);
742 match &statement.kind {
743 StatementKind::Assign(box (place, _))
744 | StatementKind::SetDiscriminant { box place, .. } => {
745 check_place(*place, AccessKind::Assign, statement.source_info, live);
746 }
747 StatementKind::Retag(_, _)
748 | StatementKind::StorageLive(_)
749 | StatementKind::StorageDead(_)
750 | StatementKind::Coverage(_)
751 | StatementKind::Intrinsic(_)
752 | StatementKind::Nop
753 | StatementKind::FakeRead(_)
754 | StatementKind::PlaceMention(_)
755 | StatementKind::ConstEvalCounter
756 | StatementKind::BackwardIncompatibleDropHint { .. }
757 | StatementKind::AscribeUserType(_, _) => (),
758 }
759 }
760 }
761
762 {
764 cursor.seek_to_block_start(START_BLOCK);
765 let live = cursor.get();
766 ever_live.union(live);
767
768 for (index, place) in checked_places.iter() {
770 let kind = if is_capture(*place) {
771 if place.projection.last() == Some(&PlaceElem::Deref) {
774 continue;
775 }
776
777 AccessKind::Capture
778 } else if body.local_kind(place.local) == LocalKind::Arg {
779 AccessKind::Param
780 } else {
781 continue;
782 };
783 let source_info = body.local_decls[place.local].source_info;
784 let access = Access { kind, live: live.contains(index) };
785 assignments[index].insert(source_info, access);
786 }
787 }
788
789 AssignmentResult {
790 tcx,
791 typing_env,
792 checked_places,
793 ever_live,
794 ever_dropped,
795 assignments,
796 body,
797 }
798 }
799
800 fn merge_guards(&mut self) {
812 for (index, place) in self.checked_places.iter() {
813 let local = place.local;
814 if let &LocalInfo::User(BindingForm::RefForGuard(arm_local)) =
815 self.body.local_decls[local].local_info()
816 {
817 debug_assert!(place.projection.is_empty());
818
819 let Some((arm_index, _proj)) = self.checked_places.get(arm_local.into()) else {
821 continue;
822 };
823 debug_assert_ne!(index, arm_index);
824 debug_assert_eq!(_proj, &[]);
825
826 if self.ever_live.contains(index) {
828 self.ever_live.insert(arm_index);
829 }
830
831 let guard_assignments = std::mem::take(&mut self.assignments[index]);
839 let arm_assignments = &mut self.assignments[arm_index];
840 for (source_info, access) in guard_assignments {
841 match arm_assignments.entry(source_info) {
842 IndexEntry::Vacant(v) => {
843 v.insert(access);
844 }
845 IndexEntry::Occupied(mut o) => {
846 o.get_mut().live |= access.live;
847 }
848 }
849 }
850 }
851 }
852 }
853
854 fn compute_dead_captures(&self, num_captures: usize) -> DenseBitSet<FieldIdx> {
856 let mut dead_captures = DenseBitSet::new_empty(num_captures);
858 for (index, place) in self.checked_places.iter() {
859 if self.ever_live.contains(index) {
860 continue;
861 }
862
863 if is_capture(*place) {
865 for p in place.projection {
866 if let PlaceElem::Field(f, _) = p {
867 dead_captures.insert(*f);
868 break;
869 }
870 }
871 continue;
872 }
873 }
874
875 dead_captures
876 }
877
878 fn report_fully_unused(&mut self) {
880 let tcx = self.tcx;
881
882 for (index, place) in self.checked_places.iter() {
884 if self.ever_live.contains(index) {
885 continue;
886 }
887
888 if is_capture(*place) {
890 continue;
891 }
892
893 let local = place.local;
894 let decl = &self.body.local_decls[local];
895
896 if decl.from_compiler_desugaring() {
897 continue;
898 }
899
900 let LocalInfo::User(BindingForm::Var(binding)) = decl.local_info() else { continue };
902 let Some(hir_id) = decl.source_info.scope.lint_root(&self.body.source_scopes) else {
903 continue;
904 };
905
906 let introductions = &binding.introductions;
907
908 let Some((name, def_span)) = self.checked_places.names[index] else { continue };
909
910 let from_macro = def_span.from_expansion()
913 && introductions.iter().any(|intro| intro.span.eq_ctxt(def_span));
914
915 let maybe_suggest_typo = || {
916 if let LocalKind::Arg = self.body.local_kind(local) {
917 None
918 } else {
919 maybe_suggest_unit_pattern_typo(
920 tcx,
921 self.body.source.def_id(),
922 name,
923 def_span,
924 decl.ty,
925 )
926 }
927 };
928
929 let statements = &mut self.assignments[index];
930 if statements.is_empty() {
931 let sugg = if from_macro {
932 errors::UnusedVariableSugg::NoSugg { span: def_span, name }
933 } else {
934 let typo = maybe_suggest_typo();
935 errors::UnusedVariableSugg::TryPrefix { spans: vec![def_span], name, typo }
936 };
937 tcx.emit_node_span_lint(
938 lint::builtin::UNUSED_VARIABLES,
939 hir_id,
940 def_span,
941 errors::UnusedVariable {
942 name,
943 string_interp: maybe_suggest_literal_matching_name(self.body, name),
944 sugg,
945 },
946 );
947 continue;
948 }
949
950 statements.retain(|source_info, _| {
954 source_info.span.find_ancestor_inside(binding.pat_span).is_none()
955 });
956
957 if let Some((_, initializer_span)) = binding.opt_match_place {
960 statements.retain(|source_info, _| {
961 let within = source_info.span.find_ancestor_inside(initializer_span);
962 let outer_initializer_span =
963 initializer_span.find_ancestor_in_same_ctxt(source_info.span);
964 within.is_none()
965 && outer_initializer_span.map_or(true, |s| !s.contains(source_info.span))
966 });
967 }
968
969 if !statements.is_empty() {
970 if maybe_drop_guard(
973 tcx,
974 self.typing_env,
975 index,
976 &self.ever_dropped,
977 self.checked_places,
978 self.body,
979 ) {
980 statements.clear();
981 continue;
982 }
983
984 let typo = maybe_suggest_typo();
985 tcx.emit_node_span_lint(
986 lint::builtin::UNUSED_VARIABLES,
987 hir_id,
988 def_span,
989 errors::UnusedVarAssignedOnly { name, typo },
990 );
991 continue;
992 }
993
994 let spans = introductions.iter().map(|intro| intro.span).collect::<Vec<_>>();
996
997 let any_shorthand = introductions.iter().any(|intro| intro.is_shorthand);
998
999 let sugg = if any_shorthand {
1000 errors::UnusedVariableSugg::TryIgnore {
1001 name,
1002 shorthands: introductions
1003 .iter()
1004 .filter_map(
1005 |intro| if intro.is_shorthand { Some(intro.span) } else { None },
1006 )
1007 .collect(),
1008 non_shorthands: introductions
1009 .iter()
1010 .filter_map(
1011 |intro| {
1012 if !intro.is_shorthand { Some(intro.span) } else { None }
1013 },
1014 )
1015 .collect(),
1016 }
1017 } else if from_macro {
1018 errors::UnusedVariableSugg::NoSugg { span: def_span, name }
1019 } else if !introductions.is_empty() {
1020 let typo = maybe_suggest_typo();
1021 errors::UnusedVariableSugg::TryPrefix { name, typo, spans: spans.clone() }
1022 } else {
1023 let typo = maybe_suggest_typo();
1024 errors::UnusedVariableSugg::TryPrefix { name, typo, spans: vec![def_span] }
1025 };
1026
1027 tcx.emit_node_span_lint(
1028 lint::builtin::UNUSED_VARIABLES,
1029 hir_id,
1030 spans,
1031 errors::UnusedVariable {
1032 name,
1033 string_interp: maybe_suggest_literal_matching_name(self.body, name),
1034 sugg,
1035 },
1036 );
1037 }
1038 }
1039
1040 fn report_unused_assignments(self) {
1043 let tcx = self.tcx;
1044
1045 for (index, statements) in self.assignments.into_iter_enumerated() {
1046 if statements.is_empty() {
1047 continue;
1048 }
1049
1050 let Some((name, decl_span)) = self.checked_places.names[index] else { continue };
1051
1052 if maybe_drop_guard(
1055 tcx,
1056 self.typing_env,
1057 index,
1058 &self.ever_dropped,
1059 self.checked_places,
1060 self.body,
1061 ) {
1062 continue;
1063 }
1064
1065 for (source_info, Access { live, kind }) in statements.into_iter().rev() {
1068 if live {
1069 continue;
1070 }
1071
1072 let Some(hir_id) = source_info.scope.lint_root(&self.body.source_scopes) else {
1074 continue;
1075 };
1076
1077 match kind {
1078 AccessKind::Assign => {
1079 let suggestion = annotate_mut_binding_to_immutable_binding(
1080 tcx,
1081 self.checked_places.places[index],
1082 self.body.source.def_id().expect_local(),
1083 source_info.span,
1084 self.body,
1085 );
1086 tcx.emit_node_span_lint(
1087 lint::builtin::UNUSED_ASSIGNMENTS,
1088 hir_id,
1089 source_info.span,
1090 errors::UnusedAssign { name, help: suggestion.is_none(), suggestion },
1091 )
1092 }
1093 AccessKind::Param => tcx.emit_node_span_lint(
1094 lint::builtin::UNUSED_ASSIGNMENTS,
1095 hir_id,
1096 source_info.span,
1097 errors::UnusedAssignPassed { name },
1098 ),
1099 AccessKind::Capture => tcx.emit_node_span_lint(
1100 lint::builtin::UNUSED_ASSIGNMENTS,
1101 hir_id,
1102 decl_span,
1103 errors::UnusedCaptureMaybeCaptureRef { name },
1104 ),
1105 }
1106 }
1107 }
1108 }
1109}
1110
1111rustc_index::newtype_index! {
1112 pub struct PlaceIndex {}
1113}
1114
1115impl DebugWithContext<MaybeLivePlaces<'_, '_>> for PlaceIndex {
1116 fn fmt_with(
1117 &self,
1118 ctxt: &MaybeLivePlaces<'_, '_>,
1119 f: &mut std::fmt::Formatter<'_>,
1120 ) -> std::fmt::Result {
1121 std::fmt::Debug::fmt(&ctxt.checked_places.places[*self], f)
1122 }
1123}
1124
1125pub struct MaybeLivePlaces<'a, 'tcx> {
1126 tcx: TyCtxt<'tcx>,
1127 checked_places: &'a PlaceSet<'tcx>,
1128 capture_kind: CaptureKind,
1129 self_assignment: FxHashSet<Location>,
1130}
1131
1132impl<'tcx> MaybeLivePlaces<'_, 'tcx> {
1133 fn transfer_function<'a>(
1134 &'a self,
1135 trans: &'a mut DenseBitSet<PlaceIndex>,
1136 ) -> TransferFunction<'a, 'tcx> {
1137 TransferFunction {
1138 tcx: self.tcx,
1139 checked_places: &self.checked_places,
1140 capture_kind: self.capture_kind,
1141 trans,
1142 self_assignment: &self.self_assignment,
1143 }
1144 }
1145}
1146
1147impl<'tcx> Analysis<'tcx> for MaybeLivePlaces<'_, 'tcx> {
1148 type Domain = DenseBitSet<PlaceIndex>;
1149 type Direction = Backward;
1150
1151 const NAME: &'static str = "liveness-lint";
1152
1153 fn bottom_value(&self, _: &Body<'tcx>) -> Self::Domain {
1154 DenseBitSet::new_empty(self.checked_places.len())
1156 }
1157
1158 fn initialize_start_block(&self, _: &Body<'tcx>, _: &mut Self::Domain) {
1159 }
1161
1162 fn apply_primary_statement_effect(
1163 &mut self,
1164 trans: &mut Self::Domain,
1165 statement: &Statement<'tcx>,
1166 location: Location,
1167 ) {
1168 self.transfer_function(trans).visit_statement(statement, location);
1169 }
1170
1171 fn apply_primary_terminator_effect<'mir>(
1172 &mut self,
1173 trans: &mut Self::Domain,
1174 terminator: &'mir Terminator<'tcx>,
1175 location: Location,
1176 ) -> TerminatorEdges<'mir, 'tcx> {
1177 self.transfer_function(trans).visit_terminator(terminator, location);
1178 terminator.edges()
1179 }
1180
1181 fn apply_call_return_effect(
1182 &mut self,
1183 _trans: &mut Self::Domain,
1184 _block: BasicBlock,
1185 _return_places: CallReturnPlaces<'_, 'tcx>,
1186 ) {
1187 }
1189}
1190
1191struct TransferFunction<'a, 'tcx> {
1192 tcx: TyCtxt<'tcx>,
1193 checked_places: &'a PlaceSet<'tcx>,
1194 trans: &'a mut DenseBitSet<PlaceIndex>,
1195 capture_kind: CaptureKind,
1196 self_assignment: &'a FxHashSet<Location>,
1197}
1198
1199impl<'tcx> Visitor<'tcx> for TransferFunction<'_, 'tcx> {
1200 fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
1201 match statement.kind {
1202 StatementKind::FakeRead(box (FakeReadCause::ForLet(None), _)) => return,
1205 StatementKind::Assign(box (ref dest, ref rvalue))
1207 if self.self_assignment.contains(&location) =>
1208 {
1209 if let Rvalue::BinaryOp(
1210 BinOp::AddWithOverflow | BinOp::SubWithOverflow | BinOp::MulWithOverflow,
1211 box (_, rhs),
1212 ) = rvalue
1213 {
1214 self.visit_operand(rhs, location);
1218 self.visit_place(
1219 dest,
1220 PlaceContext::MutatingUse(MutatingUseContext::Store),
1221 location,
1222 );
1223 } else if let Rvalue::BinaryOp(_, box (_, rhs)) = rvalue {
1224 self.visit_operand(rhs, location);
1228 } else {
1229 self.visit_rvalue(rvalue, location);
1234 }
1235 }
1236 _ => self.super_statement(statement, location),
1237 }
1238 }
1239
1240 fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
1241 match terminator.kind {
1244 TerminatorKind::Return
1245 | TerminatorKind::Yield { .. }
1246 | TerminatorKind::Goto { target: START_BLOCK } if self.capture_kind != CaptureKind::None =>
1248 {
1249 for (index, place) in self.checked_places.iter() {
1251 if place.local == ty::CAPTURE_STRUCT_LOCAL
1252 && place.projection.last() == Some(&PlaceElem::Deref)
1253 {
1254 self.trans.insert(index);
1255 }
1256 }
1257 }
1258 TerminatorKind::Drop { .. } => {}
1260 TerminatorKind::Assert { .. } => {}
1262 _ => self.super_terminator(terminator, location),
1263 }
1264 }
1265
1266 fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
1267 match rvalue {
1268 Rvalue::Aggregate(
1272 box AggregateKind::Closure(def_id, _) | box AggregateKind::Coroutine(def_id, _),
1273 operands,
1274 ) => {
1275 if let Some(def_id) = def_id.as_local() {
1276 let dead_captures = self.tcx.check_liveness(def_id);
1277 for (field, operand) in
1278 operands.iter_enumerated().take(dead_captures.domain_size())
1279 {
1280 if !dead_captures.contains(field) {
1281 self.visit_operand(operand, location);
1282 }
1283 }
1284 }
1285 }
1286 _ => self.super_rvalue(rvalue, location),
1287 }
1288 }
1289
1290 fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, location: Location) {
1291 if let Some((index, extra_projections)) = self.checked_places.get(place.as_ref()) {
1292 for i in (extra_projections.len()..=place.projection.len()).rev() {
1293 let place_part =
1294 PlaceRef { local: place.local, projection: &place.projection[..i] };
1295 let extra_projections = &place.projection[i..];
1296
1297 if let Some(&elem) = extra_projections.get(0) {
1298 self.visit_projection_elem(place_part, elem, context, location);
1299 }
1300 }
1301
1302 match DefUse::for_place(extra_projections, context) {
1303 Some(DefUse::Def) => {
1304 self.trans.remove(index);
1305 }
1306 Some(DefUse::Use) => {
1307 self.trans.insert(index);
1308 }
1309 None => {}
1310 }
1311 } else {
1312 self.super_place(place, context, location)
1313 }
1314 }
1315
1316 fn visit_local(&mut self, local: Local, context: PlaceContext, _: Location) {
1317 if let Some((index, _proj)) = self.checked_places.get(local.into()) {
1318 debug_assert_eq!(_proj, &[]);
1319 match DefUse::for_place(&[], context) {
1320 Some(DefUse::Def) => {
1321 self.trans.remove(index);
1322 }
1323 Some(DefUse::Use) => {
1324 self.trans.insert(index);
1325 }
1326 _ => {}
1327 }
1328 }
1329 }
1330}
1331
1332#[derive(Eq, PartialEq, Debug, Clone)]
1333enum DefUse {
1334 Def,
1335 Use,
1336}
1337
1338fn is_indirect(proj: &[PlaceElem<'_>]) -> bool {
1339 proj.iter().any(|p| p.is_indirect())
1340}
1341
1342impl DefUse {
1343 fn for_place<'tcx>(projection: &[PlaceElem<'tcx>], context: PlaceContext) -> Option<DefUse> {
1344 let is_indirect = is_indirect(projection);
1345 match context {
1346 PlaceContext::MutatingUse(
1347 MutatingUseContext::Store | MutatingUseContext::SetDiscriminant,
1348 ) => {
1349 if is_indirect {
1350 Some(DefUse::Use)
1353 } else if projection.is_empty() {
1354 Some(DefUse::Def)
1355 } else {
1356 None
1357 }
1358 }
1359
1360 PlaceContext::MutatingUse(
1365 MutatingUseContext::Call
1366 | MutatingUseContext::Yield
1367 | MutatingUseContext::AsmOutput,
1368 ) => is_indirect.then_some(DefUse::Use),
1369
1370 PlaceContext::MutatingUse(
1372 MutatingUseContext::RawBorrow
1373 | MutatingUseContext::Borrow
1374 | MutatingUseContext::Drop
1375 | MutatingUseContext::Retag,
1376 )
1377 | PlaceContext::NonMutatingUse(
1378 NonMutatingUseContext::RawBorrow
1379 | NonMutatingUseContext::Copy
1380 | NonMutatingUseContext::Inspect
1381 | NonMutatingUseContext::Move
1382 | NonMutatingUseContext::FakeBorrow
1383 | NonMutatingUseContext::SharedBorrow
1384 | NonMutatingUseContext::PlaceMention,
1385 ) => Some(DefUse::Use),
1386
1387 PlaceContext::NonUse(
1388 NonUseContext::StorageLive
1389 | NonUseContext::StorageDead
1390 | NonUseContext::AscribeUserTy(_)
1391 | NonUseContext::BackwardIncompatibleDropHint
1392 | NonUseContext::VarDebugInfo,
1393 ) => None,
1394
1395 PlaceContext::MutatingUse(MutatingUseContext::Projection)
1396 | PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection) => {
1397 unreachable!("A projection could be a def or a use and must be handled separately")
1398 }
1399 }
1400 }
1401}