Skip to main content

clippy_utils/
usage.rs

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