compiler/rustc_mir_transform/src/liveness.rs RUST 1,509 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_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};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        matches!(239            ty.kind(),240            ty::Closure(..)241                | ty::Coroutine(..)242                | ty::Tuple(..)243                | ty::Adt(..)244                | ty::Dynamic(..)245                | ty::Array(..)246                | ty::Slice(..)247                | ty::Alias(ty::AliasTy { kind: ty::Opaque { .. }, .. })248        ) && ty.needs_drop(tcx, typing_env)249    } else {250        false251    }252}253254/// Detect the following case255///256/// ```text257/// fn change_object(mut a: &Ty) {258///     let a = Ty::new();259///     b = &a;260/// }261/// ```262///263/// where the user likely meant to modify the value behind there reference, use `a` as an out264/// parameter, instead of mutating the local binding. When encountering this we suggest:265///266/// ```text267/// fn change_object(a: &'_ mut Ty) {268///     let a = Ty::new();269///     *b = a;270/// }271/// ```272fn annotate_mut_binding_to_immutable_binding<'tcx>(273    tcx: TyCtxt<'tcx>,274    place: PlaceRef<'tcx>,275    body_def_id: LocalDefId,276    assignment_span: Span,277    body: &Body<'tcx>,278) -> Option<diagnostics::UnusedAssignSuggestion> {279    use rustc_hir as hir;280    use rustc_hir::intravisit::{self, Visitor};281282    // Verify we have a mutable argument...283    let local = place.as_local()?;284    let LocalKind::Arg = body.local_kind(local) else { return None };285    let Mutability::Mut = body.local_decls[local].mutability else { return None };286287    // ... with reference type...288    let hir_param_index =289        local.as_usize() - if tcx.is_closure_like(body_def_id.to_def_id()) { 2 } else { 1 };290    let fn_decl = tcx.hir_node_by_def_id(body_def_id).fn_decl()?;291    let ty = fn_decl.inputs[hir_param_index];292    let hir::TyKind::Ref(lt, mut_ty) = ty.kind else { return None };293294    // ... as a binding pattern.295    let hir_body = tcx.hir_maybe_body_owned_by(body_def_id)?;296    let param = hir_body.params[hir_param_index];297    let hir::PatKind::Binding(hir::BindingMode::MUT, _hir_id, ident, _) = param.pat.kind else {298        return None;299    };300301    // Find the assignment to modify.302    let mut finder = ExprFinder { assignment_span, lhs: None, rhs: None };303    finder.visit_body(hir_body);304    let lhs = finder.lhs?;305    let rhs = finder.rhs?;306307    let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _mut, inner) = rhs.kind else { return None };308309    // Changes to the parameter's type.310    let pre = if lt.ident.span.is_empty() { "" } else { " " };311    let ty_span = if mut_ty.mutbl.is_mut() {312        // Leave `&'name mut Ty` and `&mut Ty` as they are (#136028).313        None314    } else {315        // `&'name Ty` -> `&'name mut Ty` or `&Ty` -> `&mut Ty`316        Some(mut_ty.ty.span.shrink_to_lo())317    };318319    return Some(diagnostics::UnusedAssignSuggestion {320        ty_span,321        pre,322        // Span of the `mut` before the binding.323        ty_ref_span: param.pat.span.until(ident.span),324        // Where to add a `*`.325        pre_lhs_span: lhs.span.shrink_to_lo(),326        // Where to remove the borrow.327        rhs_borrow_span: rhs.span.until(inner.span),328    });329330    #[derive(Debug)]331    struct ExprFinder<'hir> {332        assignment_span: Span,333        lhs: Option<&'hir hir::Expr<'hir>>,334        rhs: Option<&'hir hir::Expr<'hir>>,335    }336    impl<'hir> Visitor<'hir> for ExprFinder<'hir> {337        fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {338            if expr.span == self.assignment_span339                && let hir::ExprKind::Assign(lhs, rhs, _) = expr.kind340            {341                self.lhs = Some(lhs);342                self.rhs = Some(rhs);343            } else {344                intravisit::walk_expr(self, expr)345            }346        }347    }348}349350/// Compute self-assignments of the form `a += b`.351///352/// MIR building generates 2 statements and 1 terminator for such assignments:353/// - _temp = CheckedBinaryOp(a, b)354/// - assert(!_temp.1)355/// - a = _temp.0356///357/// This function tries to detect this pattern in order to avoid marking statement as a definition358/// and use. This will let the analysis be dictated by the next use of `a`.359///360/// Note that we will still need to account for the use of `b`.361fn find_self_assignments<'tcx>(362    checked_places: &PlaceSet<'tcx>,363    body: &Body<'tcx>,364) -> FxHashSet<Location> {365    let mut self_assign = FxHashSet::default();366367    const FIELD_0: FieldIdx = FieldIdx::from_u32(0);368    const FIELD_1: FieldIdx = FieldIdx::from_u32(1);369370    for (bb, bb_data) in body.basic_blocks.iter_enumerated() {371        for (statement_index, stmt) in bb_data.statements.iter().enumerate() {372            let StatementKind::Assign((first_place, rvalue)) = &stmt.kind else { continue };373            match rvalue {374                // For checked binary ops, the MIR builder inserts an assertion in between.375                Rvalue::BinaryOp(376                    BinOp::AddWithOverflow | BinOp::SubWithOverflow | BinOp::MulWithOverflow,377                    (Operand::Copy(lhs), _),378                ) => {379                    // Checked binary ops only appear at the end of the block, before the assertion.380                    if statement_index + 1 != bb_data.statements.len() {381                        continue;382                    }383384                    let TerminatorKind::Assert {385                        cond, target, msg: AssertKind::Overflow(..), ..386                    } = &bb_data.terminator().kind387                    else {388                        continue;389                    };390                    let Some(assign) = body.basic_blocks[*target].statements.first() else {391                        continue;392                    };393                    let StatementKind::Assign((dest, Rvalue::Use(Operand::Move(temp), _))) =394                        assign.kind395                    else {396                        continue;397                    };398399                    if dest != *lhs {400                        continue;401                    }402403                    let Operand::Move(cond) = cond else { continue };404                    let [PlaceElem::Field(FIELD_0, _)] = &temp.projection.as_slice() else {405                        continue;406                    };407                    let [PlaceElem::Field(FIELD_1, _)] = &cond.projection.as_slice() else {408                        continue;409                    };410411                    // We ignore indirect self-assignment, because both occurrences of `dest` are uses.412                    let is_indirect = checked_places413                        .get(dest.as_ref())414                        .map_or(false, |(_, projections)| is_indirect(projections));415                    if is_indirect {416                        continue;417                    }418419                    if first_place.local == temp.local420                        && first_place.local == cond.local421                        && first_place.projection.is_empty()422                    {423                        // Original block424                        self_assign.insert(Location {425                            block: bb,426                            statement_index: bb_data.statements.len() - 1,427                        });428                        self_assign.insert(Location {429                            block: bb,430                            statement_index: bb_data.statements.len(),431                        });432                        // Target block433                        self_assign.insert(Location { block: *target, statement_index: 0 });434                    }435                }436                // Straight self-assignment.437                Rvalue::BinaryOp(op, (Operand::Copy(lhs), _)) => {438                    if lhs != first_place {439                        continue;440                    }441442                    // We ignore indirect self-assignment, because both occurrences of `dest` are uses.443                    let is_indirect = checked_places444                        .get(first_place.as_ref())445                        .map_or(false, |(_, projections)| is_indirect(projections));446                    if is_indirect {447                        continue;448                    }449450                    self_assign.insert(Location { block: bb, statement_index });451452                    // Checked division verifies overflow before performing the division, so we453                    // need to go and ignore this check in the predecessor block.454                    if let BinOp::Div | BinOp::Rem = op455                        && statement_index == 0456                        && let &[pred] = body.basic_blocks.predecessors()[bb].as_slice()457                        && let TerminatorKind::Assert { msg, .. } =458                            &body.basic_blocks[pred].terminator().kind459                        && let AssertKind::Overflow(..) = **msg460                        && let len = body.basic_blocks[pred].statements.len()461                        && len >= 2462                    {463                        // BitAnd of two checks.464                        self_assign.insert(Location { block: pred, statement_index: len - 1 });465                        // `lhs == MIN`.466                        self_assign.insert(Location { block: pred, statement_index: len - 2 });467                    }468                }469                _ => {}470            }471        }472    }473474    self_assign475}476477#[derive(Default, Debug)]478struct PlaceSet<'tcx> {479    places: IndexVec<PlaceIndex, PlaceRef<'tcx>>,480    names: IndexVec<PlaceIndex, Option<(Symbol, Span)>>,481482    /// Places corresponding to locals, common case.483    locals: IndexVec<Local, Option<PlaceIndex>>,484485    // Handling of captures.486    /// If `_1` is a reference, we need to add a `Deref` to the matched place.487    capture_field_pos: usize,488    /// Captured fields.489    captures: IndexVec<FieldIdx, (PlaceIndex, bool)>,490}491492impl<'tcx> PlaceSet<'tcx> {493    fn insert_locals(&mut self, decls: &IndexVec<Local, LocalDecl<'tcx>>) {494        self.locals = IndexVec::from_elem(None, &decls);495        for (local, decl) in decls.iter_enumerated() {496            // Record all user-written locals for the analysis.497            // We also keep the `RefForGuard` locals (more on that below).498            if let LocalInfo::User(BindingForm::Var(_) | BindingForm::RefForGuard(_)) =499                decl.local_info()500            {501                let index = self.places.push(local.into());502                self.locals[local] = Some(index);503                let _index = self.names.push(None);504                debug_assert_eq!(index, _index);505            }506        }507    }508509    fn insert_captures(510        &mut self,511        tcx: TyCtxt<'tcx>,512        self_is_ref: bool,513        captures: &[&'tcx ty::CapturedPlace<'tcx>],514        upvars: &ty::List<Ty<'tcx>>,515    ) {516        // We should not track the environment local separately.517        debug_assert_eq!(self.locals[ty::CAPTURE_STRUCT_LOCAL], None);518519        let self_place = Place {520            local: ty::CAPTURE_STRUCT_LOCAL,521            projection: tcx.mk_place_elems(if self_is_ref { &[PlaceElem::Deref] } else { &[] }),522        };523        if self_is_ref {524            self.capture_field_pos = 1;525        }526527        for (f, (capture, ty)) in std::iter::zip(captures, upvars).enumerate() {528            let f = FieldIdx::from_usize(f);529            let elem = PlaceElem::Field(f, ty);530            let by_ref = matches!(capture.info.capture_kind, ty::UpvarCapture::ByRef(..));531            let place = if by_ref {532                self_place.project_deeper(&[elem, PlaceElem::Deref], tcx)533            } else {534                self_place.project_deeper(&[elem], tcx)535            };536            let index = self.places.push(place.as_ref());537            let _f = self.captures.push((index, by_ref));538            debug_assert_eq!(_f, f);539540            // Record a variable name from the capture, because it is much friendlier than the541            // debuginfo name.542            self.names.insert(543                index,544                (Symbol::intern(&capture.to_string(tcx)), capture.get_path_span(tcx)),545            );546        }547    }548549    fn record_debuginfo(&mut self, var_debug_info: &Vec<VarDebugInfo<'tcx>>) {550        let ignore_name = |name: Symbol| {551            name == sym::empty || name == kw::SelfLower || name.as_str().starts_with('_')552        };553        for var_debug_info in var_debug_info {554            if let VarDebugInfoContents::Place(place) = var_debug_info.value555                && let Some(index) = self.locals[place.local]556                && !ignore_name(var_debug_info.name)557            {558                self.names.get_or_insert_with(index, || {559                    (var_debug_info.name, var_debug_info.source_info.span)560                });561            }562        }563564        // Discard places that will not result in a diagnostic.565        for index_opt in self.locals.iter_mut() {566            if let Some(index) = *index_opt {567                let remove = match self.names[index] {568                    None => true,569                    Some((name, _)) => ignore_name(name),570                };571                if remove {572                    *index_opt = None;573                }574            }575        }576    }577578    #[inline]579    fn get(&self, place: PlaceRef<'tcx>) -> Option<(PlaceIndex, &'tcx [PlaceElem<'tcx>])> {580        if let Some(index) = self.locals[place.local] {581            return Some((index, place.projection));582        }583        if place.local == ty::CAPTURE_STRUCT_LOCAL584            && !self.captures.is_empty()585            && self.capture_field_pos < place.projection.len()586            && let PlaceElem::Field(f, _) = place.projection[self.capture_field_pos]587            && let Some((index, by_ref)) = self.captures.get(f)588        {589            let mut start = self.capture_field_pos + 1;590            if *by_ref {591                // Account for an extra Deref.592                start += 1;593            }594            // We may have an attempt to access `_1.f` as a shallow reborrow. Just ignore it.595            if start <= place.projection.len() {596                let projection = &place.projection[start..];597                return Some((*index, projection));598            }599        }600        None601    }602603    fn iter(&self) -> impl Iterator<Item = (PlaceIndex, &PlaceRef<'tcx>)> {604        self.places.iter_enumerated()605    }606607    fn len(&self) -> usize {608        self.places.len()609    }610}611612struct AssignmentResult<'a, 'tcx> {613    tcx: TyCtxt<'tcx>,614    typing_env: ty::TypingEnv<'tcx>,615    checked_places: &'a PlaceSet<'tcx>,616    body: &'a Body<'tcx>,617    /// Set of locals that are live at least once. This is used to report fully unused locals.618    ever_live: DenseBitSet<PlaceIndex>,619    /// Set of locals that have a non-trivial drop. This is used to skip reporting unused620    /// assignment if it would be used by the `Drop` impl.621    ever_dropped: DenseBitSet<PlaceIndex>,622    /// Set of assignments for each local. Here, assignment is understood in the AST sense. Any623    /// MIR that may look like an assignment (Assign, DropAndReplace, Yield, Call) are considered.624    ///625    /// For each local, we return a map: for each source position, whether the statement is live626    /// and which kind of access it performs. When we encounter multiple statements at the same627    /// location, we only increase the liveness, in order to avoid false positives.628    assignments: IndexVec<PlaceIndex, FxIndexMap<SourceInfo, Access>>,629}630631impl<'a, 'tcx> AssignmentResult<'a, 'tcx> {632    /// Collect all assignments to checked locals.633    ///634    /// Assignments are collected, even if they are live. Dead assignments are reported, and live635    /// assignments are used to make diagnostics correct for match guards.636    fn find_dead_assignments(637        tcx: TyCtxt<'tcx>,638        typing_env: ty::TypingEnv<'tcx>,639        checked_places: &'a PlaceSet<'tcx>,640        cursor: &mut ResultsCursor<'_, 'tcx, MaybeLivePlaces<'_, 'tcx>>,641        body: &'a Body<'tcx>,642    ) -> AssignmentResult<'a, 'tcx> {643        let mut ever_live = DenseBitSet::new_empty(checked_places.len());644        let mut ever_dropped = DenseBitSet::new_empty(checked_places.len());645        let mut assignments = IndexVec::<PlaceIndex, FxIndexMap<_, _>>::from_elem(646            Default::default(),647            &checked_places.places,648        );649650        let mut check_place = |place: Place<'tcx>,651                               kind,652                               source_info: SourceInfo,653                               location: Location,654                               live: &DenseBitSet<PlaceIndex>| {655            if let Some((index, extra_projections)) = checked_places.get(place.as_ref()) {656                if !is_indirect(extra_projections) {657                    let is_direct = extra_projections.is_empty();658                    match assignments[index].entry(source_info) {659                        IndexEntry::Vacant(v) => {660                            let access =661                                Access { kind, location, live: live.contains(index), is_direct };662                            v.insert(access);663                        }664                        IndexEntry::Occupied(mut o) => {665                            // There were already a sighting. Mark this statement as live if it666                            // was, to avoid false positives.667                            o.get_mut().live |= live.contains(index);668                            o.get_mut().is_direct &= is_direct;669                        }670                    }671                }672            }673        };674675        let mut record_drop = |place: Place<'tcx>| {676            if let Some((index, &[])) = checked_places.get(place.as_ref()) {677                ever_dropped.insert(index);678            }679        };680681        for (bb, bb_data) in traversal::postorder(body) {682            cursor.seek_to_block_end(bb);683            let live = cursor.get();684            ever_live.union(live);685686            let terminator = bb_data.terminator();687            match &terminator.kind {688                TerminatorKind::Call { destination: place, .. }689                | TerminatorKind::Yield { resume_arg: place, .. } => {690                    check_place(691                        *place,692                        AccessKind::Assign,693                        terminator.source_info,694                        body.terminator_loc(bb),695                        live,696                    );697                    record_drop(*place)698                }699                TerminatorKind::Drop { place, .. } => record_drop(*place),700                TerminatorKind::InlineAsm { operands, .. } => {701                    for operand in operands {702                        if let InlineAsmOperand::Out { place: Some(place), .. }703                        | InlineAsmOperand::InOut { out_place: Some(place), .. } = operand704                        {705                            check_place(706                                *place,707                                AccessKind::Assign,708                                terminator.source_info,709                                body.terminator_loc(bb),710                                live,711                            );712                        }713                    }714                }715                _ => {}716            }717718            for (statement_index, statement) in bb_data.statements.iter().enumerate().rev() {719                let location = Location { block: bb, statement_index };720                cursor.seek_before_primary_effect(location);721                let live = cursor.get();722                ever_live.union(live);723                match &statement.kind {724                    StatementKind::Assign((place, _)) => {725                        check_place(726                            *place,727                            AccessKind::Assign,728                            statement.source_info,729                            location,730                            live,731                        );732                    }733                    StatementKind::SetDiscriminant { place, .. } => {734                        check_place(735                            **place,736                            AccessKind::Assign,737                            statement.source_info,738                            location,739                            live,740                        );741                    }742                    StatementKind::StorageLive(_)743                    | StatementKind::StorageDead(_)744                    | StatementKind::Coverage(_)745                    | StatementKind::Intrinsic(_)746                    | StatementKind::Nop747                    | StatementKind::FakeRead(_)748                    | StatementKind::PlaceMention(_)749                    | StatementKind::ConstEvalCounter750                    | StatementKind::BackwardIncompatibleDropHint { .. }751                    | StatementKind::AscribeUserType(_, _) => (),752                }753            }754        }755756        // Check liveness of function arguments on entry.757        {758            cursor.seek_to_block_start(START_BLOCK);759            let live = cursor.get();760            ever_live.union(live);761762            // Verify that arguments and captured values are useful.763            for (index, place) in checked_places.iter() {764                let kind = if is_capture(*place) {765                    // This is a by-ref capture, an assignment to it will modify surrounding766                    // environment, so we do not report it.767                    if place.projection.last() == Some(&PlaceElem::Deref) {768                        continue;769                    }770771                    AccessKind::Capture772                } else if body.local_kind(place.local) == LocalKind::Arg {773                    AccessKind::Param774                } else {775                    continue;776                };777                let source_info = body.local_decls[place.local].source_info;778                let access = Access {779                    kind,780                    location: Location::START,781                    live: live.contains(index),782                    is_direct: true,783                };784                assignments[index].insert(source_info, access);785            }786        }787788        AssignmentResult {789            tcx,790            typing_env,791            checked_places,792            ever_live,793            ever_dropped,794            assignments,795            body,796        }797    }798799    /// Match guards introduce a different local to freeze the guarded value as immutable.800    /// Having two locals, we need to make sure that we do not report an unused_variable801    /// when the guard local is used but not the arm local, or vice versa, like in this example.802    ///803    ///    match 5 {804    ///      x if x > 2 => {}805    ///      ^    ^- This is `local`806    ///      +------ This is `arm_local`807    ///      _ => {}808    ///    }809    ///810    fn merge_guards(&mut self) {811        for (index, place) in self.checked_places.iter() {812            let local = place.local;813            if let &LocalInfo::User(BindingForm::RefForGuard(arm_local)) =814                self.body.local_decls[local].local_info()815            {816                debug_assert!(place.projection.is_empty());817818                // Local to use in the arm.819                let Some((arm_index, _proj)) = self.checked_places.get(arm_local.into()) else {820                    continue;821                };822                debug_assert_ne!(index, arm_index);823                debug_assert_eq!(_proj, &[]);824825                // Mark the arm local as used if the guard local is used.826                if self.ever_live.contains(index) {827                    self.ever_live.insert(arm_index);828                }829830                // Some assignments are common to both locals in the source code.831                // Sadly, we can only detect this using the `source_info`.832                // Therefore, we loop over all the assignments we have for the guard local:833                // - if they already appeared for the arm local, the assignment is live if one of the834                //   two versions is live;835                // - if it does not appear for the arm local, it happened inside the guard, so we add836                //   it as-is.837                let guard_assignments = std::mem::take(&mut self.assignments[index]);838                let arm_assignments = &mut self.assignments[arm_index];839                for (source_info, access) in guard_assignments {840                    match arm_assignments.entry(source_info) {841                        IndexEntry::Vacant(v) => {842                            v.insert(access);843                        }844                        IndexEntry::Occupied(mut o) => {845                            o.get_mut().live |= access.live;846                        }847                    }848                }849            }850        }851    }852853    /// Compute captures that are fully dead.854    fn compute_dead_captures(&self, num_captures: usize) -> DenseBitSet<FieldIdx> {855        // Report to caller the set of dead captures.856        let mut dead_captures = DenseBitSet::new_empty(num_captures);857        for (index, place) in self.checked_places.iter() {858            if self.ever_live.contains(index) {859                continue;860            }861862            // This is a capture: pass information to the enclosing function.863            if is_capture(*place) {864                for p in place.projection {865                    if let PlaceElem::Field(f, _) = p {866                        dead_captures.insert(*f);867                        break;868                    }869                }870                continue;871            }872        }873874        dead_captures875    }876877    /// Check if a local is referenced in any reachable basic block.878    /// Variables in unreachable code (e.g., after `todo!()`) should not trigger unused warnings.879    fn is_local_in_reachable_code(&self, local: Local) -> bool {880        struct LocalVisitor {881            target_local: Local,882            found: bool,883        }884885        impl<'tcx> Visitor<'tcx> for LocalVisitor {886            fn visit_local(&mut self, local: Local, _context: PlaceContext, _location: Location) {887                if local == self.target_local {888                    self.found = true;889                }890            }891        }892893        let mut visitor = LocalVisitor { target_local: local, found: false };894        for (bb, bb_data) in traversal::postorder(self.body) {895            visitor.visit_basic_block_data(bb, bb_data);896            if visitor.found {897                return true;898            }899        }900901        false902    }903904    /// Report fully unused locals, and forget the corresponding assignments.905    fn report_fully_unused(&mut self) {906        let tcx = self.tcx;907908        // Give a diagnostic when any of the string constants look like a naked format string that909        // would interpolate our dead local.910        let mut string_constants_in_body = None;911        let mut maybe_suggest_literal_matching_name = |name: Symbol| {912            // Visiting MIR to enumerate string constants can be expensive, so cache the result.913            let string_constants_in_body = string_constants_in_body.get_or_insert_with(|| {914                struct LiteralFinder {915                    found: Vec<(Span, String)>,916                }917918                impl<'tcx> Visitor<'tcx> for LiteralFinder {919                    fn visit_const_operand(&mut self, constant: &ConstOperand<'tcx>, _: Location) {920                        if let ty::Ref(_, ref_ty, _) = constant.ty().kind()921                            && ref_ty.kind() == &ty::Str922                        {923                            let rendered_constant = constant.const_.to_string();924                            self.found.push((constant.span, rendered_constant));925                        }926                    }927                }928929                let mut finder = LiteralFinder { found: vec![] };930                finder.visit_body(self.body);931                finder.found932            });933934            let brace_name = format!("{{{name}");935            string_constants_in_body936                .iter()937                .filter(|(_, rendered_constant)| {938                    rendered_constant939                        .split(&brace_name)940                        .any(|c| matches!(c.chars().next(), Some('}' | ':')))941                })942                .map(|&(lit, _)| diagnostics::UnusedVariableStringInterp { lit })943                .collect::<Vec<_>>()944        };945946        // First, report fully unused locals.947        for (index, place) in self.checked_places.iter() {948            if self.ever_live.contains(index) {949                continue;950            }951952            // this is a capture: let the enclosing function report the unused variable.953            if is_capture(*place) {954                continue;955            }956957            let local = place.local;958            let decl = &self.body.local_decls[local];959960            if decl.from_compiler_desugaring() {961                continue;962            }963964            // Only report actual user-defined binding from now on.965            let LocalInfo::User(BindingForm::Var(binding)) = decl.local_info() else { continue };966            let Some(hir_id) = decl.source_info.scope.lint_root(&self.body.source_scopes) else {967                continue;968            };969970            let introductions = &binding.introductions;971972            let Some((name, def_span)) = self.checked_places.names[index] else { continue };973974            // #117284, when `ident_span` and `def_span` have different contexts975            // we can't provide a good suggestion, instead we pointed out the spans from macro976            let from_macro = def_span.from_expansion()977                && introductions.iter().any(|intro| intro.span.eq_ctxt(def_span));978979            let maybe_suggest_typo = || {980                if let LocalKind::Arg = self.body.local_kind(local) {981                    None982                } else {983                    maybe_suggest_unit_pattern_typo(984                        tcx,985                        self.body.source.def_id(),986                        name,987                        def_span,988                        decl.ty,989                    )990                }991            };992993            let statements = &mut self.assignments[index];994            if statements.is_empty() {995                if !self.is_local_in_reachable_code(local) {996                    continue;997                }998999                let sugg = if from_macro {1000                    diagnostics::UnusedVariableSugg::NoSugg { span: def_span, name }1001                } else {1002                    let typo = maybe_suggest_typo();1003                    diagnostics::UnusedVariableSugg::TryPrefix { spans: vec![def_span], name, typo }1004                };1005                tcx.emit_node_span_lint(1006                    lint::builtin::UNUSED_VARIABLES,1007                    hir_id,1008                    def_span,1009                    diagnostics::UnusedVariable {1010                        name,1011                        string_interp: maybe_suggest_literal_matching_name(name),1012                        sugg,1013                    },1014                );1015                continue;1016            }10171018            // Idiomatic rust assigns a value to a local upon definition. However, we do not want to1019            // warn twice, for the unused local and for the unused assignment. Therefore, we remove1020            // from the list of assignments the ones that happen at the definition site.1021            statements.retain(|source_info, _| {1022                !binding.introductions.iter().any(|intro| intro.span == source_info.span)1023            });10241025            // Extra assignments that we recognize thanks to the initialization span. We need to1026            // take care of macro contexts here to be accurate.1027            if let Some((_, initializer_span)) = binding.opt_match_place {1028                statements.retain(|source_info, _| {1029                    let within = source_info.span.find_ancestor_inside(initializer_span);1030                    let outer_initializer_span =1031                        initializer_span.find_ancestor_in_same_ctxt(source_info.span);1032                    within.is_none()1033                        && outer_initializer_span.map_or(true, |s| !s.contains(source_info.span))1034                });1035            }10361037            if !statements.is_empty() {1038                // We have a dead local with outstanding assignments and with non-trivial drop.1039                // This is probably a drop-guard, so we do not issue a warning there.1040                if maybe_drop_guard(1041                    tcx,1042                    self.typing_env,1043                    index,1044                    &self.ever_dropped,1045                    self.checked_places,1046                    self.body,1047                ) {1048                    statements.retain(|_, access| access.is_direct);1049                    if statements.is_empty() {1050                        continue;1051                    }1052                }10531054                let typo = maybe_suggest_typo();1055                tcx.emit_node_span_lint(1056                    lint::builtin::UNUSED_VARIABLES,1057                    hir_id,1058                    def_span,1059                    diagnostics::UnusedVarAssignedOnly { name, typo },1060                );1061                continue;1062            }10631064            // We do not have outstanding assignments, suggest renaming the binding.1065            let spans = introductions.iter().map(|intro| intro.span).collect::<Vec<_>>();10661067            let any_shorthand = introductions.iter().any(|intro| intro.is_shorthand);10681069            let sugg = if any_shorthand {1070                diagnostics::UnusedVariableSugg::TryIgnore {1071                    name: name.to_ident_string(),1072                    shorthands: introductions1073                        .iter()1074                        .filter_map(1075                            |intro| if intro.is_shorthand { Some(intro.span) } else { None },1076                        )1077                        .collect(),1078                    non_shorthands: introductions1079                        .iter()1080                        .filter_map(1081                            |intro| {1082                                if !intro.is_shorthand { Some(intro.span) } else { None }1083                            },1084                        )1085                        .collect(),1086                }1087            } else if from_macro {1088                diagnostics::UnusedVariableSugg::NoSugg { span: def_span, name }1089            } else if !introductions.is_empty() {1090                let typo = maybe_suggest_typo();1091                diagnostics::UnusedVariableSugg::TryPrefix { name, typo, spans: spans.clone() }1092            } else {1093                let typo = maybe_suggest_typo();1094                diagnostics::UnusedVariableSugg::TryPrefix { name, typo, spans: vec![def_span] }1095            };10961097            tcx.emit_node_span_lint(1098                lint::builtin::UNUSED_VARIABLES,1099                hir_id,1100                spans,1101                diagnostics::UnusedVariable {1102                    name,1103                    string_interp: maybe_suggest_literal_matching_name(name),1104                    sugg,1105                },1106            );1107        }1108    }11091110    /// Second, report unused assignments that do not correspond to initialization.1111    /// Initializations have been removed in the previous loop reporting unused variables.1112    fn report_unused_assignments(self) {1113        let tcx = self.tcx;11141115        for (index, statements) in self.assignments.into_iter_enumerated() {1116            if statements.is_empty() {1117                continue;1118            }11191120            let Some((name, decl_span)) = self.checked_places.names[index] else { continue };11211122            let is_maybe_drop_guard = maybe_drop_guard(1123                tcx,1124                self.typing_env,1125                index,1126                &self.ever_dropped,1127                self.checked_places,1128                self.body,1129            );11301131            // By convention, underscore-prefixed bindings are allowed to be unused explicitly.1132            if name.as_str().starts_with('_') {1133                continue;1134            }11351136            let mut next_direct_assignments: Vec<(Span, Location)> = Vec::new();1137            let mut dead_statements = Vec::with_capacity(statements.len());11381139            for (source_info, Access { live, kind, is_direct, location }) in statements.into_iter()1140            {1141                let direct_assignment = kind == AccessKind::Assign && is_direct;1142                let should_report = !live && (is_direct || !is_maybe_drop_guard);11431144                let overwrite = if should_report && direct_assignment {1145                    next_direct_assignments1146                        .iter()1147                        .rfind(|(_, overwrite_location)| {1148                            location.is_predecessor_of(*overwrite_location, self.body)1149                        })1150                        .map(|&(overwrite_span, _)| diagnostics::UnusedAssignOverwrite {1151                            assigned_span: source_info.span,1152                            overwrite_span,1153                            name,1154                        })1155                } else {1156                    None1157                };11581159                if direct_assignment {1160                    next_direct_assignments.push((source_info.span, location));1161                }11621163                if !should_report {1164                    continue;1165                }1166                dead_statements.push((source_info, kind, is_direct, overwrite));1167            }11681169            // We probed MIR in reverse order for dataflow.1170            // Emit diagnostics in source order instead.1171            for (source_info, kind, is_direct, overwrite) in dead_statements.into_iter().rev() {1172                // Report the dead assignment.1173                let Some(hir_id) = source_info.scope.lint_root(&self.body.source_scopes) else {1174                    continue;1175                };11761177                match kind {1178                    AccessKind::Assign => {1179                        let suggestion = annotate_mut_binding_to_immutable_binding(1180                            tcx,1181                            self.checked_places.places[index],1182                            self.body.source.def_id().expect_local(),1183                            source_info.span,1184                            self.body,1185                        );1186                        let overwrite =1187                            if suggestion.is_none() && is_direct { overwrite } else { None };1188                        let help = suggestion.is_none() && overwrite.is_none();1189                        tcx.emit_node_span_lint(1190                            lint::builtin::UNUSED_ASSIGNMENTS,1191                            hir_id,1192                            source_info.span,1193                            diagnostics::UnusedAssign { name, overwrite, help, suggestion },1194                        )1195                    }1196                    AccessKind::Param => tcx.emit_node_span_lint(1197                        lint::builtin::UNUSED_ASSIGNMENTS,1198                        hir_id,1199                        source_info.span,1200                        diagnostics::UnusedAssignPassed { name },1201                    ),1202                    AccessKind::Capture => tcx.emit_node_span_lint(1203                        lint::builtin::UNUSED_ASSIGNMENTS,1204                        hir_id,1205                        decl_span,1206                        diagnostics::UnusedCaptureMaybeCaptureRef { name },1207                    ),1208                }1209            }1210        }1211    }1212}12131214rustc_index::newtype_index! {1215    pub struct PlaceIndex {}1216}12171218impl DebugWithContext<MaybeLivePlaces<'_, '_>> for PlaceIndex {1219    fn fmt_with(1220        &self,1221        ctxt: &MaybeLivePlaces<'_, '_>,1222        f: &mut std::fmt::Formatter<'_>,1223    ) -> std::fmt::Result {1224        std::fmt::Debug::fmt(&ctxt.checked_places.places[*self], f)1225    }1226}12271228pub struct MaybeLivePlaces<'a, 'tcx> {1229    tcx: TyCtxt<'tcx>,1230    checked_places: &'a PlaceSet<'tcx>,1231    capture_kind: CaptureKind,1232    self_assignment: FxHashSet<Location>,1233}12341235impl<'tcx> MaybeLivePlaces<'_, 'tcx> {1236    fn transfer_function<'a>(1237        &'a self,1238        trans: &'a mut DenseBitSet<PlaceIndex>,1239    ) -> TransferFunction<'a, 'tcx> {1240        TransferFunction {1241            tcx: self.tcx,1242            checked_places: &self.checked_places,1243            capture_kind: self.capture_kind,1244            trans,1245            self_assignment: &self.self_assignment,1246        }1247    }1248}12491250impl<'tcx> Analysis<'tcx> for MaybeLivePlaces<'_, 'tcx> {1251    type Domain = DenseBitSet<PlaceIndex>;1252    type Direction = Backward;12531254    const NAME: &'static str = "liveness-lint";12551256    fn bottom_value(&self, _: &Body<'tcx>) -> Self::Domain {1257        // bottom = not live1258        DenseBitSet::new_empty(self.checked_places.len())1259    }12601261    fn initialize_start_block(&self, _: &Body<'tcx>, _: &mut Self::Domain) {1262        // No variables are live until we observe a use1263    }12641265    fn apply_primary_statement_effect(1266        &self,1267        trans: &mut Self::Domain,1268        statement: &Statement<'tcx>,1269        location: Location,1270    ) {1271        self.transfer_function(trans).visit_statement(statement, location);1272    }12731274    fn apply_primary_terminator_effect<'mir>(1275        &self,1276        trans: &mut Self::Domain,1277        terminator: &'mir Terminator<'tcx>,1278        location: Location,1279    ) -> TerminatorEdges<'mir, 'tcx> {1280        self.transfer_function(trans).visit_terminator(terminator, location);1281        terminator.edges()1282    }12831284    fn apply_call_return_effect(1285        &self,1286        _trans: &mut Self::Domain,1287        _block: BasicBlock,1288        _return_places: CallReturnPlaces<'_, 'tcx>,1289    ) {1290        // FIXME: what should happen here?1291    }1292}12931294struct TransferFunction<'a, 'tcx> {1295    tcx: TyCtxt<'tcx>,1296    checked_places: &'a PlaceSet<'tcx>,1297    trans: &'a mut DenseBitSet<PlaceIndex>,1298    capture_kind: CaptureKind,1299    self_assignment: &'a FxHashSet<Location>,1300}13011302impl<'tcx> Visitor<'tcx> for TransferFunction<'_, 'tcx> {1303    fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {1304        match statement.kind {1305            // `ForLet(None)` and `ForGuardBinding` fake reads erroneously mark the just-assigned1306            // locals as live. This defeats the purpose of the analysis for such bindings.1307            StatementKind::FakeRead((1308                FakeReadCause::ForLet(None) | FakeReadCause::ForGuardBinding,1309                _,1310            )) => return,1311            // Handle self-assignment by restricting the read/write they do.1312            StatementKind::Assign((ref dest, ref rvalue))1313                if self.self_assignment.contains(&location) =>1314            {1315                if let Rvalue::BinaryOp(1316                    BinOp::AddWithOverflow | BinOp::SubWithOverflow | BinOp::MulWithOverflow,1317                    (_, rhs),1318                ) = rvalue1319                {1320                    // We are computing the binary operation:1321                    // - the LHS will be assigned, so we don't read it;1322                    // - the RHS still needs to be read.1323                    self.visit_operand(rhs, location);1324                    self.visit_place(1325                        dest,1326                        PlaceContext::MutatingUse(MutatingUseContext::Store),1327                        location,1328                    );1329                } else if let Rvalue::BinaryOp(_, (_, rhs)) = rvalue {1330                    // We are computing the binary operation:1331                    // - the LHS is being updated, so we don't read it;1332                    // - the RHS still needs to be read.1333                    self.visit_operand(rhs, location);1334                } else {1335                    // This is the second part of a checked self-assignment,1336                    // we are assigning the result.1337                    // We do not consider the write to the destination as a `def`.1338                    // `self_assignment` must be false if the assignment is indirect.1339                    self.visit_rvalue(rvalue, location);1340                }1341            }1342            _ => self.super_statement(statement, location),1343        }1344    }13451346    fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {1347        // By-ref captures could be read by the surrounding environment, so we mark1348        // them as live upon yield and return.1349        match terminator.kind {1350            TerminatorKind::Return1351            | TerminatorKind::Yield { .. }1352            | TerminatorKind::Goto { target: START_BLOCK } // Inserted for the `FnMut` case.1353            | TerminatorKind::Call { target: None, .. } // unwinding could be caught1354                if self.capture_kind != CaptureKind::None =>1355            {1356                // All indirect captures have an effect on the environment, so we mark them as live.1357                for (index, place) in self.checked_places.iter() {1358                    if place.local == ty::CAPTURE_STRUCT_LOCAL1359                        && place.projection.last() == Some(&PlaceElem::Deref)1360                    {1361                        self.trans.insert(index);1362                    }1363                }1364            }1365            // Do not consider a drop to be a use. We whitelist interesting drops elsewhere.1366            TerminatorKind::Drop { .. } => {}1367            // Ignore assertions since they must be triggered by actual code.1368            TerminatorKind::Assert { .. } => {}1369            _ => self.super_terminator(terminator, location),1370        }1371    }13721373    fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {1374        match rvalue {1375            // When a closure/generator does not use some of its captures, do not consider these1376            // captures as live in the surrounding function. This allows to report unused variables,1377            // even if they have been (uselessly) captured.1378            Rvalue::Aggregate(1379                AggregateKind::Closure(def_id, _) | AggregateKind::Coroutine(def_id, _),1380                operands,1381            ) => {1382                if let Some(def_id) = def_id.as_local() {1383                    let dead_captures = self.tcx.check_liveness(def_id);1384                    for (field, operand) in1385                        operands.iter_enumerated().take(dead_captures.domain_size())1386                    {1387                        if !dead_captures.contains(field) {1388                            self.visit_operand(operand, location);1389                        }1390                    }1391                }1392            }1393            _ => self.super_rvalue(rvalue, location),1394        }1395    }13961397    fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, location: Location) {1398        if let Some((index, extra_projections)) = self.checked_places.get(place.as_ref()) {1399            for i in (extra_projections.len()..=place.projection.len()).rev() {1400                let place_part =1401                    PlaceRef { local: place.local, projection: &place.projection[..i] };1402                let extra_projections = &place.projection[i..];14031404                if let Some(&elem) = extra_projections.get(0) {1405                    self.visit_projection_elem(place_part, elem, context, location);1406                }1407            }14081409            match DefUse::for_place(extra_projections, context) {1410                Some(DefUse::Def) => {1411                    self.trans.remove(index);1412                }1413                Some(DefUse::Use) => {1414                    self.trans.insert(index);1415                }1416                None => {}1417            }1418        } else {1419            self.super_place(place, context, location)1420        }1421    }14221423    fn visit_local(&mut self, local: Local, context: PlaceContext, _: Location) {1424        if let Some((index, _proj)) = self.checked_places.get(local.into()) {1425            debug_assert_eq!(_proj, &[]);1426            match DefUse::for_place(&[], context) {1427                Some(DefUse::Def) => {1428                    self.trans.remove(index);1429                }1430                Some(DefUse::Use) => {1431                    self.trans.insert(index);1432                }1433                _ => {}1434            }1435        }1436    }1437}14381439#[derive(Eq, PartialEq, Debug, Clone)]1440enum DefUse {1441    Def,1442    Use,1443}14441445fn is_indirect(proj: &[PlaceElem<'_>]) -> bool {1446    proj.iter().any(|p| p.is_indirect())1447}14481449impl DefUse {1450    fn for_place<'tcx>(projection: &[PlaceElem<'tcx>], context: PlaceContext) -> Option<DefUse> {1451        let is_indirect = is_indirect(projection);1452        match context {1453            PlaceContext::MutatingUse(1454                MutatingUseContext::Store | MutatingUseContext::SetDiscriminant,1455            ) => {1456                if is_indirect {1457                    // Treat derefs as a use of the base local. `*p = 4` is not a def of `p` but a1458                    // use.1459                    Some(DefUse::Use)1460                } else if projection.is_empty() {1461                    Some(DefUse::Def)1462                } else {1463                    None1464                }1465            }14661467            // For the associated terminators, this is only a `Def` when the terminator returns1468            // "successfully." As such, we handle this case separately in `call_return_effect`1469            // above. However, if the place looks like `*_5`, this is still unconditionally a use of1470            // `_5`.1471            PlaceContext::MutatingUse(1472                MutatingUseContext::Call1473                | MutatingUseContext::Yield1474                | MutatingUseContext::AsmOutput,1475            ) => is_indirect.then_some(DefUse::Use),14761477            // All other contexts are uses...1478            PlaceContext::MutatingUse(1479                MutatingUseContext::RawBorrow1480                | MutatingUseContext::Borrow1481                | MutatingUseContext::Drop1482                | MutatingUseContext::Retag,1483            )1484            | PlaceContext::NonMutatingUse(1485                NonMutatingUseContext::RawBorrow1486                | NonMutatingUseContext::Copy1487                | NonMutatingUseContext::Inspect1488                | NonMutatingUseContext::Move1489                | NonMutatingUseContext::FakeBorrow1490                | NonMutatingUseContext::SharedBorrow1491                | NonMutatingUseContext::PlaceMention,1492            ) => Some(DefUse::Use),14931494            PlaceContext::NonUse(1495                NonUseContext::StorageLive1496                | NonUseContext::StorageDead1497                | NonUseContext::AscribeUserTy(_)1498                | NonUseContext::BackwardIncompatibleDropHint1499                | NonUseContext::VarDebugInfo,1500            ) => None,15011502            PlaceContext::MutatingUse(MutatingUseContext::Projection)1503            | PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection) => {1504                unreachable!("A projection could be a def or a use and must be handled separately")1505            }1506        }1507    }1508}

Code quality findings 41

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.
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.