rustc_mir_transform/
liveness.rs

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    /// Describe the current access.
43    kind: AccessKind,
44    /// Is the accessed place is live at the current statement?
45    /// When we encounter multiple statements at the same location, we only increase the liveness,
46    /// in order to avoid false positives.
47    live: bool,
48    /// Is this a direct access to the place itself, no projections, or to a field?
49    /// This helps distinguish `x = ...` from `x.field = ...`
50    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    // Don't run on synthetic MIR, as that will ICE trying to access HIR.
56    if tcx.is_synthetic_mir(def_id) {
57        return DenseBitSet::new_empty(0);
58    }
59
60    // Don't run unused pass for intrinsics
61    if tcx.intrinsic(def_id.to_def_id()).is_some() {
62        return DenseBitSet::new_empty(0);
63    }
64
65    // Don't run unused pass for #[naked]
66    if find_attr!(tcx.get_all_attrs(def_id.to_def_id()), AttributeKind::Naked(..)) {
67        return DenseBitSet::new_empty(0);
68    }
69
70    // Don't run unused pass for #[derive]
71    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    // Don't run unused pass for items generated by foreign macros
79    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    // Don't run if there are errors.
87    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    // The body is the one of a closure or generator, so we also want to analyse captures.
95    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        // `FnMut` closures can modify captured values and carry those
118        // modified values with them in subsequent calls. To model this behaviour,
119        // we consider the `FnMut` closure as jumping to `bb0` upon return.
120        if let CaptureKind::Closure(ty::ClosureKind::FnMut) = capture_kind {
121            // FIXME: stop cloning the body.
122            body_mem = body.clone();
123            for bbdata in body_mem.basic_blocks_mut() {
124                // We can call a closure again, either after a normal return or an unwind.
125                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    // Get the remaining variables' names from debuginfo.
140    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/// Small helper to make semantics easier to read.
165#[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
175/// Give a diagnostic when an unused variable may be a typo of a unit variant or a struct.
176fn 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    // Look for consts of the same type with similar names as well,
206    // not just unit structs and variants.
207    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
231/// Return whether we should consider the current place as a drop guard and skip reporting.
232fn 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
258/// Detect the following case
259///
260/// ```text
261/// fn change_object(mut a: &Ty) {
262///     let a = Ty::new();
263///     b = &a;
264/// }
265/// ```
266///
267/// where the user likely meant to modify the value behind there reference, use `a` as an out
268/// parameter, instead of mutating the local binding. When encountering this we suggest:
269///
270/// ```text
271/// fn change_object(a: &'_ mut Ty) {
272///     let a = Ty::new();
273///     *b = a;
274/// }
275/// ```
276fn 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    // Verify we have a mutable argument...
287    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    // ... with reference type...
292    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    // ... as a binding pattern.
299    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    // Find the assignment to modify.
306    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    // Changes to the parameter's type.
314    let pre = if lt.ident.span.is_empty() { "" } else { " " };
315    let ty_span = if mut_ty.mutbl.is_mut() {
316        // Leave `&'name mut Ty` and `&mut Ty` as they are (#136028).
317        None
318    } else {
319        // `&'name Ty` -> `&'name mut Ty` or `&Ty` -> `&mut Ty`
320        Some(mut_ty.ty.span.shrink_to_lo())
321    };
322
323    return Some(errors::UnusedAssignSuggestion {
324        ty_span,
325        pre,
326        // Span of the `mut` before the binding.
327        ty_ref_span: param.pat.span.until(ident.span),
328        // Where to add a `*`.
329        pre_lhs_span: lhs.span.shrink_to_lo(),
330        // Where to remove the borrow.
331        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
354/// Compute self-assignments of the form `a += b`.
355///
356/// MIR building generates 2 statements and 1 terminator for such assignments:
357/// - _temp = CheckedBinaryOp(a, b)
358/// - assert(!_temp.1)
359/// - a = _temp.0
360///
361/// This function tries to detect this pattern in order to avoid marking statement as a definition
362/// and use. This will let the analysis be dictated by the next use of `a`.
363///
364/// Note that we will still need to account for the use of `b`.
365fn 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                // For checked binary ops, the MIR builder inserts an assertion in between.
379                Rvalue::BinaryOp(
380                    BinOp::AddWithOverflow | BinOp::SubWithOverflow | BinOp::MulWithOverflow,
381                    box (Operand::Copy(lhs), _),
382                ) => {
383                    // Checked binary ops only appear at the end of the block, before the assertion.
384                    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                    // We ignore indirect self-assignment, because both occurrences of `dest` are uses.
419                    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                        // Original block
431                        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                        // Target block
440                        self_assign.insert(Location { block: *target, statement_index: 0 });
441                    }
442                }
443                // Straight self-assignment.
444                Rvalue::BinaryOp(op, box (Operand::Copy(lhs), _)) => {
445                    if lhs != first_place {
446                        continue;
447                    }
448
449                    // We ignore indirect self-assignment, because both occurrences of `dest` are uses.
450                    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                    // Checked division verifies overflow before performing the division, so we
460                    // need to go and ignore this check in the predecessor block.
461                    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                        // BitAnd of two checks.
471                        self_assign.insert(Location { block: pred, statement_index: len - 1 });
472                        // `lhs == MIN`.
473                        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    /// Places corresponding to locals, common case.
490    locals: IndexVec<Local, Option<PlaceIndex>>,
491
492    // Handling of captures.
493    /// If `_1` is a reference, we need to add a `Deref` to the matched place.
494    capture_field_pos: usize,
495    /// Captured fields.
496    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            // Record all user-written locals for the analysis.
504            // We also keep the `RefForGuard` locals (more on that below).
505            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        // We should not track the environment local separately.
524        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            // Record a variable name from the capture, because it is much friendlier than the
548            // debuginfo name.
549            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        // Discard places that will not result in a diagnostic.
572        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                // Account for an extra Deref.
599                start += 1;
600            }
601            // We may have an attempt to access `_1.f` as a shallow reborrow. Just ignore it.
602            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    /// Set of locals that are live at least once. This is used to report fully unused locals.
625    ever_live: DenseBitSet<PlaceIndex>,
626    /// Set of locals that have a non-trivial drop. This is used to skip reporting unused
627    /// assignment if it would be used by the `Drop` impl.
628    ever_dropped: DenseBitSet<PlaceIndex>,
629    /// Set of assignments for each local. Here, assignment is understood in the AST sense. Any
630    /// MIR that may look like an assignment (Assign, DropAndReplace, Yield, Call) are considered.
631    ///
632    /// For each local, we return a map: for each source position, whether the statement is live
633    /// and which kind of access it performs. When we encounter multiple statements at the same
634    /// location, we only increase the liveness, in order to avoid false positives.
635    assignments: IndexVec<PlaceIndex, FxIndexMap<SourceInfo, Access>>,
636}
637
638impl<'a, 'tcx> AssignmentResult<'a, 'tcx> {
639    /// Collect all assignments to checked locals.
640    ///
641    /// Assignments are collected, even if they are live. Dead assignments are reported, and live
642    /// assignments are used to make diagnostics correct for match guards.
643    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                                // There were already a sighting. Mark this statement as live if it
669                                // was, to avoid false positives.
670                                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        // Check liveness of function arguments on entry.
734        {
735            cursor.seek_to_block_start(START_BLOCK);
736            let live = cursor.get();
737            ever_live.union(live);
738
739            // Verify that arguments and captured values are useful.
740            for (index, place) in checked_places.iter() {
741                let kind = if is_capture(*place) {
742                    // This is a by-ref capture, an assignment to it will modify surrounding
743                    // environment, so we do not report it.
744                    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    /// Match guards introduce a different local to freeze the guarded value as immutable.
772    /// Having two locals, we need to make sure that we do not report an unused_variable
773    /// when the guard local is used but not the arm local, or vice versa, like in this example.
774    ///
775    ///    match 5 {
776    ///      x if x > 2 => {}
777    ///      ^    ^- This is `local`
778    ///      +------ This is `arm_local`
779    ///      _ => {}
780    ///    }
781    ///
782    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                // Local to use in the arm.
791                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                // Mark the arm local as used if the guard local is used.
798                if self.ever_live.contains(index) {
799                    self.ever_live.insert(arm_index);
800                }
801
802                // Some assignments are common to both locals in the source code.
803                // Sadly, we can only detect this using the `source_info`.
804                // Therefore, we loop over all the assignments we have for the guard local:
805                // - if they already appeared for the arm local, the assignment is live if one of the
806                //   two versions is live;
807                // - if it does not appear for the arm local, it happened inside the guard, so we add
808                //   it as-is.
809                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    /// Compute captures that are fully dead.
826    fn compute_dead_captures(&self, num_captures: usize) -> DenseBitSet<FieldIdx> {
827        // Report to caller the set of dead captures.
828        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            // This is a capture: pass information to the enclosing function.
835            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    /// Check if a local is referenced in any reachable basic block.
850    /// Variables in unreachable code (e.g., after `todo!()`) should not trigger unused warnings.
851    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    /// Report fully unused locals, and forget the corresponding assignments.
877    fn report_fully_unused(&mut self) {
878        let tcx = self.tcx;
879
880        // Give a diagnostic when any of the string constants look like a naked format string that
881        // would interpolate our dead local.
882        let mut string_constants_in_body = None;
883        let mut maybe_suggest_literal_matching_name = |name: Symbol| {
884            // Visiting MIR to enumerate string constants can be expensive, so cache the result.
885            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        // First, report fully unused locals.
919        for (index, place) in self.checked_places.iter() {
920            if self.ever_live.contains(index) {
921                continue;
922            }
923
924            // this is a capture: let the enclosing function report the unused variable.
925            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            // Only report actual user-defined binding from now on.
937            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            // #117284, when `ident_span` and `def_span` have different contexts
947            // we can't provide a good suggestion, instead we pointed out the spans from macro
948            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            // Idiomatic rust assigns a value to a local upon definition. However, we do not want to
991            // warn twice, for the unused local and for the unused assignment. Therefore, we remove
992            // from the list of assignments the ones that happen at the definition site.
993            statements.retain(|source_info, _| {
994                source_info.span.find_ancestor_inside(binding.pat_span).is_none()
995            });
996
997            // Extra assignments that we recognize thanks to the initialization span. We need to
998            // take care of macro contexts here to be accurate.
999            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                // We have a dead local with outstanding assignments and with non-trivial drop.
1011                // This is probably a drop-guard, so we do not issue a warning there.
1012                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            // We do not have outstanding assignments, suggest renaming the binding.
1037            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    /// Second, report unused assignments that do not correspond to initialization.
1083    /// Initializations have been removed in the previous loop reporting unused variables.
1084    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            // We probed MIR in reverse order for dataflow.
1104            // We revert the vector to give a consistent order to the user.
1105            for (source_info, Access { live, kind, is_direct }) in statements.into_iter().rev() {
1106                if live {
1107                    continue;
1108                }
1109
1110                // If this place was dropped and has non-trivial drop,
1111                // skip reporting field assignments.
1112                if !is_direct && is_maybe_drop_guard {
1113                    continue;
1114                }
1115
1116                // Report the dead assignment.
1117                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        // bottom = not live
1199        DenseBitSet::new_empty(self.checked_places.len())
1200    }
1201
1202    fn initialize_start_block(&self, _: &Body<'tcx>, _: &mut Self::Domain) {
1203        // No variables are live until we observe a use
1204    }
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        // FIXME: what should happen here?
1232    }
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            // `ForLet(None)` fake read erroneously marks the just-assigned local as live.
1247            // This defeats the purpose of the analysis for `let` bindings.
1248            StatementKind::FakeRead(box (FakeReadCause::ForLet(None), _)) => return,
1249            // Handle self-assignment by restricting the read/write they do.
1250            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                    // We are computing the binary operation:
1259                    // - the LHS will be assigned, so we don't read it;
1260                    // - the RHS still needs to be read.
1261                    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                    // We are computing the binary operation:
1269                    // - the LHS is being updated, so we don't read it;
1270                    // - the RHS still needs to be read.
1271                    self.visit_operand(rhs, location);
1272                } else {
1273                    // This is the second part of a checked self-assignment,
1274                    // we are assigning the result.
1275                    // We do not consider the write to the destination as a `def`.
1276                    // `self_assignment` must be false if the assignment is indirect.
1277                    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        // By-ref captures could be read by the surrounding environment, so we mark
1286        // them as live upon yield and return.
1287        match terminator.kind {
1288            TerminatorKind::Return
1289            | TerminatorKind::Yield { .. }
1290            | TerminatorKind::Goto { target: START_BLOCK } // Inserted for the `FnMut` case.
1291                if self.capture_kind != CaptureKind::None =>
1292            {
1293                // All indirect captures have an effect on the environment, so we mark them as live.
1294                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            // Do not consider a drop to be a use. We whitelist interesting drops elsewhere.
1303            TerminatorKind::Drop { .. } => {}
1304            // Ignore assertions since they must be triggered by actual code.
1305            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            // When a closure/generator does not use some of its captures, do not consider these
1313            // captures as live in the surrounding function. This allows to report unused variables,
1314            // even if they have been (uselessly) captured.
1315            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                    // Treat derefs as a use of the base local. `*p = 4` is not a def of `p` but a
1395                    // use.
1396                    Some(DefUse::Use)
1397                } else if projection.is_empty() {
1398                    Some(DefUse::Def)
1399                } else {
1400                    None
1401                }
1402            }
1403
1404            // For the associated terminators, this is only a `Def` when the terminator returns
1405            // "successfully." As such, we handle this case separately in `call_return_effect`
1406            // above. However, if the place looks like `*_5`, this is still unconditionally a use of
1407            // `_5`.
1408            PlaceContext::MutatingUse(
1409                MutatingUseContext::Call
1410                | MutatingUseContext::Yield
1411                | MutatingUseContext::AsmOutput,
1412            ) => is_indirect.then_some(DefUse::Use),
1413
1414            // All other contexts are uses...
1415            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}