clippy_utils/
usage.rs

1use crate::macros::root_macro_call_first_node;
2use crate::visitors::{Descend, Visitable, for_each_expr, for_each_expr_without_closures};
3use crate::{self as utils, get_enclosing_loop_or_multi_call_closure};
4use core::ops::ControlFlow;
5use hir::def::Res;
6use rustc_hir::intravisit::{self, Visitor};
7use rustc_hir::{self as hir, Expr, ExprKind, HirId, HirIdSet};
8use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, Place, PlaceBase, PlaceWithHirId};
9use rustc_lint::LateContext;
10use rustc_middle::hir::nested_filter;
11use rustc_middle::mir::FakeReadCause;
12use rustc_middle::ty;
13use rustc_span::sym;
14
15/// 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();
24
25    if delegate.skip {
26        return None;
27    }
28    Some(delegate.used_mutably)
29}
30
31pub 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}
34
35pub 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            true
41        },
42        _ => false,
43    }
44}
45
46struct MutVarsDelegate {
47    used_mutably: HirIdSet,
48    skip: bool,
49}
50
51impl 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` from
59                //`Categorization::Upvar(_)`. So we search for any `Upvar`s in the
60                //`while`-body, not just the ones in the condition.
61                self.skip = true;
62            },
63            _ => {},
64        }
65    }
66}
67
68impl<'tcx> Delegate<'tcx> for MutVarsDelegate {
69    fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
70
71    fn use_cloned(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
72
73    fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, bk: ty::BorrowKind) {
74        if bk == ty::BorrowKind::Mutable {
75            self.update(cmt);
76        }
77    }
78
79    fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) {
80        self.update(cmt);
81    }
82
83    fn fake_read(&mut self, _: &PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
84}
85
86pub 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_ids
102    }
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}
112
113pub 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;
129
130    fn visit_path(&mut self, path: &hir::Path<'tcx>, _: HirId) -> Self::Result {
131        if let Res::Local(id) = path.res
132            && self.binding_ids.contains(&id)
133        {
134            return ControlFlow::Break(());
135        }
136
137        ControlFlow::Continue(())
138    }
139
140    fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
141        self.cx.tcx
142    }
143}
144
145/// 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).is_some_and(|macro_call| {
148        [sym::todo_macro, sym::unimplemented_macro]
149            .iter()
150            .any(|&sym| cx.tcx.is_diagnostic_item(sym, macro_call.def_id))
151    })
152}
153
154/// Checks if the given expression is a stub, i.e., a `todo!()` or `unimplemented!()` expression,
155/// or a block whose last expression is a `todo!()` or `unimplemented!()`.
156pub fn is_todo_unimplemented_stub(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
157    if let ExprKind::Block(block, _) = expr.kind {
158        if let Some(last_expr) = block.expr {
159            return is_todo_unimplemented_macro(cx, last_expr);
160        }
161
162        return block.stmts.last().is_some_and(|stmt| {
163            if let hir::StmtKind::Expr(expr) | hir::StmtKind::Semi(expr) = stmt.kind {
164                return is_todo_unimplemented_macro(cx, expr);
165            }
166            false
167        });
168    }
169
170    is_todo_unimplemented_macro(cx, expr)
171}
172
173/// Checks if the given expression contains macro call to `todo!()` or `unimplemented!()`.
174pub fn contains_todo_unimplement_macro(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool {
175    for_each_expr_without_closures(expr, |e| {
176        if is_todo_unimplemented_macro(cx, e) {
177            ControlFlow::Break(())
178        } else {
179            ControlFlow::Continue(())
180        }
181    })
182    .is_some()
183}
184
185pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool {
186    for_each_expr_without_closures(expression, |e| {
187        match e.kind {
188            ExprKind::Ret(..) | ExprKind::Break(..) | ExprKind::Continue(..) => ControlFlow::Break(()),
189            // Something special could be done here to handle while or for loop
190            // desugaring, as this will detect a break if there's a while loop
191            // or a for loop inside the expression.
192            _ if e.span.from_expansion() => ControlFlow::Break(()),
193            _ => ControlFlow::Continue(()),
194        }
195    })
196    .is_some()
197}
198
199pub fn local_used_in<'tcx>(cx: &LateContext<'tcx>, local_id: HirId, v: impl Visitable<'tcx>) -> bool {
200    for_each_expr(cx, v, |e| {
201        if utils::path_to_local_id(e, local_id) {
202            ControlFlow::Break(())
203        } else {
204            ControlFlow::Continue(())
205        }
206    })
207    .is_some()
208}
209
210pub fn local_used_after_expr(cx: &LateContext<'_>, local_id: HirId, after: &Expr<'_>) -> bool {
211    let Some(block) = utils::get_enclosing_block(cx, local_id) else {
212        return false;
213    };
214
215    // for _ in 1..3 {
216    //    local
217    // }
218    //
219    // let closure = || local;
220    // closure();
221    // closure();
222    let loop_start = get_enclosing_loop_or_multi_call_closure(cx, after).map(|e| e.hir_id);
223
224    let mut past_expr = false;
225    for_each_expr(cx, block, |e| {
226        if past_expr {
227            if utils::path_to_local_id(e, local_id) {
228                ControlFlow::Break(())
229            } else {
230                ControlFlow::Continue(Descend::Yes)
231            }
232        } else if e.hir_id == after.hir_id {
233            past_expr = true;
234            ControlFlow::Continue(Descend::No)
235        } else {
236            past_expr = Some(e.hir_id) == loop_start;
237            ControlFlow::Continue(Descend::Yes)
238        }
239    })
240    .is_some()
241}