compiler/rustc_mir_transform/src/liveness.rs RUST 1,578 lines View on github.com → Search inside
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_lint_defs::builtin::{UNUSED_ASSIGNMENTS, UNUSED_VARIABLES};9use rustc_middle::bug;10use rustc_middle::mir::visit::{11    MutatingUseContext, NonMutatingUseContext, NonUseContext, PlaceContext, Visitor,12};13use rustc_middle::mir::*;14use rustc_middle::ty::print::with_no_trimmed_paths;15use rustc_middle::ty::{self, Ty, TyCtxt};16use rustc_mir_dataflow::fmt::DebugWithContext;17use rustc_mir_dataflow::{Analysis, Backward, ResultsCursor};18use rustc_span::Span;19use rustc_span::edit_distance::find_best_match_for_name;20use rustc_span::symbol::{Symbol, kw, sym};2122use crate::diagnostics;2324#[derive(Copy, Clone, Debug, PartialEq, Eq)]25enum AccessKind {26    Param,27    Assign,28    Capture,29}3031#[derive(Copy, Clone, Debug, PartialEq, Eq)]32enum CaptureKind {33    Closure(ty::ClosureKind),34    Coroutine,35    CoroutineClosure,36    None,37}3839#[derive(Copy, Clone, 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}5354#[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    }6061    // Don't run unused pass for intrinsics62    if tcx.intrinsic(def_id.to_def_id()).is_some() {63        return DenseBitSet::new_empty(0);64    }6566    // 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    }7071    // 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    }7879    let mut body = &*tcx.mir_promoted(def_id).0.borrow();80    let mut body_mem;8182    // Don't run if there are errors.83    if body.tainted_by_errors.is_some() {84        return DenseBitSet::new_empty(0);85    }8687    let mut checked_places = PlaceSet::default();88    checked_places.insert_locals(&body.local_decls);8990    // 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        }9899        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        };109110        let captures = tcx.closure_captures(def_id);111        checked_places.insert_captures(tcx, self_is_ref, captures, args.upvar_tys());112113        // `FnMut` closures can modify captured values and carry those114        // 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().kind123                {124                    bbdata.terminator_mut().kind = TerminatorKind::Goto { target: START_BLOCK };125                }126            }127            body = &body_mem;128        }129130        (capture_kind, args.upvar_tys().len())131    } else {132        (CaptureKind::None, 0)133    };134135    // Get the remaining variables' names from debuginfo.136    checked_places.record_debuginfo(&body.var_debug_info);137138    let self_assignment = find_self_assignments(&checked_places, body);139140    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);144145    let typing_env = ty::TypingEnv::post_analysis(tcx, body.source.def_id());146147    let mut assignments =148        AssignmentResult::find_dead_assignments(tcx, typing_env, &checked_places, &mut live, body);149150    assignments.merge_guards();151152    let dead_captures = assignments.compute_dead_captures(num_captures);153154    assignments.report_fully_unused();155    assignments.report_unused_assignments();156157    dead_captures158}159160/// Small helper to make semantics easier to read.161#[inline]162fn is_capture(place: PlaceRef<'_>) -> bool {163    if !place.projection.is_empty() {164        debug_assert_eq!(place.local, ty::CAPTURE_STRUCT_LOCAL);165        true166    } else {167        false168    }169}170171/// 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_def181            .variants()182            .iter()183            .filter(|v| 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_def188                .variants()189                .iter()190                .find(|v| v.name == name && matches!(v.ctor, Some((CtorKind::Const, _))))191        {192            return Some(diagnostics::PatternTypo {193                span,194                code: 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    }200201    // Look for consts of the same type with similar names as well,202    // not just unit structs and variants.203    let constants = tcx204        .hir_body_owners()205        .filter(|&def_id| {206            matches!(tcx.def_kind(def_id), DefKind::Const { .. })207                && tcx.type_of(def_id).instantiate_identity().skip_norm_wip() == ty208                && 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: with_no_trimmed_paths!(tcx.def_path_str(def_id)),219            kind: "constant",220            item_name,221        });222    }223224    None225}226227/// 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 still240        // in an earlier `TypingMode`. This is odd and we have to241        // manually mark aliases as non-rigid here.242        let ty = ty::set_aliases_to_non_rigid(tcx, ty).skip_norm_wip();243        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        false256    }257}258259/// Detect the following case260///261/// ```text262/// 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 out269/// parameter, instead of mutating the local binding. When encountering this we suggest:270///271/// ```text272/// 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};286287    // 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 };291292    // ... 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 };298299    // ... 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    };305306    // 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?;311312    let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _mut, inner) = rhs.kind else { return None };313314    // 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        None319    } else {320        // `&'name Ty` -> `&'name mut Ty` or `&Ty` -> `&mut Ty`321        Some(mut_ty.ty.span.shrink_to_lo())322    };323324    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    });334335    #[derive(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_span344                && let hir::ExprKind::Assign(lhs, rhs, _) = expr.kind345            {346                self.lhs = Some(lhs);347                self.rhs = Some(rhs);348            } else {349                intravisit::walk_expr(self, expr)350            }351        }352    }353}354355/// 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.0361///362/// This function tries to detect this pattern in order to avoid marking statement as a definition363/// 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();371372    const FIELD_0: FieldIdx = FieldIdx::from_u32(0);373    const FIELD_1: FieldIdx = FieldIdx::from_u32(1);374375    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                    }388389                    let TerminatorKind::Assert {390                        cond, target, msg: AssertKind::Overflow(..), ..391                    } = &bb_data.terminator().kind392                    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.kind400                    else {401                        continue;402                    };403404                    if dest != *lhs {405                        continue;406                    }407408                    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                    };415416                    // We ignore indirect self-assignment, because both occurrences of `dest` are uses.417                    let is_indirect = checked_places418                        .get(dest.as_ref())419                        .map_or(false, |(_, projections)| is_indirect(projections));420                    if is_indirect {421                        continue;422                    }423424                    if first_place.local == temp.local425                        && first_place.local == cond.local426                        && first_place.projection.is_empty()427                    {428                        // Original block429                        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 block438                        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                    }446447                    // We ignore indirect self-assignment, because both occurrences of `dest` are uses.448                    let is_indirect = checked_places449                        .get(first_place.as_ref())450                        .map_or(false, |(_, projections)| is_indirect(projections));451                    if is_indirect {452                        continue;453                    }454455                    self_assign.insert(Location { block: bb, statement_index });456457                    // Checked division verifies overflow before performing the division, so we458                    // need to go and ignore this check in the predecessor block.459                    if let BinOp::Div | BinOp::Rem = op460                        && statement_index == 0461                        && let &[pred] = body.basic_blocks.predecessors()[bb].as_slice()462                        && let TerminatorKind::Assert { msg, .. } =463                            &body.basic_blocks[pred].terminator().kind464                        && let AssertKind::Overflow(..) = **msg465                        && let len = body.basic_blocks[pred].statements.len()466                        && len >= 2467                    {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    }478479    self_assign480}481482#[derive(Default, Debug)]483struct PlaceSet<'tcx> {484    places: IndexVec<PlaceIndex, PlaceRef<'tcx>>,485    names: IndexVec<PlaceIndex, Option<(Symbol, Span)>>,486487    /// Places corresponding to locals, common case.488    locals: IndexVec<Local, Option<PlaceIndex>>,489490    // 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}496497impl<'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                debug_assert_eq!(index, _index);510            }511        }512    }513514    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        debug_assert_eq!(self.locals[ty::CAPTURE_STRUCT_LOCAL], None);523524        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        }531532        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 = 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            debug_assert_eq!(_f, f);544545            // Record a variable name from the capture, because it is much friendlier than the546            // debuginfo name.547            self.names.insert(548                index,549                (Symbol::intern(&capture.to_string(tcx)), capture.get_path_span(tcx)),550            );551        }552    }553554    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.value560                && 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        }568569        // 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    }582583    #[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_LOCAL589            && !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        None606    }607608    fn iter(&self) -> impl Iterator<Item = (PlaceIndex, &PlaceRef<'tcx>)> {609        self.places.iter_enumerated()610    }611612    fn len(&self) -> usize {613        self.places.len()614    }615}616617struct 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 unused625    /// 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. Any628    /// 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 live631    /// and which kind of access it performs. When we encounter multiple statements at the same632    /// location, we only increase the liveness, in order to avoid false positives.633    assignments: IndexVec<PlaceIndex, FxIndexMap<SourceInfo, Access>>,634}635636impl<'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 live640    /// 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        );654655        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 it671                            // 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        };679680        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        };685686        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);690691            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), .. } = operand709                        {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            }722723            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::Nop752                    | StatementKind::FakeRead(_)753                    | StatementKind::PlaceMention(_)754                    | StatementKind::ConstEvalCounter755                    | StatementKind::BackwardIncompatibleDropHint { .. }756                    | StatementKind::AscribeUserType(_, _) => (),757                }758            }759        }760761        // 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);766767            // 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 surrounding771                    // environment, so we do not report it.772                    if place.projection.last() == Some(&PlaceElem::Deref) {773                        continue;774                    }775776                    AccessKind::Capture777                } else if body.local_kind(place.local) == LocalKind::Arg {778                    AccessKind::Param779                } 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        }792793        AssignmentResult {794            tcx,795            typing_env,796            checked_places,797            ever_live,798            ever_dropped,799            assignments,800            body,801        }802    }803804    /// 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_variable806    /// 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                debug_assert!(place.projection.is_empty());822823                // Local to use in the arm.824                let Some((arm_index, _proj)) = self.checked_places.get(arm_local.into()) else {825                    continue;826                };827                debug_assert_ne!(index, arm_index);828                debug_assert_eq!(_proj, &[]);829830                // 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                }834835                // 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 the839                //   two versions is live;840                // - if it does not appear for the arm local, it happened inside the guard, so we add841                //   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    }857858    /// 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            }866867            // 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        }878879        dead_captures880    }881882    /// 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        }889890        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        }897898        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        }905906        false907    }908909    /// Check for source-level uses that may have been removed from reachable MIR.910    /// For example:911    /// ```rust912    /// fn example() {913    ///     let x = todo!();914    ///     eprintln!("{x}");915    /// }916    /// ```917    /// The use of x is unreachable, but we'll still want to know if x is used to correctly emit918    /// unused variable warning.919    fn is_local_used_in_source(&self, name: Symbol, def_span: Span) -> bool {920        use rustc_hir as hir;921        use rustc_hir::def::Res;922        use rustc_hir::intravisit::{self, Visitor};923924        let Some(body_def_id) = self.body.source.def_id().as_local() else { return false };925        let Some(hir_body) = self.tcx.hir_maybe_body_owned_by(body_def_id) else { return false };926        let typeck_results = self.tcx.typeck(body_def_id);927928        struct LocalUseVisitor<'a, 'tcx> {929            tcx: TyCtxt<'tcx>,930            typeck_results: &'a ty::TypeckResults<'tcx>,931            name: Symbol,932            def_span: Span,933            found: bool,934        }935936        impl<'a, 'tcx> Visitor<'tcx> for LocalUseVisitor<'a, 'tcx> {937            fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {938                if self.found {939                    return;940                }941942                if let hir::ExprKind::Path(qpath) = &expr.kind943                    && let Res::Local(hir_id) = self.typeck_results.qpath_res(qpath, expr.hir_id)944                    && self.tcx.hir_name(hir_id) == self.name945                    && self.tcx.hir_span(hir_id) == self.def_span946                {947                    self.found = true;948                    return;949                }950951                intravisit::walk_expr(self, expr);952            }953        }954955        let mut visitor =956            LocalUseVisitor { tcx: self.tcx, typeck_results, name, def_span, found: false };957        visitor.visit_body(hir_body);958        visitor.found959    }960961    /// Report fully unused locals, and forget the corresponding assignments.962    fn report_fully_unused(&mut self) {963        let tcx = self.tcx;964965        // Give a diagnostic when any of the string constants look like a naked format string that966        // would interpolate our dead local.967        let mut string_constants_in_body = None;968        let mut maybe_suggest_literal_matching_name = |name: Symbol| {969            // Visiting MIR to enumerate string constants can be expensive, so cache the result.970            let string_constants_in_body = string_constants_in_body.get_or_insert_with(|| {971                struct LiteralFinder {972                    found: Vec<(Span, String)>,973                }974975                impl<'tcx> Visitor<'tcx> for LiteralFinder {976                    fn visit_const_operand(&mut self, constant: &ConstOperand<'tcx>, _: Location) {977                        if let ty::Ref(_, ref_ty, _) = constant.ty().kind()978                            && ref_ty.kind() == &ty::Str979                        {980                            let rendered_constant = constant.const_.to_string();981                            self.found.push((constant.span, rendered_constant));982                        }983                    }984                }985986                let mut finder = LiteralFinder { found: vec![] };987                finder.visit_body(self.body);988                finder.found989            });990991            let brace_name = format!("{{{name}");992            string_constants_in_body993                .iter()994                .filter(|(_, rendered_constant)| {995                    rendered_constant996                        .split(&brace_name)997                        .any(|c| matches!(c.chars().next(), Some('}' | ':')))998                })999                .map(|&(lit, _)| diagnostics::UnusedVariableStringInterp { lit })1000                .collect::<Vec<_>>()1001        };10021003        // First, report fully unused locals.1004        for (index, place) in self.checked_places.iter() {1005            if self.ever_live.contains(index) {1006                continue;1007            }10081009            // this is a capture: let the enclosing function report the unused variable.1010            if is_capture(*place) {1011                continue;1012            }10131014            let local = place.local;1015            let decl = &self.body.local_decls[local];10161017            if decl.from_compiler_desugaring() {1018                continue;1019            }10201021            // Only report actual user-defined binding from now on.1022            let LocalInfo::User(BindingForm::Var(binding)) = decl.local_info() else { continue };1023            let Some(hir_id) = decl.source_info.scope.lint_root(&self.body.source_scopes) else {1024                continue;1025            };10261027            let introductions = &binding.introductions;10281029            let Some((name, def_span)) = self.checked_places.names[index] else { continue };10301031            // #117284, when `ident_span` and `def_span` have different contexts1032            // we can't provide a good suggestion, instead we pointed out the spans from macro1033            let from_macro = def_span.from_expansion()1034                && introductions.iter().any(|intro| intro.span.eq_ctxt(def_span));10351036            let maybe_suggest_typo = || {1037                if let LocalKind::Arg = self.body.local_kind(local) {1038                    None1039                } else {1040                    maybe_suggest_unit_pattern_typo(1041                        tcx,1042                        self.body.source.def_id(),1043                        name,1044                        def_span,1045                        decl.ty,1046                    )1047                }1048            };10491050            // the is_local_used_in_source is sufficient to check if the local is used in the source code,1051            // but we keep the local_kind check for a cheap filter to avoid heavy check1052            let is_used_after_uninitialized = self.body.local_kind(local) == LocalKind::Temp1053                && matches!(binding.opt_match_place, Some((None, _)))1054                && self.is_local_used_in_source(name, def_span);10551056            let statements = &mut self.assignments[index];1057            if statements.is_empty() {1058                if is_used_after_uninitialized {1059                    // A local from `let PAT = ...` normally has an assignment recorded for the1060                    // value it initializes. If no assignment was recorded in reachable MIR, the1061                    // initializer did not complete. If the local still has a source-level use,1062                    // that use was made unreachable by the diverging initializer.1063                    continue;1064                }10651066                if !self.is_local_in_reachable_code(local) {1067                    continue;1068                }10691070                let sugg = if from_macro {1071                    diagnostics::UnusedVariableSugg::NoSugg { span: def_span, name }1072                } else {1073                    let typo = maybe_suggest_typo();1074                    diagnostics::UnusedVariableSugg::TryPrefix { spans: vec![def_span], name, typo }1075                };1076                tcx.emit_node_span_lint(1077                    UNUSED_VARIABLES,1078                    hir_id,1079                    def_span,1080                    diagnostics::UnusedVariable {1081                        name,1082                        string_interp: maybe_suggest_literal_matching_name(name),1083                        sugg,1084                    },1085                );1086                continue;1087            }10881089            // Idiomatic rust assigns a value to a local upon definition. However, we do not want to1090            // warn twice, for the unused local and for the unused assignment. Therefore, we remove1091            // from the list of assignments the ones that happen at the definition site.1092            statements.retain(|source_info, _| {1093                !binding.introductions.iter().any(|intro| intro.span == source_info.span)1094            });10951096            // Extra assignments that we recognize thanks to the initialization span. We need to1097            // take care of macro contexts here to be accurate.1098            if let Some((_, initializer_span)) = binding.opt_match_place {1099                statements.retain(|source_info, _| {1100                    let within = source_info.span.find_ancestor_inside(initializer_span);1101                    let outer_initializer_span =1102                        initializer_span.find_ancestor_in_same_ctxt(source_info.span);1103                    within.is_none()1104                        && outer_initializer_span.map_or(true, |s| !s.contains(source_info.span))1105                });1106            }11071108            if !statements.is_empty() {1109                // We have a dead local with outstanding assignments and with non-trivial drop.1110                // This is probably a drop-guard, so we do not issue a warning there.1111                if maybe_drop_guard(1112                    tcx,1113                    self.typing_env,1114                    index,1115                    &self.ever_dropped,1116                    self.checked_places,1117                    self.body,1118                ) {1119                    statements.retain(|_, access| access.is_direct);1120                    if statements.is_empty() {1121                        continue;1122                    }1123                }11241125                let typo = maybe_suggest_typo();1126                tcx.emit_node_span_lint(1127                    UNUSED_VARIABLES,1128                    hir_id,1129                    def_span,1130                    diagnostics::UnusedVarAssignedOnly { name, typo },1131                );1132                continue;1133            }11341135            // We do not have outstanding assignments, suggest renaming the binding.1136            let spans = introductions.iter().map(|intro| intro.span).collect::<Vec<_>>();11371138            let any_shorthand = introductions.iter().any(|intro| intro.is_shorthand);11391140            let sugg = if any_shorthand {1141                diagnostics::UnusedVariableSugg::TryIgnore {1142                    name: name.to_ident_string(),1143                    shorthands: introductions1144                        .iter()1145                        .filter_map(1146                            |intro| if intro.is_shorthand { Some(intro.span) } else { None },1147                        )1148                        .collect(),1149                    non_shorthands: introductions1150                        .iter()1151                        .filter_map(1152                            |intro| {1153                                if !intro.is_shorthand { Some(intro.span) } else { None }1154                            },1155                        )1156                        .collect(),1157                }1158            } else if from_macro {1159                diagnostics::UnusedVariableSugg::NoSugg { span: def_span, name }1160            } else if !introductions.is_empty() {1161                let typo = maybe_suggest_typo();1162                diagnostics::UnusedVariableSugg::TryPrefix { name, typo, spans: spans.clone() }1163            } else {1164                let typo = maybe_suggest_typo();1165                diagnostics::UnusedVariableSugg::TryPrefix { name, typo, spans: vec![def_span] }1166            };11671168            tcx.emit_node_span_lint(1169                UNUSED_VARIABLES,1170                hir_id,1171                spans,1172                diagnostics::UnusedVariable {1173                    name,1174                    string_interp: maybe_suggest_literal_matching_name(name),1175                    sugg,1176                },1177            );1178        }1179    }11801181    /// Second, report unused assignments that do not correspond to initialization.1182    /// Initializations have been removed in the previous loop reporting unused variables.1183    fn report_unused_assignments(self) {1184        let tcx = self.tcx;11851186        for (index, statements) in self.assignments.into_iter_enumerated() {1187            if statements.is_empty() {1188                continue;1189            }11901191            let Some((name, decl_span)) = self.checked_places.names[index] else { continue };11921193            let is_maybe_drop_guard = maybe_drop_guard(1194                tcx,1195                self.typing_env,1196                index,1197                &self.ever_dropped,1198                self.checked_places,1199                self.body,1200            );12011202            // By convention, underscore-prefixed bindings are allowed to be unused explicitly.1203            if name.as_str().starts_with('_') {1204                continue;1205            }12061207            let mut next_direct_assignments: Vec<(Span, Location)> = Vec::new();1208            let mut dead_statements = Vec::with_capacity(statements.len());12091210            for (source_info, Access { live, kind, is_direct, location }) in statements.into_iter()1211            {1212                let direct_assignment = kind == AccessKind::Assign && is_direct;1213                let should_report = !live && (is_direct || !is_maybe_drop_guard);12141215                let overwrite = if should_report && direct_assignment {1216                    next_direct_assignments1217                        .iter()1218                        .rfind(|(_, overwrite_location)| {1219                            location.is_predecessor_of(*overwrite_location, self.body)1220                        })1221                        .map(|&(overwrite_span, _)| diagnostics::UnusedAssignOverwrite {1222                            assigned_span: source_info.span,1223                            overwrite_span,1224                            name,1225                        })1226                } else {1227                    None1228                };12291230                if direct_assignment {1231                    next_direct_assignments.push((source_info.span, location));1232                }12331234                if !should_report {1235                    continue;1236                }1237                dead_statements.push((source_info, kind, is_direct, overwrite));1238            }12391240            // We probed MIR in reverse order for dataflow.1241            // Emit diagnostics in source order instead.1242            for (source_info, kind, is_direct, overwrite) in dead_statements.into_iter().rev() {1243                // Report the dead assignment.1244                let Some(hir_id) = source_info.scope.lint_root(&self.body.source_scopes) else {1245                    continue;1246                };12471248                match kind {1249                    AccessKind::Assign => {1250                        let suggestion = annotate_mut_binding_to_immutable_binding(1251                            tcx,1252                            self.checked_places.places[index],1253                            self.body.source.def_id().expect_local(),1254                            source_info.span,1255                            self.body,1256                        );1257                        let overwrite =1258                            if suggestion.is_none() && is_direct { overwrite } else { None };1259                        let help = suggestion.is_none() && overwrite.is_none();1260                        tcx.emit_node_span_lint(1261                            UNUSED_ASSIGNMENTS,1262                            hir_id,1263                            source_info.span,1264                            diagnostics::UnusedAssign { name, overwrite, help, suggestion },1265                        )1266                    }1267                    AccessKind::Param => tcx.emit_node_span_lint(1268                        UNUSED_ASSIGNMENTS,1269                        hir_id,1270                        source_info.span,1271                        diagnostics::UnusedAssignPassed { name },1272                    ),1273                    AccessKind::Capture => tcx.emit_node_span_lint(1274                        UNUSED_ASSIGNMENTS,1275                        hir_id,1276                        decl_span,1277                        diagnostics::UnusedCaptureMaybeCaptureRef { name },1278                    ),1279                }1280            }1281        }1282    }1283}12841285rustc_index::newtype_index! {1286    pub struct PlaceIndex {}1287}12881289impl DebugWithContext<MaybeLivePlaces<'_, '_>> for PlaceIndex {1290    fn fmt_with(1291        &self,1292        ctxt: &MaybeLivePlaces<'_, '_>,1293        f: &mut std::fmt::Formatter<'_>,1294    ) -> std::fmt::Result {1295        std::fmt::Debug::fmt(&ctxt.checked_places.places[*self], f)1296    }1297}12981299pub struct MaybeLivePlaces<'a, 'tcx> {1300    tcx: TyCtxt<'tcx>,1301    checked_places: &'a PlaceSet<'tcx>,1302    capture_kind: CaptureKind,1303    self_assignment: FxHashSet<Location>,1304}13051306impl<'tcx> MaybeLivePlaces<'_, 'tcx> {1307    fn transfer_function<'a>(1308        &'a self,1309        trans: &'a mut DenseBitSet<PlaceIndex>,1310    ) -> TransferFunction<'a, 'tcx> {1311        TransferFunction {1312            tcx: self.tcx,1313            checked_places: &self.checked_places,1314            capture_kind: self.capture_kind,1315            trans,1316            self_assignment: &self.self_assignment,1317        }1318    }1319}13201321impl<'tcx> Analysis<'tcx> for MaybeLivePlaces<'_, 'tcx> {1322    type Domain = DenseBitSet<PlaceIndex>;1323    type Direction = Backward;13241325    const NAME: &'static str = "liveness-lint";13261327    fn bottom_value(&self, _: &Body<'tcx>) -> Self::Domain {1328        // bottom = not live1329        DenseBitSet::new_empty(self.checked_places.len())1330    }13311332    fn initialize_start_block(&self, _: &Body<'tcx>, _: &mut Self::Domain) {1333        // No variables are live until we observe a use1334    }13351336    fn apply_primary_statement_effect(1337        &self,1338        trans: &mut Self::Domain,1339        statement: &Statement<'tcx>,1340        location: Location,1341    ) {1342        self.transfer_function(trans).visit_statement(statement, location);1343    }13441345    fn apply_primary_terminator_effect(1346        &self,1347        trans: &mut Self::Domain,1348        terminator: &Terminator<'tcx>,1349        location: Location,1350    ) {1351        self.transfer_function(trans).visit_terminator(terminator, location);1352    }13531354    fn apply_call_return_effect(1355        &self,1356        _trans: &mut Self::Domain,1357        _block: BasicBlock,1358        _return_places: CallReturnPlaces<'_, 'tcx>,1359    ) {1360        // FIXME: what should happen here?1361    }1362}13631364struct TransferFunction<'a, 'tcx> {1365    tcx: TyCtxt<'tcx>,1366    checked_places: &'a PlaceSet<'tcx>,1367    trans: &'a mut DenseBitSet<PlaceIndex>,1368    capture_kind: CaptureKind,1369    self_assignment: &'a FxHashSet<Location>,1370}13711372impl<'tcx> Visitor<'tcx> for TransferFunction<'_, 'tcx> {1373    fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {1374        match statement.kind {1375            // `ForLet(None)` and `ForGuardBinding` fake reads erroneously mark the just-assigned1376            // locals as live. This defeats the purpose of the analysis for such bindings.1377            StatementKind::FakeRead((1378                FakeReadCause::ForLet(None) | FakeReadCause::ForGuardBinding,1379                _,1380            )) => return,1381            // Handle self-assignment by restricting the read/write they do.1382            StatementKind::Assign((ref dest, ref rvalue))1383                if self.self_assignment.contains(&location) =>1384            {1385                if let Rvalue::BinaryOp(1386                    BinOp::AddWithOverflow | BinOp::SubWithOverflow | BinOp::MulWithOverflow,1387                    (_, rhs),1388                ) = rvalue1389                {1390                    // We are computing the binary operation:1391                    // - the LHS will be assigned, so we don't read it;1392                    // - the RHS still needs to be read.1393                    self.visit_operand(rhs, location);1394                    self.visit_place(1395                        dest,1396                        PlaceContext::MutatingUse(MutatingUseContext::Store),1397                        location,1398                    );1399                } else if let Rvalue::BinaryOp(_, (_, rhs)) = rvalue {1400                    // We are computing the binary operation:1401                    // - the LHS is being updated, so we don't read it;1402                    // - the RHS still needs to be read.1403                    self.visit_operand(rhs, location);1404                } else {1405                    // This is the second part of a checked self-assignment,1406                    // we are assigning the result.1407                    // We do not consider the write to the destination as a `def`.1408                    // `self_assignment` must be false if the assignment is indirect.1409                    self.visit_rvalue(rvalue, location);1410                }1411            }1412            _ => self.super_statement(statement, location),1413        }1414    }14151416    fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {1417        // By-ref captures could be read by the surrounding environment, so we mark1418        // them as live upon yield and return.1419        match terminator.kind {1420            TerminatorKind::Return1421            | TerminatorKind::Yield { .. }1422            | TerminatorKind::Goto { target: START_BLOCK } // Inserted for the `FnMut` case.1423            | TerminatorKind::Call { target: None, .. } // unwinding could be caught1424                if self.capture_kind != CaptureKind::None =>1425            {1426                // All indirect captures have an effect on the environment, so we mark them as live.1427                for (index, place) in self.checked_places.iter() {1428                    if place.local == ty::CAPTURE_STRUCT_LOCAL1429                        && place.projection.last() == Some(&PlaceElem::Deref)1430                    {1431                        self.trans.insert(index);1432                    }1433                }1434            }1435            // Do not consider a drop to be a use. We whitelist interesting drops elsewhere.1436            TerminatorKind::Drop { .. } => {}1437            // Ignore assertions since they must be triggered by actual code.1438            TerminatorKind::Assert { .. } => {}1439            _ => self.super_terminator(terminator, location),1440        }1441    }14421443    fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {1444        match rvalue {1445            // When a closure/generator does not use some of its captures, do not consider these1446            // captures as live in the surrounding function. This allows to report unused variables,1447            // even if they have been (uselessly) captured.1448            Rvalue::Aggregate(1449                AggregateKind::Closure(def_id, _) | AggregateKind::Coroutine(def_id, _),1450                operands,1451            ) => {1452                if let Some(def_id) = def_id.as_local() {1453                    let dead_captures = self.tcx.check_liveness(def_id);1454                    for (field, operand) in1455                        operands.iter_enumerated().take(dead_captures.domain_size())1456                    {1457                        if !dead_captures.contains(field) {1458                            self.visit_operand(operand, location);1459                        }1460                    }1461                }1462            }1463            _ => self.super_rvalue(rvalue, location),1464        }1465    }14661467    fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, location: Location) {1468        if let Some((index, extra_projections)) = self.checked_places.get(place.as_ref()) {1469            for i in (extra_projections.len()..=place.projection.len()).rev() {1470                let place_part =1471                    PlaceRef { local: place.local, projection: &place.projection[..i] };1472                let extra_projections = &place.projection[i..];14731474                if let Some(&elem) = extra_projections.get(0) {1475                    self.visit_projection_elem(place_part, elem, context, location);1476                }1477            }14781479            match DefUse::for_place(extra_projections, context) {1480                Some(DefUse::Def) => {1481                    self.trans.remove(index);1482                }1483                Some(DefUse::Use) => {1484                    self.trans.insert(index);1485                }1486                None => {}1487            }1488        } else {1489            self.super_place(place, context, location)1490        }1491    }14921493    fn visit_local(&mut self, local: Local, context: PlaceContext, _: Location) {1494        if let Some((index, _proj)) = self.checked_places.get(local.into()) {1495            debug_assert_eq!(_proj, &[]);1496            match DefUse::for_place(&[], context) {1497                Some(DefUse::Def) => {1498                    self.trans.remove(index);1499                }1500                Some(DefUse::Use) => {1501                    self.trans.insert(index);1502                }1503                _ => {}1504            }1505        }1506    }1507}15081509#[derive(Eq, PartialEq, Debug, Clone)]1510enum DefUse {1511    Def,1512    Use,1513}15141515fn is_indirect(proj: &[PlaceElem<'_>]) -> bool {1516    proj.iter().any(|p| p.is_indirect())1517}15181519impl DefUse {1520    fn for_place<'tcx>(projection: &[PlaceElem<'tcx>], context: PlaceContext) -> Option<DefUse> {1521        let is_indirect = is_indirect(projection);1522        match context {1523            PlaceContext::MutatingUse(1524                MutatingUseContext::Store | MutatingUseContext::SetDiscriminant,1525            ) => {1526                if is_indirect {1527                    // Treat derefs as a use of the base local. `*p = 4` is not a def of `p` but a1528                    // use.1529                    Some(DefUse::Use)1530                } else if projection.is_empty() {1531                    Some(DefUse::Def)1532                } else {1533                    None1534                }1535            }15361537            // For the associated terminators, this is only a `Def` when the terminator returns1538            // "successfully." As such, we handle this case separately in `call_return_effect`1539            // above. However, if the place looks like `*_5`, this is still unconditionally a use of1540            // `_5`.1541            PlaceContext::MutatingUse(1542                MutatingUseContext::Call1543                | MutatingUseContext::Yield1544                | MutatingUseContext::AsmOutput,1545            ) => is_indirect.then_some(DefUse::Use),15461547            // All other contexts are uses...1548            PlaceContext::MutatingUse(1549                MutatingUseContext::RawBorrow1550                | MutatingUseContext::Borrow1551                | MutatingUseContext::Drop,1552            )1553            | PlaceContext::NonMutatingUse(1554                NonMutatingUseContext::RawBorrow1555                | NonMutatingUseContext::Copy1556                | NonMutatingUseContext::Inspect1557                | NonMutatingUseContext::Move1558                | NonMutatingUseContext::FakeBorrow1559                | NonMutatingUseContext::SharedBorrow1560                | NonMutatingUseContext::PlaceMention,1561            ) => Some(DefUse::Use),15621563            PlaceContext::NonUse(1564                NonUseContext::StorageLive1565                | NonUseContext::StorageDead1566                | NonUseContext::AscribeUserTy(_)1567                | NonUseContext::BackwardIncompatibleDropHint1568                | NonUseContext::VarDebugInfo,1569            ) => None,15701571            PlaceContext::MutatingUse(MutatingUseContext::Projection)1572            | PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection) => {1573                unreachable!("A projection could be a def or a use and must be handled separately")1574            }1575        }1576    }1577}

Code quality findings 43

Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let mut self_ty = body.local_decls[ty::CAPTURE_STRUCT_LOCAL].ty;
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let ty = checked_places.places[index].ty(&body.local_decls, tcx).ty;
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let Mutability::Mut = body.local_decls[local].mutability else { return None };
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let ty = fn_decl.inputs[hir_param_index];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let param = hir_body.params[hir_param_index];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let Some(assign) = body.basic_blocks[*target].statements.first() else {
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let [PlaceElem::Field(FIELD_0, _)] = &temp.projection.as_slice() else {
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let [PlaceElem::Field(FIELD_1, _)] = &cond.projection.as_slice() else {
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
&& let &[pred] = body.basic_blocks.predecessors()[bb].as_slice()
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
&body.basic_blocks[pred].terminator().kind
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
&& let len = body.basic_blocks[pred].statements.len()
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
self.locals[local] = Some(index);
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
debug_assert_eq!(self.locals[ty::CAPTURE_STRUCT_LOCAL], None);
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
&& let Some(index) = self.locals[place.local]
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let remove = match self.names[index] {
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
fn get(&self, place: PlaceRef<'tcx>) -> Option<(PlaceIndex, &'tcx [PlaceElem<'tcx>])> {
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
if let Some(index) = self.locals[place.local] {
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
match assignments[index].entry(source_info) {
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let source_info = body.local_decls[place.local].source_info;
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
assignments[index].insert(source_info, access);
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
self.body.local_decls[local].local_info()
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let guard_assignments = std::mem::take(&mut self.assignments[index]);
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let arm_assignments = &mut self.assignments[arm_index];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let decl = &self.body.local_decls[local];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let Some((name, def_span)) = self.checked_places.names[index] else { continue };
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let statements = &mut self.assignments[index];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let Some((name, decl_span)) = self.checked_places.names[index] else { continue };
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
self.checked_places.places[index],
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
std::fmt::Debug::fmt(&ctxt.checked_places.places[*self], f)
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
PlaceRef { local: place.local, projection: &place.projection[..i] };
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let extra_projections = &place.projection[i..];
Info: Wildcard imports (`use some::path::*;`) can obscure the origin of names and lead to conflicts. Prefer importing specific items explicitly.
info maintainability wildcard-import
use rustc_middle::mir::*;
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
let (capture_kind, args) = match self_ty.kind() {
Performance Info: Frequent cloning, especially of Strings, Vecs, or other heap-allocated types inside loops, can be expensive. Consider using references/borrowing where possible.
info performance clone-in-loop
body_mem = body.clone();
Performance Info: Calling .push() repeatedly inside a loop without prior capacity reservation can lead to multiple reallocations. Consider using `Vec::with_capacity(n)` or `vec.reserve(n)` if the approximate number of elements is known.
info performance push-without-reserve
let index = self.places.push(local.into());
Performance Info: Calling .push() repeatedly inside a loop without prior capacity reservation can lead to multiple reallocations. Consider using `Vec::with_capacity(n)` or `vec.reserve(n)` if the approximate number of elements is known.
info performance push-without-reserve
let _index = self.names.push(None);
Performance Info: Calling .push() repeatedly inside a loop without prior capacity reservation can lead to multiple reallocations. Consider using `Vec::with_capacity(n)` or `vec.reserve(n)` if the approximate number of elements is known.
info performance push-without-reserve
let index = self.places.push(place.as_ref());
Performance Info: Calling .push() repeatedly inside a loop without prior capacity reservation can lead to multiple reallocations. Consider using `Vec::with_capacity(n)` or `vec.reserve(n)` if the approximate number of elements is known.
info performance push-without-reserve
let _f = self.captures.push((index, by_ref));
Maintainability Info: `todo!()` or `unimplemented!()` macros indicate incomplete code paths that will panic at runtime if reached. Ensure these are replaced with actual logic before production use.
info correctness todo-unimplemented
/// Variables in unreachable code (e.g., after `todo!()`) should not trigger unused warnings.
Maintainability Info: `todo!()` or `unimplemented!()` macros indicate incomplete code paths that will panic at runtime if reached. Ensure these are replaced with actual logic before production use.
info correctness todo-unimplemented
/// let x = todo!();
Info: Direct printing to stdout/stderr. For application logging, prefer using a logging facade like `log` or `tracing` for better control over levels, formatting, and output destinations.
info maintainability println-macro
/// eprintln!("{x}");
Performance Info: Calling .push() repeatedly inside a loop without prior capacity reservation can lead to multiple reallocations. Consider using `Vec::with_capacity(n)` or `vec.reserve(n)` if the approximate number of elements is known.
info performance push-without-reserve
dead_statements.push((source_info, kind, is_direct, overwrite));
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
match DefUse::for_place(&[], context) {

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.