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