Skip to main content

rustc_mir_transform/
liveness.rs

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