src/tools/clippy/clippy_utils/src/usage.rs RUST 240 lines View on github.com → Search inside
1use crate::macros::root_macro_call_first_node;2use crate::res::MaybeResPath;3use crate::visitors::{Descend, Visitable, for_each_expr, for_each_expr_without_closures};4use crate::{self as utils, get_enclosing_loop_or_multi_call_closure, sym};5use core::ops::ControlFlow;6use hir::def::Res;7use rustc_hir::intravisit::{self, Visitor};8use rustc_hir::{self as hir, Expr, ExprKind, HirId, HirIdSet};9use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, Place, PlaceBase, PlaceWithHirId};10use rustc_lint::LateContext;11use rustc_middle::hir::nested_filter;12use rustc_middle::mir::FakeReadCause;13use rustc_middle::ty;1415/// Returns a set of mutated local variable IDs, or `None` if mutations could not be determined.16pub fn mutated_variables<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> Option<HirIdSet> {17    let mut delegate = MutVarsDelegate {18        used_mutably: HirIdSet::default(),19        skip: false,20    };21    ExprUseVisitor::for_clippy(cx, expr.hir_id.owner.def_id, &mut delegate)22        .walk_expr(expr)23        .into_ok();2425    if delegate.skip {26        return None;27    }28    Some(delegate.used_mutably)29}3031pub fn is_potentially_mutated<'tcx>(variable: HirId, expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> bool {32    mutated_variables(expr, cx).is_none_or(|mutated| mutated.contains(&variable))33}3435pub fn is_potentially_local_place(local_id: HirId, place: &Place<'_>) -> bool {36    match place.base {37        PlaceBase::Local(id) => id == local_id,38        PlaceBase::Upvar(_) => {39            // Conservatively assume yes.40            true41        },42        _ => false,43    }44}4546struct MutVarsDelegate {47    used_mutably: HirIdSet,48    skip: bool,49}5051impl MutVarsDelegate {52    fn update(&mut self, cat: &PlaceWithHirId<'_>) {53        match cat.place.base {54            PlaceBase::Local(id) => {55                self.used_mutably.insert(id);56            },57            PlaceBase::Upvar(_) => {58                //FIXME: This causes false negatives. We can't get the `NodeId` from59                //`Categorization::Upvar(_)`. So we search for any `Upvar`s in the60                //`while`-body, not just the ones in the condition.61                self.skip = true;62            },63            _ => {},64        }65    }66}6768impl<'tcx> Delegate<'tcx> for MutVarsDelegate {69    fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}7071    fn use_cloned(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}7273    fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, bk: ty::BorrowKind) {74        if bk == ty::BorrowKind::Mutable {75            self.update(cmt);76        }77    }7879    fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) {80        self.update(cmt);81    }8283    fn fake_read(&mut self, _: &PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}84}8586pub struct ParamBindingIdCollector {87    pub binding_hir_ids: Vec<HirId>,88}89impl<'tcx> ParamBindingIdCollector {90    fn collect_binding_hir_ids(body: &'tcx hir::Body<'tcx>) -> Vec<HirId> {91        let mut hir_ids: Vec<HirId> = Vec::new();92        for param in body.params {93            let mut finder = ParamBindingIdCollector {94                binding_hir_ids: Vec::new(),95            };96            finder.visit_param(param);97            for hir_id in &finder.binding_hir_ids {98                hir_ids.push(*hir_id);99            }100        }101        hir_ids102    }103}104impl<'tcx> Visitor<'tcx> for ParamBindingIdCollector {105    fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) {106        if let hir::PatKind::Binding(_, hir_id, ..) = pat.kind {107            self.binding_hir_ids.push(hir_id);108        }109        intravisit::walk_pat(self, pat);110    }111}112113pub struct BindingUsageFinder<'a, 'tcx> {114    cx: &'a LateContext<'tcx>,115    binding_ids: Vec<HirId>,116}117impl<'a, 'tcx> BindingUsageFinder<'a, 'tcx> {118    pub fn are_params_used(cx: &'a LateContext<'tcx>, body: &'tcx hir::Body<'tcx>) -> bool {119        let mut finder = BindingUsageFinder {120            cx,121            binding_ids: ParamBindingIdCollector::collect_binding_hir_ids(body),122        };123        finder.visit_body(body).is_break()124    }125}126impl<'tcx> Visitor<'tcx> for BindingUsageFinder<'_, 'tcx> {127    type Result = ControlFlow<()>;128    type NestedFilter = nested_filter::OnlyBodies;129130    fn visit_path(&mut self, path: &hir::Path<'tcx>, _: HirId) -> Self::Result {131        if let Res::Local(id) = path.res132            && self.binding_ids.contains(&id)133        {134            return ControlFlow::Break(());135        }136137        ControlFlow::Continue(())138    }139140    fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {141        self.cx.tcx142    }143}144145/// Checks if the given expression is a macro call to `todo!()` or `unimplemented!()`.146pub fn is_todo_unimplemented_macro(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {147    root_macro_call_first_node(cx, expr)148        .and_then(|macro_call| cx.tcx.get_diagnostic_name(macro_call.def_id))149        .is_some_and(|macro_name| matches!(macro_name, sym::todo_macro | sym::unimplemented_macro))150}151152/// Checks if the given expression is a stub, i.e., a `todo!()` or `unimplemented!()` expression,153/// or a block whose last expression is a `todo!()` or `unimplemented!()`.154pub fn is_todo_unimplemented_stub(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {155    if let ExprKind::Block(block, _) = expr.kind {156        if let Some(last_expr) = block.expr {157            return is_todo_unimplemented_macro(cx, last_expr);158        }159160        return block.stmts.last().is_some_and(|stmt| {161            if let hir::StmtKind::Expr(expr) | hir::StmtKind::Semi(expr) = stmt.kind {162                return is_todo_unimplemented_macro(cx, expr);163            }164            false165        });166    }167168    is_todo_unimplemented_macro(cx, expr)169}170171/// Checks if the given expression contains macro call to `todo!()` or `unimplemented!()`.172pub fn contains_todo_unimplement_macro(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool {173    for_each_expr_without_closures(expr, |e| {174        if is_todo_unimplemented_macro(cx, e) {175            ControlFlow::Break(())176        } else {177            ControlFlow::Continue(())178        }179    })180    .is_some()181}182183pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool {184    for_each_expr_without_closures(expression, |e| {185        match e.kind {186            ExprKind::Ret(..) | ExprKind::Break(..) | ExprKind::Continue(..) => ControlFlow::Break(()),187            // Something special could be done here to handle while or for loop188            // desugaring, as this will detect a break if there's a while loop189            // or a for loop inside the expression.190            _ if e.span.from_expansion() => ControlFlow::Break(()),191            _ => ControlFlow::Continue(()),192        }193    })194    .is_some()195}196197pub fn local_used_in<'tcx>(cx: &LateContext<'tcx>, local_id: HirId, v: impl Visitable<'tcx>) -> bool {198    for_each_expr(cx, v, |e| {199        if e.res_local_id() == Some(local_id) {200            ControlFlow::Break(())201        } else {202            ControlFlow::Continue(())203        }204    })205    .is_some()206}207208pub fn local_used_after_expr(cx: &LateContext<'_>, local_id: HirId, after: &Expr<'_>) -> bool {209    let Some(block) = utils::get_enclosing_block(cx, local_id) else {210        return false;211    };212213    // for _ in 1..3 {214    //    local215    // }216    //217    // let closure = || local;218    // closure();219    // closure();220    let loop_start = get_enclosing_loop_or_multi_call_closure(cx, after).map(|e| e.hir_id);221222    let mut past_expr = false;223    for_each_expr(cx, block, |e| {224        if past_expr {225            if e.res_local_id() == Some(local_id) {226                ControlFlow::Break(())227            } else {228                ControlFlow::Continue(Descend::Yes)229            }230        } else if e.hir_id == after.hir_id {231            past_expr = true;232            ControlFlow::Continue(Descend::No)233        } else {234            past_expr = Some(e.hir_id) == loop_start;235            ControlFlow::Continue(Descend::Yes)236        }237    })238    .is_some()239}

Code quality findings 9

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 place.base {
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 cat.place.base {
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
hir_ids.push(*hir_id);
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
self.binding_hir_ids.push(hir_id);
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
/// Checks if the given expression is a macro call to `todo!()` or `unimplemented!()`.
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
/// Checks if the given expression is a stub, i.e., a `todo!()` or `unimplemented!()` expression,
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
/// or a block whose last expression is a `todo!()` or `unimplemented!()`.
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
/// Checks if the given expression contains macro call to `todo!()` or `unimplemented!()`.
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 e.kind {

Get this view in your editor

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