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