clippy_utils/
usage.rs

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};
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;
14use rustc_span::sym;
15
16/// Returns a set of mutated local variable IDs, or `None` if mutations could not be determined.
17pub fn mutated_variables<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> Option<HirIdSet> {
18    let mut delegate = MutVarsDelegate {
19        used_mutably: HirIdSet::default(),
20        skip: false,
21    };
22    ExprUseVisitor::for_clippy(cx, expr.hir_id.owner.def_id, &mut delegate)
23        .walk_expr(expr)
24        .into_ok();
25
26    if delegate.skip {
27        return None;
28    }
29    Some(delegate.used_mutably)
30}
31
32pub fn is_potentially_mutated<'tcx>(variable: HirId, expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> bool {
33    mutated_variables(expr, cx).is_none_or(|mutated| mutated.contains(&variable))
34}
35
36pub fn is_potentially_local_place(local_id: HirId, place: &Place<'_>) -> bool {
37    match place.base {
38        PlaceBase::Local(id) => id == local_id,
39        PlaceBase::Upvar(_) => {
40            // Conservatively assume yes.
41            true
42        },
43        _ => false,
44    }
45}
46
47struct MutVarsDelegate {
48    used_mutably: HirIdSet,
49    skip: bool,
50}
51
52impl MutVarsDelegate {
53    fn update(&mut self, cat: &PlaceWithHirId<'_>) {
54        match cat.place.base {
55            PlaceBase::Local(id) => {
56                self.used_mutably.insert(id);
57            },
58            PlaceBase::Upvar(_) => {
59                //FIXME: This causes false negatives. We can't get the `NodeId` from
60                //`Categorization::Upvar(_)`. So we search for any `Upvar`s in the
61                //`while`-body, not just the ones in the condition.
62                self.skip = true;
63            },
64            _ => {},
65        }
66    }
67}
68
69impl<'tcx> Delegate<'tcx> for MutVarsDelegate {
70    fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
71
72    fn use_cloned(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
73
74    fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, bk: ty::BorrowKind) {
75        if bk == ty::BorrowKind::Mutable {
76            self.update(cmt);
77        }
78    }
79
80    fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) {
81        self.update(cmt);
82    }
83
84    fn fake_read(&mut self, _: &PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
85}
86
87pub struct ParamBindingIdCollector {
88    pub binding_hir_ids: Vec<HirId>,
89}
90impl<'tcx> ParamBindingIdCollector {
91    fn collect_binding_hir_ids(body: &'tcx hir::Body<'tcx>) -> Vec<HirId> {
92        let mut hir_ids: Vec<HirId> = Vec::new();
93        for param in body.params {
94            let mut finder = ParamBindingIdCollector {
95                binding_hir_ids: Vec::new(),
96            };
97            finder.visit_param(param);
98            for hir_id in &finder.binding_hir_ids {
99                hir_ids.push(*hir_id);
100            }
101        }
102        hir_ids
103    }
104}
105impl<'tcx> Visitor<'tcx> for ParamBindingIdCollector {
106    fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) {
107        if let hir::PatKind::Binding(_, hir_id, ..) = pat.kind {
108            self.binding_hir_ids.push(hir_id);
109        }
110        intravisit::walk_pat(self, pat);
111    }
112}
113
114pub struct BindingUsageFinder<'a, 'tcx> {
115    cx: &'a LateContext<'tcx>,
116    binding_ids: Vec<HirId>,
117}
118impl<'a, 'tcx> BindingUsageFinder<'a, 'tcx> {
119    pub fn are_params_used(cx: &'a LateContext<'tcx>, body: &'tcx hir::Body<'tcx>) -> bool {
120        let mut finder = BindingUsageFinder {
121            cx,
122            binding_ids: ParamBindingIdCollector::collect_binding_hir_ids(body),
123        };
124        finder.visit_body(body).is_break()
125    }
126}
127impl<'tcx> Visitor<'tcx> for BindingUsageFinder<'_, 'tcx> {
128    type Result = ControlFlow<()>;
129    type NestedFilter = nested_filter::OnlyBodies;
130
131    fn visit_path(&mut self, path: &hir::Path<'tcx>, _: HirId) -> Self::Result {
132        if let Res::Local(id) = path.res
133            && self.binding_ids.contains(&id)
134        {
135            return ControlFlow::Break(());
136        }
137
138        ControlFlow::Continue(())
139    }
140
141    fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
142        self.cx.tcx
143    }
144}
145
146/// Checks if the given expression is a macro call to `todo!()` or `unimplemented!()`.
147pub fn is_todo_unimplemented_macro(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
148    root_macro_call_first_node(cx, expr)
149        .and_then(|macro_call| cx.tcx.get_diagnostic_name(macro_call.def_id))
150        .is_some_and(|macro_name| matches!(macro_name, sym::todo_macro | sym::unimplemented_macro))
151}
152
153/// Checks if the given expression is a stub, i.e., a `todo!()` or `unimplemented!()` expression,
154/// or a block whose last expression is a `todo!()` or `unimplemented!()`.
155pub fn is_todo_unimplemented_stub(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
156    if let ExprKind::Block(block, _) = expr.kind {
157        if let Some(last_expr) = block.expr {
158            return is_todo_unimplemented_macro(cx, last_expr);
159        }
160
161        return block.stmts.last().is_some_and(|stmt| {
162            if let hir::StmtKind::Expr(expr) | hir::StmtKind::Semi(expr) = stmt.kind {
163                return is_todo_unimplemented_macro(cx, expr);
164            }
165            false
166        });
167    }
168
169    is_todo_unimplemented_macro(cx, expr)
170}
171
172/// Checks if the given expression contains macro call to `todo!()` or `unimplemented!()`.
173pub fn contains_todo_unimplement_macro(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool {
174    for_each_expr_without_closures(expr, |e| {
175        if is_todo_unimplemented_macro(cx, e) {
176            ControlFlow::Break(())
177        } else {
178            ControlFlow::Continue(())
179        }
180    })
181    .is_some()
182}
183
184pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool {
185    for_each_expr_without_closures(expression, |e| {
186        match e.kind {
187            ExprKind::Ret(..) | ExprKind::Break(..) | ExprKind::Continue(..) => ControlFlow::Break(()),
188            // Something special could be done here to handle while or for loop
189            // desugaring, as this will detect a break if there's a while loop
190            // or a for loop inside the expression.
191            _ if e.span.from_expansion() => ControlFlow::Break(()),
192            _ => ControlFlow::Continue(()),
193        }
194    })
195    .is_some()
196}
197
198pub fn local_used_in<'tcx>(cx: &LateContext<'tcx>, local_id: HirId, v: impl Visitable<'tcx>) -> bool {
199    for_each_expr(cx, v, |e| {
200        if e.res_local_id() == Some(local_id) {
201            ControlFlow::Break(())
202        } else {
203            ControlFlow::Continue(())
204        }
205    })
206    .is_some()
207}
208
209pub fn local_used_after_expr(cx: &LateContext<'_>, local_id: HirId, after: &Expr<'_>) -> bool {
210    let Some(block) = utils::get_enclosing_block(cx, local_id) else {
211        return false;
212    };
213
214    // for _ in 1..3 {
215    //    local
216    // }
217    //
218    // let closure = || local;
219    // closure();
220    // closure();
221    let loop_start = get_enclosing_loop_or_multi_call_closure(cx, after).map(|e| e.hir_id);
222
223    let mut past_expr = false;
224    for_each_expr(cx, block, |e| {
225        if past_expr {
226            if e.res_local_id() == Some(local_id) {
227                ControlFlow::Break(())
228            } else {
229                ControlFlow::Continue(Descend::Yes)
230            }
231        } else if e.hir_id == after.hir_id {
232            past_expr = true;
233            ControlFlow::Continue(Descend::No)
234        } else {
235            past_expr = Some(e.hir_id) == loop_start;
236            ControlFlow::Continue(Descend::Yes)
237        }
238    })
239    .is_some()
240}