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