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    let mut body = &*tcx.mir_promoted(def_id).0.borrow();
79    let mut body_mem;
80
81    // Don't run if there are errors.
82    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    // The body is the one of a closure or generator, so we also want to analyse captures.
90    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        // `FnMut` closures can modify captured values and carry those
113        // modified values with them in subsequent calls. To model this behaviour,
114        // we consider the `FnMut` closure as jumping to `bb0` upon return.
115        if let CaptureKind::Closure(ty::ClosureKind::FnMut) = capture_kind {
116            // FIXME: stop cloning the body.
117            body_mem = body.clone();
118            for bbdata in body_mem.basic_blocks_mut() {
119                // We can call a closure again, either after a normal return or an unwind.
120                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    // Get the remaining variables' names from debuginfo.
135    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/// Small helper to make semantics easier to read.
160#[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
170/// Give a diagnostic when any of the string constants look like a naked format string that would
171/// interpolate our dead local.
172fn 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
209/// Give a diagnostic when an unused variable may be a typo of a unit variant or a struct.
210fn 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    // Look for consts of the same type with similar names as well,
240    // not just unit structs and variants.
241    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
265/// Return whether we should consider the current place as a drop guard and skip reporting.
266fn 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
292/// Detect the following case
293///
294/// ```text
295/// fn change_object(mut a: &Ty) {
296///     let a = Ty::new();
297///     b = &a;
298/// }
299/// ```
300///
301/// where the user likely meant to modify the value behind there reference, use `a` as an out
302/// parameter, instead of mutating the local binding. When encountering this we suggest:
303///
304/// ```text
305/// fn change_object(a: &'_ mut Ty) {
306///     let a = Ty::new();
307///     *b = a;
308/// }
309/// ```
310fn 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    // Verify we have a mutable argument...
321    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    // ... with reference type...
326    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    // ... as a binding pattern.
333    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    // Find the assignment to modify.
340    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    // Changes to the parameter's type.
348    let pre = if lt.ident.span.is_empty() { "" } else { " " };
349    let ty_span = if mut_ty.mutbl.is_mut() {
350        // Leave `&'name mut Ty` and `&mut Ty` as they are (#136028).
351        None
352    } else {
353        // `&'name Ty` -> `&'name mut Ty` or `&Ty` -> `&mut Ty`
354        Some(mut_ty.ty.span.shrink_to_lo())
355    };
356
357    return Some(errors::UnusedAssignSuggestion {
358        ty_span,
359        pre,
360        // Span of the `mut` before the binding.
361        ty_ref_span: param.pat.span.until(ident.span),
362        // Where to add a `*`.
363        pre_lhs_span: lhs.span.shrink_to_lo(),
364        // Where to remove the borrow.
365        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
388/// Compute self-assignments of the form `a += b`.
389///
390/// MIR building generates 2 statements and 1 terminator for such assignments:
391/// - _temp = CheckedBinaryOp(a, b)
392/// - assert(!_temp.1)
393/// - a = _temp.0
394///
395/// This function tries to detect this pattern in order to avoid marking statement as a definition
396/// and use. This will let the analysis be dictated by the next use of `a`.
397///
398/// Note that we will still need to account for the use of `b`.
399fn 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                // For checked binary ops, the MIR builder inserts an assertion in between.
413                Rvalue::BinaryOp(
414                    BinOp::AddWithOverflow | BinOp::SubWithOverflow | BinOp::MulWithOverflow,
415                    box (Operand::Copy(lhs), _),
416                ) => {
417                    // Checked binary ops only appear at the end of the block, before the assertion.
418                    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                    // We ignore indirect self-assignment, because both occurrences of `dest` are uses.
453                    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                        // Original block
465                        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                        // Target block
474                        self_assign.insert(Location { block: *target, statement_index: 0 });
475                    }
476                }
477                // Straight self-assignment.
478                Rvalue::BinaryOp(op, box (Operand::Copy(lhs), _)) => {
479                    if lhs != first_place {
480                        continue;
481                    }
482
483                    // We ignore indirect self-assignment, because both occurrences of `dest` are uses.
484                    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                    // Checked division verifies overflow before performing the division, so we
494                    // need to go and ignore this check in the predecessor block.
495                    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                        // BitAnd of two checks.
505                        self_assign.insert(Location { block: pred, statement_index: len - 1 });
506                        // `lhs == MIN`.
507                        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    /// Places corresponding to locals, common case.
524    locals: IndexVec<Local, Option<PlaceIndex>>,
525
526    // Handling of captures.
527    /// If `_1` is a reference, we need to add a `Deref` to the matched place.
528    capture_field_pos: usize,
529    /// Captured fields.
530    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            // Record all user-written locals for the analysis.
538            // We also keep the `RefForGuard` locals (more on that below).
539            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        // We should not track the environment local separately.
558        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            // Record a variable name from the capture, because it is much friendlier than the
582            // debuginfo name.
583            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        // Discard places that will not result in a diagnostic.
606        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                // Account for an extra Deref.
633                start += 1;
634            }
635            // We may have an attempt to access `_1.f` as a shallow reborrow. Just ignore it.
636            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    /// Set of locals that are live at least once. This is used to report fully unused locals.
659    ever_live: DenseBitSet<PlaceIndex>,
660    /// Set of locals that have a non-trivial drop. This is used to skip reporting unused
661    /// assignment if it would be used by the `Drop` impl.
662    ever_dropped: DenseBitSet<PlaceIndex>,
663    /// Set of assignments for each local. Here, assignment is understood in the AST sense. Any
664    /// MIR that may look like an assignment (Assign, DropAndReplace, Yield, Call) are considered.
665    ///
666    /// For each local, we return a map: for each source position, whether the statement is live
667    /// and which kind of access it performs. When we encounter multiple statements at the same
668    /// location, we only increase the liveness, in order to avoid false positives.
669    assignments: IndexVec<PlaceIndex, FxIndexMap<SourceInfo, Access>>,
670}
671
672impl<'a, 'tcx> AssignmentResult<'a, 'tcx> {
673    /// Collect all assignments to checked locals.
674    ///
675    /// Assignments are collected, even if they are live. Dead assignments are reported, and live
676    /// assignments are used to make diagnostics correct for match guards.
677    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                                // There were already a sighting. Mark this statement as live if it
703                                // was, to avoid false positives.
704                                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        // Check liveness of function arguments on entry.
768        {
769            cursor.seek_to_block_start(START_BLOCK);
770            let live = cursor.get();
771            ever_live.union(live);
772
773            // Verify that arguments and captured values are useful.
774            for (index, place) in checked_places.iter() {
775                let kind = if is_capture(*place) {
776                    // This is a by-ref capture, an assignment to it will modify surrounding
777                    // environment, so we do not report it.
778                    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    /// Match guards introduce a different local to freeze the guarded value as immutable.
806    /// Having two locals, we need to make sure that we do not report an unused_variable
807    /// when the guard local is used but not the arm local, or vice versa, like in this example.
808    ///
809    ///    match 5 {
810    ///      x if x > 2 => {}
811    ///      ^    ^- This is `local`
812    ///      +------ This is `arm_local`
813    ///      _ => {}
814    ///    }
815    ///
816    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                // Local to use in the arm.
825                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                // Mark the arm local as used if the guard local is used.
832                if self.ever_live.contains(index) {
833                    self.ever_live.insert(arm_index);
834                }
835
836                // Some assignments are common to both locals in the source code.
837                // Sadly, we can only detect this using the `source_info`.
838                // Therefore, we loop over all the assignments we have for the guard local:
839                // - if they already appeared for the arm local, the assignment is live if one of the
840                //   two versions is live;
841                // - if it does not appear for the arm local, it happened inside the guard, so we add
842                //   it as-is.
843                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    /// Compute captures that are fully dead.
860    fn compute_dead_captures(&self, num_captures: usize) -> DenseBitSet<FieldIdx> {
861        // Report to caller the set of dead captures.
862        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            // This is a capture: pass information to the enclosing function.
869            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    /// Check if a local is referenced in any reachable basic block.
884    /// Variables in unreachable code (e.g., after `todo!()`) should not trigger unused warnings.
885    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    /// Report fully unused locals, and forget the corresponding assignments.
911    fn report_fully_unused(&mut self) {
912        let tcx = self.tcx;
913
914        // First, report fully unused locals.
915        for (index, place) in self.checked_places.iter() {
916            if self.ever_live.contains(index) {
917                continue;
918            }
919
920            // this is a capture: let the enclosing function report the unused variable.
921            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            // Only report actual user-defined binding from now on.
933            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            // #117284, when `ident_span` and `def_span` have different contexts
943            // we can't provide a good suggestion, instead we pointed out the spans from macro
944            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            // Idiomatic rust assigns a value to a local upon definition. However, we do not want to
987            // warn twice, for the unused local and for the unused assignment. Therefore, we remove
988            // from the list of assignments the ones that happen at the definition site.
989            statements.retain(|source_info, _| {
990                source_info.span.find_ancestor_inside(binding.pat_span).is_none()
991            });
992
993            // Extra assignments that we recognize thanks to the initialization span. We need to
994            // take care of macro contexts here to be accurate.
995            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                // We have a dead local with outstanding assignments and with non-trivial drop.
1007                // This is probably a drop-guard, so we do not issue a warning there.
1008                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            // We do not have outstanding assignments, suggest renaming the binding.
1033            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    /// Second, report unused assignments that do not correspond to initialization.
1079    /// Initializations have been removed in the previous loop reporting unused variables.
1080    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            // We probed MIR in reverse order for dataflow.
1100            // We revert the vector to give a consistent order to the user.
1101            for (source_info, Access { live, kind, is_direct }) in statements.into_iter().rev() {
1102                if live {
1103                    continue;
1104                }
1105
1106                // If this place was dropped and has non-trivial drop,
1107                // skip reporting field assignments.
1108                if !is_direct && is_maybe_drop_guard {
1109                    continue;
1110                }
1111
1112                // Report the dead assignment.
1113                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        // bottom = not live
1195        DenseBitSet::new_empty(self.checked_places.len())
1196    }
1197
1198    fn initialize_start_block(&self, _: &Body<'tcx>, _: &mut Self::Domain) {
1199        // No variables are live until we observe a use
1200    }
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        // FIXME: what should happen here?
1228    }
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            // `ForLet(None)` fake read erroneously marks the just-assigned local as live.
1243            // This defeats the purpose of the analysis for `let` bindings.
1244            StatementKind::FakeRead(box (FakeReadCause::ForLet(None), _)) => return,
1245            // Handle self-assignment by restricting the read/write they do.
1246            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                    // We are computing the binary operation:
1255                    // - the LHS will be assigned, so we don't read it;
1256                    // - the RHS still needs to be read.
1257                    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                    // We are computing the binary operation:
1265                    // - the LHS is being updated, so we don't read it;
1266                    // - the RHS still needs to be read.
1267                    self.visit_operand(rhs, location);
1268                } else {
1269                    // This is the second part of a checked self-assignment,
1270                    // we are assigning the result.
1271                    // We do not consider the write to the destination as a `def`.
1272                    // `self_assignment` must be false if the assignment is indirect.
1273                    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        // By-ref captures could be read by the surrounding environment, so we mark
1282        // them as live upon yield and return.
1283        match terminator.kind {
1284            TerminatorKind::Return
1285            | TerminatorKind::Yield { .. }
1286            | TerminatorKind::Goto { target: START_BLOCK } // Inserted for the `FnMut` case.
1287                if self.capture_kind != CaptureKind::None =>
1288            {
1289                // All indirect captures have an effect on the environment, so we mark them as live.
1290                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            // Do not consider a drop to be a use. We whitelist interesting drops elsewhere.
1299            TerminatorKind::Drop { .. } => {}
1300            // Ignore assertions since they must be triggered by actual code.
1301            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            // When a closure/generator does not use some of its captures, do not consider these
1309            // captures as live in the surrounding function. This allows to report unused variables,
1310            // even if they have been (uselessly) captured.
1311            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                    // Treat derefs as a use of the base local. `*p = 4` is not a def of `p` but a
1391                    // use.
1392                    Some(DefUse::Use)
1393                } else if projection.is_empty() {
1394                    Some(DefUse::Def)
1395                } else {
1396                    None
1397                }
1398            }
1399
1400            // For the associated terminators, this is only a `Def` when the terminator returns
1401            // "successfully." As such, we handle this case separately in `call_return_effect`
1402            // above. However, if the place looks like `*_5`, this is still unconditionally a use of
1403            // `_5`.
1404            PlaceContext::MutatingUse(
1405                MutatingUseContext::Call
1406                | MutatingUseContext::Yield
1407                | MutatingUseContext::AsmOutput,
1408            ) => is_indirect.then_some(DefUse::Use),
1409
1410            // All other contexts are uses...
1411            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}