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.
match place.base {
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}
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.