rustc_mir_transform/
liveness.rs

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