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}
49
50#[tracing::instrument(level = "debug", skip(tcx), ret)]
51pub(crate) fn check_liveness<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> DenseBitSet<FieldIdx> {
52    // Don't run on synthetic MIR, as that will ICE trying to access HIR.
53    if tcx.is_synthetic_mir(def_id) {
54        return DenseBitSet::new_empty(0);
55    }
56
57    // Don't run unused pass for intrinsics
58    if tcx.intrinsic(def_id.to_def_id()).is_some() {
59        return DenseBitSet::new_empty(0);
60    }
61
62    // Don't run unused pass for #[naked]
63    if find_attr!(tcx.get_all_attrs(def_id.to_def_id()), AttributeKind::Naked(..)) {
64        return DenseBitSet::new_empty(0);
65    }
66
67    // Don't run unused pass for #[derive]
68    let parent = tcx.parent(tcx.typeck_root_def_id(def_id.to_def_id()));
69    if let DefKind::Impl { of_trait: true } = tcx.def_kind(parent)
70        && find_attr!(tcx.get_all_attrs(parent), AttributeKind::AutomaticallyDerived(..))
71    {
72        return DenseBitSet::new_empty(0);
73    }
74
75    let mut body = &*tcx.mir_promoted(def_id).0.borrow();
76    let mut body_mem;
77
78    // Don't run if there are errors.
79    if body.tainted_by_errors.is_some() {
80        return DenseBitSet::new_empty(0);
81    }
82
83    let mut checked_places = PlaceSet::default();
84    checked_places.insert_locals(&body.local_decls);
85
86    // The body is the one of a closure or generator, so we also want to analyse captures.
87    let (capture_kind, num_captures) = if tcx.is_closure_like(def_id.to_def_id()) {
88        let mut self_ty = body.local_decls[ty::CAPTURE_STRUCT_LOCAL].ty;
89        let mut self_is_ref = false;
90        if let ty::Ref(_, ty, _) = self_ty.kind() {
91            self_ty = *ty;
92            self_is_ref = true;
93        }
94
95        let (capture_kind, args) = match self_ty.kind() {
96            ty::Closure(_, args) => {
97                (CaptureKind::Closure(args.as_closure().kind()), ty::UpvarArgs::Closure(args))
98            }
99            &ty::Coroutine(_, args) => (CaptureKind::Coroutine, ty::UpvarArgs::Coroutine(args)),
100            &ty::CoroutineClosure(_, args) => {
101                (CaptureKind::CoroutineClosure, ty::UpvarArgs::CoroutineClosure(args))
102            }
103            _ => bug!("expected closure or generator, found {:?}", self_ty),
104        };
105
106        let captures = tcx.closure_captures(def_id);
107        checked_places.insert_captures(tcx, self_is_ref, captures, args.upvar_tys());
108
109        // `FnMut` closures can modify captured values and carry those
110        // modified values with them in subsequent calls. To model this behaviour,
111        // we consider the `FnMut` closure as jumping to `bb0` upon return.
112        if let CaptureKind::Closure(ty::ClosureKind::FnMut) = capture_kind {
113            // FIXME: stop cloning the body.
114            body_mem = body.clone();
115            for bbdata in body_mem.basic_blocks_mut() {
116                // We can call a closure again, either after a normal return or an unwind.
117                if let TerminatorKind::Return | TerminatorKind::UnwindResume =
118                    bbdata.terminator().kind
119                {
120                    bbdata.terminator_mut().kind = TerminatorKind::Goto { target: START_BLOCK };
121                }
122            }
123            body = &body_mem;
124        }
125
126        (capture_kind, args.upvar_tys().len())
127    } else {
128        (CaptureKind::None, 0)
129    };
130
131    // Get the remaining variables' names from debuginfo.
132    checked_places.record_debuginfo(&body.var_debug_info);
133
134    let self_assignment = find_self_assignments(&checked_places, body);
135
136    let mut live =
137        MaybeLivePlaces { tcx, capture_kind, checked_places: &checked_places, self_assignment }
138            .iterate_to_fixpoint(tcx, body, None)
139            .into_results_cursor(body);
140
141    let typing_env = ty::TypingEnv::post_analysis(tcx, body.source.def_id());
142
143    let mut assignments =
144        AssignmentResult::find_dead_assignments(tcx, typing_env, &checked_places, &mut live, body);
145
146    assignments.merge_guards();
147
148    let dead_captures = assignments.compute_dead_captures(num_captures);
149
150    assignments.report_fully_unused();
151    assignments.report_unused_assignments();
152
153    dead_captures
154}
155
156/// Small helper to make semantics easier to read.
157#[inline]
158fn is_capture(place: PlaceRef<'_>) -> bool {
159    if !place.projection.is_empty() {
160        debug_assert_eq!(place.local, ty::CAPTURE_STRUCT_LOCAL);
161        true
162    } else {
163        false
164    }
165}
166
167/// Give a diagnostic when any of the string constants look like a naked format string that would
168/// interpolate our dead local.
169fn maybe_suggest_literal_matching_name(
170    body: &Body<'_>,
171    name: Symbol,
172) -> Vec<errors::UnusedVariableStringInterp> {
173    struct LiteralFinder<'body, 'tcx> {
174        body: &'body Body<'tcx>,
175        name: String,
176        name_colon: String,
177        found: Vec<errors::UnusedVariableStringInterp>,
178    }
179
180    impl<'tcx> Visitor<'tcx> for LiteralFinder<'_, 'tcx> {
181        fn visit_const_operand(&mut self, constant: &ConstOperand<'tcx>, loc: Location) {
182            if let ty::Ref(_, ref_ty, _) = constant.ty().kind()
183                && ref_ty.kind() == &ty::Str
184            {
185                let rendered_constant = constant.const_.to_string();
186                if rendered_constant.contains(&self.name)
187                    || rendered_constant.contains(&self.name_colon)
188                {
189                    let lit = self.body.source_info(loc).span;
190                    self.found.push(errors::UnusedVariableStringInterp { lit });
191                }
192            }
193        }
194    }
195
196    let mut finder = LiteralFinder {
197        body,
198        name: format!("{{{name}}}"),
199        name_colon: format!("{{{name}:"),
200        found: vec![],
201    };
202    finder.visit_body(body);
203    finder.found
204}
205
206/// Give a diagnostic when an unused variable may be a typo of a unit variant or a struct.
207fn maybe_suggest_unit_pattern_typo<'tcx>(
208    tcx: TyCtxt<'tcx>,
209    body_def_id: DefId,
210    name: Symbol,
211    span: Span,
212    ty: Ty<'tcx>,
213) -> Option<errors::PatternTypo> {
214    if let ty::Adt(adt_def, _) = ty.peel_refs().kind() {
215        let variant_names: Vec<_> = adt_def
216            .variants()
217            .iter()
218            .filter(|v| matches!(v.ctor, Some((CtorKind::Const, _))))
219            .map(|v| v.name)
220            .collect();
221        if let Some(name) = find_best_match_for_name(&variant_names, name, None)
222            && let Some(variant) = adt_def
223                .variants()
224                .iter()
225                .find(|v| v.name == name && matches!(v.ctor, Some((CtorKind::Const, _))))
226        {
227            return Some(errors::PatternTypo {
228                span,
229                code: with_no_trimmed_paths!(tcx.def_path_str(variant.def_id)),
230                kind: tcx.def_descr(variant.def_id),
231                item_name: variant.name,
232            });
233        }
234    }
235
236    // Look for consts of the same type with similar names as well,
237    // not just unit structs and variants.
238    let constants = tcx
239        .hir_body_owners()
240        .filter(|&def_id| {
241            matches!(tcx.def_kind(def_id), DefKind::Const)
242                && tcx.type_of(def_id).instantiate_identity() == ty
243                && tcx.visibility(def_id).is_accessible_from(body_def_id, tcx)
244        })
245        .collect::<Vec<_>>();
246    let names = constants.iter().map(|&def_id| tcx.item_name(def_id)).collect::<Vec<_>>();
247    if let Some(item_name) = find_best_match_for_name(&names, name, None)
248        && let Some(position) = names.iter().position(|&n| n == item_name)
249        && let Some(&def_id) = constants.get(position)
250    {
251        return Some(errors::PatternTypo {
252            span,
253            code: with_no_trimmed_paths!(tcx.def_path_str(def_id)),
254            kind: "constant",
255            item_name,
256        });
257    }
258
259    None
260}
261
262/// Return whether we should consider the current place as a drop guard and skip reporting.
263fn maybe_drop_guard<'tcx>(
264    tcx: TyCtxt<'tcx>,
265    typing_env: ty::TypingEnv<'tcx>,
266    index: PlaceIndex,
267    ever_dropped: &DenseBitSet<PlaceIndex>,
268    checked_places: &PlaceSet<'tcx>,
269    body: &Body<'tcx>,
270) -> bool {
271    if ever_dropped.contains(index) {
272        let ty = checked_places.places[index].ty(&body.local_decls, tcx).ty;
273        matches!(
274            ty.kind(),
275            ty::Closure(..)
276                | ty::Coroutine(..)
277                | ty::Tuple(..)
278                | ty::Adt(..)
279                | ty::Dynamic(..)
280                | ty::Array(..)
281                | ty::Slice(..)
282                | ty::Alias(ty::Opaque, ..)
283        ) && ty.needs_drop(tcx, typing_env)
284    } else {
285        false
286    }
287}
288
289/// Detect the following case
290///
291/// ```text
292/// fn change_object(mut a: &Ty) {
293///     let a = Ty::new();
294///     b = &a;
295/// }
296/// ```
297///
298/// where the user likely meant to modify the value behind there reference, use `a` as an out
299/// parameter, instead of mutating the local binding. When encountering this we suggest:
300///
301/// ```text
302/// fn change_object(a: &'_ mut Ty) {
303///     let a = Ty::new();
304///     *b = a;
305/// }
306/// ```
307fn annotate_mut_binding_to_immutable_binding<'tcx>(
308    tcx: TyCtxt<'tcx>,
309    place: PlaceRef<'tcx>,
310    body_def_id: LocalDefId,
311    assignment_span: Span,
312    body: &Body<'tcx>,
313) -> Option<errors::UnusedAssignSuggestion> {
314    use rustc_hir as hir;
315    use rustc_hir::intravisit::{self, Visitor};
316
317    // Verify we have a mutable argument...
318    let local = place.as_local()?;
319    let LocalKind::Arg = body.local_kind(local) else { return None };
320    let Mutability::Mut = body.local_decls[local].mutability else { return None };
321
322    // ... with reference type...
323    let hir_param_index =
324        local.as_usize() - if tcx.is_closure_like(body_def_id.to_def_id()) { 2 } else { 1 };
325    let fn_decl = tcx.hir_node_by_def_id(body_def_id).fn_decl()?;
326    let ty = fn_decl.inputs[hir_param_index];
327    let hir::TyKind::Ref(lt, mut_ty) = ty.kind else { return None };
328
329    // ... as a binding pattern.
330    let hir_body = tcx.hir_maybe_body_owned_by(body_def_id)?;
331    let param = hir_body.params[hir_param_index];
332    let hir::PatKind::Binding(hir::BindingMode::MUT, _hir_id, ident, _) = param.pat.kind else {
333        return None;
334    };
335
336    // Find the assignment to modify.
337    let mut finder = ExprFinder { assignment_span, lhs: None, rhs: None };
338    finder.visit_body(hir_body);
339    let lhs = finder.lhs?;
340    let rhs = finder.rhs?;
341
342    let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _mut, inner) = rhs.kind else { return None };
343
344    // Changes to the parameter's type.
345    let pre = if lt.ident.span.is_empty() { "" } else { " " };
346    let ty_span = if mut_ty.mutbl.is_mut() {
347        // Leave `&'name mut Ty` and `&mut Ty` as they are (#136028).
348        None
349    } else {
350        // `&'name Ty` -> `&'name mut Ty` or `&Ty` -> `&mut Ty`
351        Some(mut_ty.ty.span.shrink_to_lo())
352    };
353
354    return Some(errors::UnusedAssignSuggestion {
355        ty_span,
356        pre,
357        // Span of the `mut` before the binding.
358        ty_ref_span: param.pat.span.until(ident.span),
359        // Where to add a `*`.
360        pre_lhs_span: lhs.span.shrink_to_lo(),
361        // Where to remove the borrow.
362        rhs_borrow_span: rhs.span.until(inner.span),
363    });
364
365    #[derive(Debug)]
366    struct ExprFinder<'hir> {
367        assignment_span: Span,
368        lhs: Option<&'hir hir::Expr<'hir>>,
369        rhs: Option<&'hir hir::Expr<'hir>>,
370    }
371    impl<'hir> Visitor<'hir> for ExprFinder<'hir> {
372        fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
373            if expr.span == self.assignment_span
374                && let hir::ExprKind::Assign(lhs, rhs, _) = expr.kind
375            {
376                self.lhs = Some(lhs);
377                self.rhs = Some(rhs);
378            } else {
379                intravisit::walk_expr(self, expr)
380            }
381        }
382    }
383}
384
385/// Compute self-assignments of the form `a += b`.
386///
387/// MIR building generates 2 statements and 1 terminator for such assignments:
388/// - _temp = CheckedBinaryOp(a, b)
389/// - assert(!_temp.1)
390/// - a = _temp.0
391///
392/// This function tries to detect this pattern in order to avoid marking statement as a definition
393/// and use. This will let the analysis be dictated by the next use of `a`.
394///
395/// Note that we will still need to account for the use of `b`.
396fn find_self_assignments<'tcx>(
397    checked_places: &PlaceSet<'tcx>,
398    body: &Body<'tcx>,
399) -> FxHashSet<Location> {
400    let mut self_assign = FxHashSet::default();
401
402    const FIELD_0: FieldIdx = FieldIdx::from_u32(0);
403    const FIELD_1: FieldIdx = FieldIdx::from_u32(1);
404
405    for (bb, bb_data) in body.basic_blocks.iter_enumerated() {
406        for (statement_index, stmt) in bb_data.statements.iter().enumerate() {
407            let StatementKind::Assign(box (first_place, rvalue)) = &stmt.kind else { continue };
408            match rvalue {
409                // For checked binary ops, the MIR builder inserts an assertion in between.
410                Rvalue::BinaryOp(
411                    BinOp::AddWithOverflow | BinOp::SubWithOverflow | BinOp::MulWithOverflow,
412                    box (Operand::Copy(lhs), _),
413                ) => {
414                    // Checked binary ops only appear at the end of the block, before the assertion.
415                    if statement_index + 1 != bb_data.statements.len() {
416                        continue;
417                    }
418
419                    let TerminatorKind::Assert {
420                        cond,
421                        target,
422                        msg: box AssertKind::Overflow(..),
423                        ..
424                    } = &bb_data.terminator().kind
425                    else {
426                        continue;
427                    };
428                    let Some(assign) = body.basic_blocks[*target].statements.first() else {
429                        continue;
430                    };
431                    let StatementKind::Assign(box (dest, Rvalue::Use(Operand::Move(temp)))) =
432                        assign.kind
433                    else {
434                        continue;
435                    };
436
437                    if dest != *lhs {
438                        continue;
439                    }
440
441                    let Operand::Move(cond) = cond else { continue };
442                    let [PlaceElem::Field(FIELD_0, _)] = &temp.projection.as_slice() else {
443                        continue;
444                    };
445                    let [PlaceElem::Field(FIELD_1, _)] = &cond.projection.as_slice() else {
446                        continue;
447                    };
448
449                    // We ignore indirect self-assignment, because both occurrences of `dest` are uses.
450                    let is_indirect = checked_places
451                        .get(dest.as_ref())
452                        .map_or(false, |(_, projections)| is_indirect(projections));
453                    if is_indirect {
454                        continue;
455                    }
456
457                    if first_place.local == temp.local
458                        && first_place.local == cond.local
459                        && first_place.projection.is_empty()
460                    {
461                        // Original block
462                        self_assign.insert(Location {
463                            block: bb,
464                            statement_index: bb_data.statements.len() - 1,
465                        });
466                        self_assign.insert(Location {
467                            block: bb,
468                            statement_index: bb_data.statements.len(),
469                        });
470                        // Target block
471                        self_assign.insert(Location { block: *target, statement_index: 0 });
472                    }
473                }
474                // Straight self-assignment.
475                Rvalue::BinaryOp(op, box (Operand::Copy(lhs), _)) => {
476                    if lhs != first_place {
477                        continue;
478                    }
479
480                    // We ignore indirect self-assignment, because both occurrences of `dest` are uses.
481                    let is_indirect = checked_places
482                        .get(first_place.as_ref())
483                        .map_or(false, |(_, projections)| is_indirect(projections));
484                    if is_indirect {
485                        continue;
486                    }
487
488                    self_assign.insert(Location { block: bb, statement_index });
489
490                    // Checked division verifies overflow before performing the division, so we
491                    // need to go and ignore this check in the predecessor block.
492                    if let BinOp::Div | BinOp::Rem = op
493                        && statement_index == 0
494                        && let &[pred] = body.basic_blocks.predecessors()[bb].as_slice()
495                        && let TerminatorKind::Assert { msg, .. } =
496                            &body.basic_blocks[pred].terminator().kind
497                        && let AssertKind::Overflow(..) = **msg
498                        && let len = body.basic_blocks[pred].statements.len()
499                        && len >= 2
500                    {
501                        // BitAnd of two checks.
502                        self_assign.insert(Location { block: pred, statement_index: len - 1 });
503                        // `lhs == MIN`.
504                        self_assign.insert(Location { block: pred, statement_index: len - 2 });
505                    }
506                }
507                _ => {}
508            }
509        }
510    }
511
512    self_assign
513}
514
515#[derive(Default, Debug)]
516struct PlaceSet<'tcx> {
517    places: IndexVec<PlaceIndex, PlaceRef<'tcx>>,
518    names: IndexVec<PlaceIndex, Option<(Symbol, Span)>>,
519
520    /// Places corresponding to locals, common case.
521    locals: IndexVec<Local, Option<PlaceIndex>>,
522
523    // Handling of captures.
524    /// If `_1` is a reference, we need to add a `Deref` to the matched place.
525    capture_field_pos: usize,
526    /// Captured fields.
527    captures: IndexVec<FieldIdx, (PlaceIndex, bool)>,
528}
529
530impl<'tcx> PlaceSet<'tcx> {
531    fn insert_locals(&mut self, decls: &IndexVec<Local, LocalDecl<'tcx>>) {
532        self.locals = IndexVec::from_elem(None, &decls);
533        for (local, decl) in decls.iter_enumerated() {
534            // Record all user-written locals for the analysis.
535            // We also keep the `RefForGuard` locals (more on that below).
536            if let LocalInfo::User(BindingForm::Var(_) | BindingForm::RefForGuard(_)) =
537                decl.local_info()
538            {
539                let index = self.places.push(local.into());
540                self.locals[local] = Some(index);
541                let _index = self.names.push(None);
542                debug_assert_eq!(index, _index);
543            }
544        }
545    }
546
547    fn insert_captures(
548        &mut self,
549        tcx: TyCtxt<'tcx>,
550        self_is_ref: bool,
551        captures: &[&'tcx ty::CapturedPlace<'tcx>],
552        upvars: &ty::List<Ty<'tcx>>,
553    ) {
554        // We should not track the environment local separately.
555        debug_assert_eq!(self.locals[ty::CAPTURE_STRUCT_LOCAL], None);
556
557        let self_place = Place {
558            local: ty::CAPTURE_STRUCT_LOCAL,
559            projection: tcx.mk_place_elems(if self_is_ref { &[PlaceElem::Deref] } else { &[] }),
560        };
561        if self_is_ref {
562            self.capture_field_pos = 1;
563        }
564
565        for (f, (capture, ty)) in std::iter::zip(captures, upvars).enumerate() {
566            let f = FieldIdx::from_usize(f);
567            let elem = PlaceElem::Field(f, ty);
568            let by_ref = matches!(capture.info.capture_kind, ty::UpvarCapture::ByRef(..));
569            let place = if by_ref {
570                self_place.project_deeper(&[elem, PlaceElem::Deref], tcx)
571            } else {
572                self_place.project_deeper(&[elem], tcx)
573            };
574            let index = self.places.push(place.as_ref());
575            let _f = self.captures.push((index, by_ref));
576            debug_assert_eq!(_f, f);
577
578            // Record a variable name from the capture, because it is much friendlier than the
579            // debuginfo name.
580            self.names.insert(
581                index,
582                (Symbol::intern(&capture.to_string(tcx)), capture.get_path_span(tcx)),
583            );
584        }
585    }
586
587    fn record_debuginfo(&mut self, var_debug_info: &Vec<VarDebugInfo<'tcx>>) {
588        let ignore_name = |name: Symbol| {
589            name == sym::empty || name == kw::SelfLower || name.as_str().starts_with('_')
590        };
591        for var_debug_info in var_debug_info {
592            if let VarDebugInfoContents::Place(place) = var_debug_info.value
593                && let Some(index) = self.locals[place.local]
594                && !ignore_name(var_debug_info.name)
595            {
596                self.names.get_or_insert_with(index, || {
597                    (var_debug_info.name, var_debug_info.source_info.span)
598                });
599            }
600        }
601
602        // Discard places that will not result in a diagnostic.
603        for index_opt in self.locals.iter_mut() {
604            if let Some(index) = *index_opt {
605                let remove = match self.names[index] {
606                    None => true,
607                    Some((name, _)) => ignore_name(name),
608                };
609                if remove {
610                    *index_opt = None;
611                }
612            }
613        }
614    }
615
616    #[inline]
617    fn get(&self, place: PlaceRef<'tcx>) -> Option<(PlaceIndex, &'tcx [PlaceElem<'tcx>])> {
618        if let Some(index) = self.locals[place.local] {
619            return Some((index, place.projection));
620        }
621        if place.local == ty::CAPTURE_STRUCT_LOCAL
622            && !self.captures.is_empty()
623            && self.capture_field_pos < place.projection.len()
624            && let PlaceElem::Field(f, _) = place.projection[self.capture_field_pos]
625            && let Some((index, by_ref)) = self.captures.get(f)
626        {
627            let mut start = self.capture_field_pos + 1;
628            if *by_ref {
629                // Account for an extra Deref.
630                start += 1;
631            }
632            // We may have an attempt to access `_1.f` as a shallow reborrow. Just ignore it.
633            if start <= place.projection.len() {
634                let projection = &place.projection[start..];
635                return Some((*index, projection));
636            }
637        }
638        None
639    }
640
641    fn iter(&self) -> impl Iterator<Item = (PlaceIndex, &PlaceRef<'tcx>)> {
642        self.places.iter_enumerated()
643    }
644
645    fn len(&self) -> usize {
646        self.places.len()
647    }
648}
649
650struct AssignmentResult<'a, 'tcx> {
651    tcx: TyCtxt<'tcx>,
652    typing_env: ty::TypingEnv<'tcx>,
653    checked_places: &'a PlaceSet<'tcx>,
654    body: &'a Body<'tcx>,
655    /// Set of locals that are live at least once. This is used to report fully unused locals.
656    ever_live: DenseBitSet<PlaceIndex>,
657    /// Set of locals that have a non-trivial drop. This is used to skip reporting unused
658    /// assignment if it would be used by the `Drop` impl.
659    ever_dropped: DenseBitSet<PlaceIndex>,
660    /// Set of assignments for each local. Here, assignment is understood in the AST sense. Any
661    /// MIR that may look like an assignment (Assign, DropAndReplace, Yield, Call) are considered.
662    ///
663    /// For each local, we return a map: for each source position, whether the statement is live
664    /// and which kind of access it performs. When we encounter multiple statements at the same
665    /// location, we only increase the liveness, in order to avoid false positives.
666    assignments: IndexVec<PlaceIndex, FxIndexMap<SourceInfo, Access>>,
667}
668
669impl<'a, 'tcx> AssignmentResult<'a, 'tcx> {
670    /// Collect all assignments to checked locals.
671    ///
672    /// Assignments are collected, even if they are live. Dead assignments are reported, and live
673    /// assignments are used to make diagnostics correct for match guards.
674    fn find_dead_assignments(
675        tcx: TyCtxt<'tcx>,
676        typing_env: ty::TypingEnv<'tcx>,
677        checked_places: &'a PlaceSet<'tcx>,
678        cursor: &mut ResultsCursor<'_, 'tcx, MaybeLivePlaces<'_, 'tcx>>,
679        body: &'a Body<'tcx>,
680    ) -> AssignmentResult<'a, 'tcx> {
681        let mut ever_live = DenseBitSet::new_empty(checked_places.len());
682        let mut ever_dropped = DenseBitSet::new_empty(checked_places.len());
683        let mut assignments = IndexVec::<PlaceIndex, FxIndexMap<_, _>>::from_elem(
684            Default::default(),
685            &checked_places.places,
686        );
687
688        let mut check_place =
689            |place: Place<'tcx>, kind, source_info: SourceInfo, live: &DenseBitSet<PlaceIndex>| {
690                if let Some((index, extra_projections)) = checked_places.get(place.as_ref()) {
691                    if !is_indirect(extra_projections) {
692                        match assignments[index].entry(source_info) {
693                            IndexEntry::Vacant(v) => {
694                                let access = Access { kind, live: live.contains(index) };
695                                v.insert(access);
696                            }
697                            IndexEntry::Occupied(mut o) => {
698                                // There were already a sighting. Mark this statement as live if it
699                                // was, to avoid false positives.
700                                o.get_mut().live |= live.contains(index);
701                            }
702                        }
703                    }
704                }
705            };
706
707        let mut record_drop = |place: Place<'tcx>| {
708            if let Some((index, &[])) = checked_places.get(place.as_ref()) {
709                ever_dropped.insert(index);
710            }
711        };
712
713        for (bb, bb_data) in traversal::postorder(body) {
714            cursor.seek_to_block_end(bb);
715            let live = cursor.get();
716            ever_live.union(live);
717
718            let terminator = bb_data.terminator();
719            match &terminator.kind {
720                TerminatorKind::Call { destination: place, .. }
721                | TerminatorKind::Yield { resume_arg: place, .. } => {
722                    check_place(*place, AccessKind::Assign, terminator.source_info, live);
723                    record_drop(*place)
724                }
725                TerminatorKind::Drop { place, .. } => record_drop(*place),
726                TerminatorKind::InlineAsm { operands, .. } => {
727                    for operand in operands {
728                        if let InlineAsmOperand::Out { place: Some(place), .. }
729                        | InlineAsmOperand::InOut { out_place: Some(place), .. } = operand
730                        {
731                            check_place(*place, AccessKind::Assign, terminator.source_info, live);
732                        }
733                    }
734                }
735                _ => {}
736            }
737
738            for (statement_index, statement) in bb_data.statements.iter().enumerate().rev() {
739                cursor.seek_before_primary_effect(Location { block: bb, statement_index });
740                let live = cursor.get();
741                ever_live.union(live);
742                match &statement.kind {
743                    StatementKind::Assign(box (place, _))
744                    | StatementKind::SetDiscriminant { box place, .. } => {
745                        check_place(*place, AccessKind::Assign, statement.source_info, live);
746                    }
747                    StatementKind::Retag(_, _)
748                    | StatementKind::StorageLive(_)
749                    | StatementKind::StorageDead(_)
750                    | StatementKind::Coverage(_)
751                    | StatementKind::Intrinsic(_)
752                    | StatementKind::Nop
753                    | StatementKind::FakeRead(_)
754                    | StatementKind::PlaceMention(_)
755                    | StatementKind::ConstEvalCounter
756                    | StatementKind::BackwardIncompatibleDropHint { .. }
757                    | StatementKind::AscribeUserType(_, _) => (),
758                }
759            }
760        }
761
762        // Check liveness of function arguments on entry.
763        {
764            cursor.seek_to_block_start(START_BLOCK);
765            let live = cursor.get();
766            ever_live.union(live);
767
768            // Verify that arguments and captured values are useful.
769            for (index, place) in checked_places.iter() {
770                let kind = if is_capture(*place) {
771                    // This is a by-ref capture, an assignment to it will modify surrounding
772                    // environment, so we do not report it.
773                    if place.projection.last() == Some(&PlaceElem::Deref) {
774                        continue;
775                    }
776
777                    AccessKind::Capture
778                } else if body.local_kind(place.local) == LocalKind::Arg {
779                    AccessKind::Param
780                } else {
781                    continue;
782                };
783                let source_info = body.local_decls[place.local].source_info;
784                let access = Access { kind, live: live.contains(index) };
785                assignments[index].insert(source_info, access);
786            }
787        }
788
789        AssignmentResult {
790            tcx,
791            typing_env,
792            checked_places,
793            ever_live,
794            ever_dropped,
795            assignments,
796            body,
797        }
798    }
799
800    /// Match guards introduce a different local to freeze the guarded value as immutable.
801    /// Having two locals, we need to make sure that we do not report an unused_variable
802    /// when the guard local is used but not the arm local, or vice versa, like in this example.
803    ///
804    ///    match 5 {
805    ///      x if x > 2 => {}
806    ///      ^    ^- This is `local`
807    ///      +------ This is `arm_local`
808    ///      _ => {}
809    ///    }
810    ///
811    fn merge_guards(&mut self) {
812        for (index, place) in self.checked_places.iter() {
813            let local = place.local;
814            if let &LocalInfo::User(BindingForm::RefForGuard(arm_local)) =
815                self.body.local_decls[local].local_info()
816            {
817                debug_assert!(place.projection.is_empty());
818
819                // Local to use in the arm.
820                let Some((arm_index, _proj)) = self.checked_places.get(arm_local.into()) else {
821                    continue;
822                };
823                debug_assert_ne!(index, arm_index);
824                debug_assert_eq!(_proj, &[]);
825
826                // Mark the arm local as used if the guard local is used.
827                if self.ever_live.contains(index) {
828                    self.ever_live.insert(arm_index);
829                }
830
831                // Some assignments are common to both locals in the source code.
832                // Sadly, we can only detect this using the `source_info`.
833                // Therefore, we loop over all the assignments we have for the guard local:
834                // - if they already appeared for the arm local, the assignment is live if one of the
835                //   two versions is live;
836                // - if it does not appear for the arm local, it happened inside the guard, so we add
837                //   it as-is.
838                let guard_assignments = std::mem::take(&mut self.assignments[index]);
839                let arm_assignments = &mut self.assignments[arm_index];
840                for (source_info, access) in guard_assignments {
841                    match arm_assignments.entry(source_info) {
842                        IndexEntry::Vacant(v) => {
843                            v.insert(access);
844                        }
845                        IndexEntry::Occupied(mut o) => {
846                            o.get_mut().live |= access.live;
847                        }
848                    }
849                }
850            }
851        }
852    }
853
854    /// Compute captures that are fully dead.
855    fn compute_dead_captures(&self, num_captures: usize) -> DenseBitSet<FieldIdx> {
856        // Report to caller the set of dead captures.
857        let mut dead_captures = DenseBitSet::new_empty(num_captures);
858        for (index, place) in self.checked_places.iter() {
859            if self.ever_live.contains(index) {
860                continue;
861            }
862
863            // This is a capture: pass information to the enclosing function.
864            if is_capture(*place) {
865                for p in place.projection {
866                    if let PlaceElem::Field(f, _) = p {
867                        dead_captures.insert(*f);
868                        break;
869                    }
870                }
871                continue;
872            }
873        }
874
875        dead_captures
876    }
877
878    /// Report fully unused locals, and forget the corresponding assignments.
879    fn report_fully_unused(&mut self) {
880        let tcx = self.tcx;
881
882        // First, report fully unused locals.
883        for (index, place) in self.checked_places.iter() {
884            if self.ever_live.contains(index) {
885                continue;
886            }
887
888            // this is a capture: let the enclosing function report the unused variable.
889            if is_capture(*place) {
890                continue;
891            }
892
893            let local = place.local;
894            let decl = &self.body.local_decls[local];
895
896            if decl.from_compiler_desugaring() {
897                continue;
898            }
899
900            // Only report actual user-defined binding from now on.
901            let LocalInfo::User(BindingForm::Var(binding)) = decl.local_info() else { continue };
902            let Some(hir_id) = decl.source_info.scope.lint_root(&self.body.source_scopes) else {
903                continue;
904            };
905
906            let introductions = &binding.introductions;
907
908            let Some((name, def_span)) = self.checked_places.names[index] else { continue };
909
910            // #117284, when `ident_span` and `def_span` have different contexts
911            // we can't provide a good suggestion, instead we pointed out the spans from macro
912            let from_macro = def_span.from_expansion()
913                && introductions.iter().any(|intro| intro.span.eq_ctxt(def_span));
914
915            let maybe_suggest_typo = || {
916                if let LocalKind::Arg = self.body.local_kind(local) {
917                    None
918                } else {
919                    maybe_suggest_unit_pattern_typo(
920                        tcx,
921                        self.body.source.def_id(),
922                        name,
923                        def_span,
924                        decl.ty,
925                    )
926                }
927            };
928
929            let statements = &mut self.assignments[index];
930            if statements.is_empty() {
931                let sugg = if from_macro {
932                    errors::UnusedVariableSugg::NoSugg { span: def_span, name }
933                } else {
934                    let typo = maybe_suggest_typo();
935                    errors::UnusedVariableSugg::TryPrefix { spans: vec![def_span], name, typo }
936                };
937                tcx.emit_node_span_lint(
938                    lint::builtin::UNUSED_VARIABLES,
939                    hir_id,
940                    def_span,
941                    errors::UnusedVariable {
942                        name,
943                        string_interp: maybe_suggest_literal_matching_name(self.body, name),
944                        sugg,
945                    },
946                );
947                continue;
948            }
949
950            // Idiomatic rust assigns a value to a local upon definition. However, we do not want to
951            // warn twice, for the unused local and for the unused assignment. Therefore, we remove
952            // from the list of assignments the ones that happen at the definition site.
953            statements.retain(|source_info, _| {
954                source_info.span.find_ancestor_inside(binding.pat_span).is_none()
955            });
956
957            // Extra assignments that we recognize thanks to the initialization span. We need to
958            // take care of macro contexts here to be accurate.
959            if let Some((_, initializer_span)) = binding.opt_match_place {
960                statements.retain(|source_info, _| {
961                    let within = source_info.span.find_ancestor_inside(initializer_span);
962                    let outer_initializer_span =
963                        initializer_span.find_ancestor_in_same_ctxt(source_info.span);
964                    within.is_none()
965                        && outer_initializer_span.map_or(true, |s| !s.contains(source_info.span))
966                });
967            }
968
969            if !statements.is_empty() {
970                // We have a dead local with outstanding assignments and with non-trivial drop.
971                // This is probably a drop-guard, so we do not issue a warning there.
972                if maybe_drop_guard(
973                    tcx,
974                    self.typing_env,
975                    index,
976                    &self.ever_dropped,
977                    self.checked_places,
978                    self.body,
979                ) {
980                    statements.clear();
981                    continue;
982                }
983
984                let typo = maybe_suggest_typo();
985                tcx.emit_node_span_lint(
986                    lint::builtin::UNUSED_VARIABLES,
987                    hir_id,
988                    def_span,
989                    errors::UnusedVarAssignedOnly { name, typo },
990                );
991                continue;
992            }
993
994            // We do not have outstanding assignments, suggest renaming the binding.
995            let spans = introductions.iter().map(|intro| intro.span).collect::<Vec<_>>();
996
997            let any_shorthand = introductions.iter().any(|intro| intro.is_shorthand);
998
999            let sugg = if any_shorthand {
1000                errors::UnusedVariableSugg::TryIgnore {
1001                    name,
1002                    shorthands: introductions
1003                        .iter()
1004                        .filter_map(
1005                            |intro| if intro.is_shorthand { Some(intro.span) } else { None },
1006                        )
1007                        .collect(),
1008                    non_shorthands: introductions
1009                        .iter()
1010                        .filter_map(
1011                            |intro| {
1012                                if !intro.is_shorthand { Some(intro.span) } else { None }
1013                            },
1014                        )
1015                        .collect(),
1016                }
1017            } else if from_macro {
1018                errors::UnusedVariableSugg::NoSugg { span: def_span, name }
1019            } else if !introductions.is_empty() {
1020                let typo = maybe_suggest_typo();
1021                errors::UnusedVariableSugg::TryPrefix { name, typo, spans: spans.clone() }
1022            } else {
1023                let typo = maybe_suggest_typo();
1024                errors::UnusedVariableSugg::TryPrefix { name, typo, spans: vec![def_span] }
1025            };
1026
1027            tcx.emit_node_span_lint(
1028                lint::builtin::UNUSED_VARIABLES,
1029                hir_id,
1030                spans,
1031                errors::UnusedVariable {
1032                    name,
1033                    string_interp: maybe_suggest_literal_matching_name(self.body, name),
1034                    sugg,
1035                },
1036            );
1037        }
1038    }
1039
1040    /// Second, report unused assignments that do not correspond to initialization.
1041    /// Initializations have been removed in the previous loop reporting unused variables.
1042    fn report_unused_assignments(self) {
1043        let tcx = self.tcx;
1044
1045        for (index, statements) in self.assignments.into_iter_enumerated() {
1046            if statements.is_empty() {
1047                continue;
1048            }
1049
1050            let Some((name, decl_span)) = self.checked_places.names[index] else { continue };
1051
1052            // We have outstanding assignments and with non-trivial drop.
1053            // This is probably a drop-guard, so we do not issue a warning there.
1054            if maybe_drop_guard(
1055                tcx,
1056                self.typing_env,
1057                index,
1058                &self.ever_dropped,
1059                self.checked_places,
1060                self.body,
1061            ) {
1062                continue;
1063            }
1064
1065            // We probed MIR in reverse order for dataflow.
1066            // We revert the vector to give a consistent order to the user.
1067            for (source_info, Access { live, kind }) in statements.into_iter().rev() {
1068                if live {
1069                    continue;
1070                }
1071
1072                // Report the dead assignment.
1073                let Some(hir_id) = source_info.scope.lint_root(&self.body.source_scopes) else {
1074                    continue;
1075                };
1076
1077                match kind {
1078                    AccessKind::Assign => {
1079                        let suggestion = annotate_mut_binding_to_immutable_binding(
1080                            tcx,
1081                            self.checked_places.places[index],
1082                            self.body.source.def_id().expect_local(),
1083                            source_info.span,
1084                            self.body,
1085                        );
1086                        tcx.emit_node_span_lint(
1087                            lint::builtin::UNUSED_ASSIGNMENTS,
1088                            hir_id,
1089                            source_info.span,
1090                            errors::UnusedAssign { name, help: suggestion.is_none(), suggestion },
1091                        )
1092                    }
1093                    AccessKind::Param => tcx.emit_node_span_lint(
1094                        lint::builtin::UNUSED_ASSIGNMENTS,
1095                        hir_id,
1096                        source_info.span,
1097                        errors::UnusedAssignPassed { name },
1098                    ),
1099                    AccessKind::Capture => tcx.emit_node_span_lint(
1100                        lint::builtin::UNUSED_ASSIGNMENTS,
1101                        hir_id,
1102                        decl_span,
1103                        errors::UnusedCaptureMaybeCaptureRef { name },
1104                    ),
1105                }
1106            }
1107        }
1108    }
1109}
1110
1111rustc_index::newtype_index! {
1112    pub struct PlaceIndex {}
1113}
1114
1115impl DebugWithContext<MaybeLivePlaces<'_, '_>> for PlaceIndex {
1116    fn fmt_with(
1117        &self,
1118        ctxt: &MaybeLivePlaces<'_, '_>,
1119        f: &mut std::fmt::Formatter<'_>,
1120    ) -> std::fmt::Result {
1121        std::fmt::Debug::fmt(&ctxt.checked_places.places[*self], f)
1122    }
1123}
1124
1125pub struct MaybeLivePlaces<'a, 'tcx> {
1126    tcx: TyCtxt<'tcx>,
1127    checked_places: &'a PlaceSet<'tcx>,
1128    capture_kind: CaptureKind,
1129    self_assignment: FxHashSet<Location>,
1130}
1131
1132impl<'tcx> MaybeLivePlaces<'_, 'tcx> {
1133    fn transfer_function<'a>(
1134        &'a self,
1135        trans: &'a mut DenseBitSet<PlaceIndex>,
1136    ) -> TransferFunction<'a, 'tcx> {
1137        TransferFunction {
1138            tcx: self.tcx,
1139            checked_places: &self.checked_places,
1140            capture_kind: self.capture_kind,
1141            trans,
1142            self_assignment: &self.self_assignment,
1143        }
1144    }
1145}
1146
1147impl<'tcx> Analysis<'tcx> for MaybeLivePlaces<'_, 'tcx> {
1148    type Domain = DenseBitSet<PlaceIndex>;
1149    type Direction = Backward;
1150
1151    const NAME: &'static str = "liveness-lint";
1152
1153    fn bottom_value(&self, _: &Body<'tcx>) -> Self::Domain {
1154        // bottom = not live
1155        DenseBitSet::new_empty(self.checked_places.len())
1156    }
1157
1158    fn initialize_start_block(&self, _: &Body<'tcx>, _: &mut Self::Domain) {
1159        // No variables are live until we observe a use
1160    }
1161
1162    fn apply_primary_statement_effect(
1163        &mut self,
1164        trans: &mut Self::Domain,
1165        statement: &Statement<'tcx>,
1166        location: Location,
1167    ) {
1168        self.transfer_function(trans).visit_statement(statement, location);
1169    }
1170
1171    fn apply_primary_terminator_effect<'mir>(
1172        &mut self,
1173        trans: &mut Self::Domain,
1174        terminator: &'mir Terminator<'tcx>,
1175        location: Location,
1176    ) -> TerminatorEdges<'mir, 'tcx> {
1177        self.transfer_function(trans).visit_terminator(terminator, location);
1178        terminator.edges()
1179    }
1180
1181    fn apply_call_return_effect(
1182        &mut self,
1183        _trans: &mut Self::Domain,
1184        _block: BasicBlock,
1185        _return_places: CallReturnPlaces<'_, 'tcx>,
1186    ) {
1187        // FIXME: what should happen here?
1188    }
1189}
1190
1191struct TransferFunction<'a, 'tcx> {
1192    tcx: TyCtxt<'tcx>,
1193    checked_places: &'a PlaceSet<'tcx>,
1194    trans: &'a mut DenseBitSet<PlaceIndex>,
1195    capture_kind: CaptureKind,
1196    self_assignment: &'a FxHashSet<Location>,
1197}
1198
1199impl<'tcx> Visitor<'tcx> for TransferFunction<'_, 'tcx> {
1200    fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
1201        match statement.kind {
1202            // `ForLet(None)` fake read erroneously marks the just-assigned local as live.
1203            // This defeats the purpose of the analysis for `let` bindings.
1204            StatementKind::FakeRead(box (FakeReadCause::ForLet(None), _)) => return,
1205            // Handle self-assignment by restricting the read/write they do.
1206            StatementKind::Assign(box (ref dest, ref rvalue))
1207                if self.self_assignment.contains(&location) =>
1208            {
1209                if let Rvalue::BinaryOp(
1210                    BinOp::AddWithOverflow | BinOp::SubWithOverflow | BinOp::MulWithOverflow,
1211                    box (_, rhs),
1212                ) = rvalue
1213                {
1214                    // We are computing the binary operation:
1215                    // - the LHS will be assigned, so we don't read it;
1216                    // - the RHS still needs to be read.
1217                    self.visit_operand(rhs, location);
1218                    self.visit_place(
1219                        dest,
1220                        PlaceContext::MutatingUse(MutatingUseContext::Store),
1221                        location,
1222                    );
1223                } else if let Rvalue::BinaryOp(_, box (_, rhs)) = rvalue {
1224                    // We are computing the binary operation:
1225                    // - the LHS is being updated, so we don't read it;
1226                    // - the RHS still needs to be read.
1227                    self.visit_operand(rhs, location);
1228                } else {
1229                    // This is the second part of a checked self-assignment,
1230                    // we are assigning the result.
1231                    // We do not consider the write to the destination as a `def`.
1232                    // `self_assignment` must be false if the assignment is indirect.
1233                    self.visit_rvalue(rvalue, location);
1234                }
1235            }
1236            _ => self.super_statement(statement, location),
1237        }
1238    }
1239
1240    fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
1241        // By-ref captures could be read by the surrounding environment, so we mark
1242        // them as live upon yield and return.
1243        match terminator.kind {
1244            TerminatorKind::Return
1245            | TerminatorKind::Yield { .. }
1246            | TerminatorKind::Goto { target: START_BLOCK } // Inserted for the `FnMut` case.
1247                if self.capture_kind != CaptureKind::None =>
1248            {
1249                // All indirect captures have an effect on the environment, so we mark them as live.
1250                for (index, place) in self.checked_places.iter() {
1251                    if place.local == ty::CAPTURE_STRUCT_LOCAL
1252                        && place.projection.last() == Some(&PlaceElem::Deref)
1253                    {
1254                        self.trans.insert(index);
1255                    }
1256                }
1257            }
1258            // Do not consider a drop to be a use. We whitelist interesting drops elsewhere.
1259            TerminatorKind::Drop { .. } => {}
1260            // Ignore assertions since they must be triggered by actual code.
1261            TerminatorKind::Assert { .. } => {}
1262            _ => self.super_terminator(terminator, location),
1263        }
1264    }
1265
1266    fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
1267        match rvalue {
1268            // When a closure/generator does not use some of its captures, do not consider these
1269            // captures as live in the surrounding function. This allows to report unused variables,
1270            // even if they have been (uselessly) captured.
1271            Rvalue::Aggregate(
1272                box AggregateKind::Closure(def_id, _) | box AggregateKind::Coroutine(def_id, _),
1273                operands,
1274            ) => {
1275                if let Some(def_id) = def_id.as_local() {
1276                    let dead_captures = self.tcx.check_liveness(def_id);
1277                    for (field, operand) in
1278                        operands.iter_enumerated().take(dead_captures.domain_size())
1279                    {
1280                        if !dead_captures.contains(field) {
1281                            self.visit_operand(operand, location);
1282                        }
1283                    }
1284                }
1285            }
1286            _ => self.super_rvalue(rvalue, location),
1287        }
1288    }
1289
1290    fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, location: Location) {
1291        if let Some((index, extra_projections)) = self.checked_places.get(place.as_ref()) {
1292            for i in (extra_projections.len()..=place.projection.len()).rev() {
1293                let place_part =
1294                    PlaceRef { local: place.local, projection: &place.projection[..i] };
1295                let extra_projections = &place.projection[i..];
1296
1297                if let Some(&elem) = extra_projections.get(0) {
1298                    self.visit_projection_elem(place_part, elem, context, location);
1299                }
1300            }
1301
1302            match DefUse::for_place(extra_projections, context) {
1303                Some(DefUse::Def) => {
1304                    self.trans.remove(index);
1305                }
1306                Some(DefUse::Use) => {
1307                    self.trans.insert(index);
1308                }
1309                None => {}
1310            }
1311        } else {
1312            self.super_place(place, context, location)
1313        }
1314    }
1315
1316    fn visit_local(&mut self, local: Local, context: PlaceContext, _: Location) {
1317        if let Some((index, _proj)) = self.checked_places.get(local.into()) {
1318            debug_assert_eq!(_proj, &[]);
1319            match DefUse::for_place(&[], context) {
1320                Some(DefUse::Def) => {
1321                    self.trans.remove(index);
1322                }
1323                Some(DefUse::Use) => {
1324                    self.trans.insert(index);
1325                }
1326                _ => {}
1327            }
1328        }
1329    }
1330}
1331
1332#[derive(Eq, PartialEq, Debug, Clone)]
1333enum DefUse {
1334    Def,
1335    Use,
1336}
1337
1338fn is_indirect(proj: &[PlaceElem<'_>]) -> bool {
1339    proj.iter().any(|p| p.is_indirect())
1340}
1341
1342impl DefUse {
1343    fn for_place<'tcx>(projection: &[PlaceElem<'tcx>], context: PlaceContext) -> Option<DefUse> {
1344        let is_indirect = is_indirect(projection);
1345        match context {
1346            PlaceContext::MutatingUse(
1347                MutatingUseContext::Store | MutatingUseContext::SetDiscriminant,
1348            ) => {
1349                if is_indirect {
1350                    // Treat derefs as a use of the base local. `*p = 4` is not a def of `p` but a
1351                    // use.
1352                    Some(DefUse::Use)
1353                } else if projection.is_empty() {
1354                    Some(DefUse::Def)
1355                } else {
1356                    None
1357                }
1358            }
1359
1360            // For the associated terminators, this is only a `Def` when the terminator returns
1361            // "successfully." As such, we handle this case separately in `call_return_effect`
1362            // above. However, if the place looks like `*_5`, this is still unconditionally a use of
1363            // `_5`.
1364            PlaceContext::MutatingUse(
1365                MutatingUseContext::Call
1366                | MutatingUseContext::Yield
1367                | MutatingUseContext::AsmOutput,
1368            ) => is_indirect.then_some(DefUse::Use),
1369
1370            // All other contexts are uses...
1371            PlaceContext::MutatingUse(
1372                MutatingUseContext::RawBorrow
1373                | MutatingUseContext::Borrow
1374                | MutatingUseContext::Drop
1375                | MutatingUseContext::Retag,
1376            )
1377            | PlaceContext::NonMutatingUse(
1378                NonMutatingUseContext::RawBorrow
1379                | NonMutatingUseContext::Copy
1380                | NonMutatingUseContext::Inspect
1381                | NonMutatingUseContext::Move
1382                | NonMutatingUseContext::FakeBorrow
1383                | NonMutatingUseContext::SharedBorrow
1384                | NonMutatingUseContext::PlaceMention,
1385            ) => Some(DefUse::Use),
1386
1387            PlaceContext::NonUse(
1388                NonUseContext::StorageLive
1389                | NonUseContext::StorageDead
1390                | NonUseContext::AscribeUserTy(_)
1391                | NonUseContext::BackwardIncompatibleDropHint
1392                | NonUseContext::VarDebugInfo,
1393            ) => None,
1394
1395            PlaceContext::MutatingUse(MutatingUseContext::Projection)
1396            | PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection) => {
1397                unreachable!("A projection could be a def or a use and must be handled separately")
1398            }
1399        }
1400    }
1401}