clippy_utils/
usage.rs

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