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 borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, bk: ty::BorrowKind) {
70        if bk == ty::BorrowKind::Mutable {
71            self.update(cmt);
72        }
73    }
74
75    fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) {
76        self.update(cmt);
77    }
78
79    fn fake_read(&mut self, _: &PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
80}
81
82pub struct ParamBindingIdCollector {
83    pub binding_hir_ids: Vec<HirId>,
84}
85impl<'tcx> ParamBindingIdCollector {
86    fn collect_binding_hir_ids(body: &'tcx hir::Body<'tcx>) -> Vec<HirId> {
87        let mut hir_ids: Vec<HirId> = Vec::new();
88        for param in body.params {
89            let mut finder = ParamBindingIdCollector {
90                binding_hir_ids: Vec::new(),
91            };
92            finder.visit_param(param);
93            for hir_id in &finder.binding_hir_ids {
94                hir_ids.push(*hir_id);
95            }
96        }
97        hir_ids
98    }
99}
100impl<'tcx> Visitor<'tcx> for ParamBindingIdCollector {
101    fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) {
102        if let hir::PatKind::Binding(_, hir_id, ..) = pat.kind {
103            self.binding_hir_ids.push(hir_id);
104        }
105        intravisit::walk_pat(self, pat);
106    }
107}
108
109pub struct BindingUsageFinder<'a, 'tcx> {
110    cx: &'a LateContext<'tcx>,
111    binding_ids: Vec<HirId>,
112}
113impl<'a, 'tcx> BindingUsageFinder<'a, 'tcx> {
114    pub fn are_params_used(cx: &'a LateContext<'tcx>, body: &'tcx hir::Body<'tcx>) -> bool {
115        let mut finder = BindingUsageFinder {
116            cx,
117            binding_ids: ParamBindingIdCollector::collect_binding_hir_ids(body),
118        };
119        finder.visit_body(body).is_break()
120    }
121}
122impl<'tcx> Visitor<'tcx> for BindingUsageFinder<'_, 'tcx> {
123    type Result = ControlFlow<()>;
124    type NestedFilter = nested_filter::OnlyBodies;
125
126    fn visit_path(&mut self, path: &hir::Path<'tcx>, _: HirId) -> Self::Result {
127        if let Res::Local(id) = path.res {
128            if self.binding_ids.contains(&id) {
129                return ControlFlow::Break(());
130            }
131        }
132
133        ControlFlow::Continue(())
134    }
135
136    fn nested_visit_map(&mut self) -> Self::Map {
137        self.cx.tcx.hir()
138    }
139}
140
141pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool {
142    for_each_expr_without_closures(expression, |e| {
143        match e.kind {
144            ExprKind::Ret(..) | ExprKind::Break(..) | ExprKind::Continue(..) => ControlFlow::Break(()),
145            // Something special could be done here to handle while or for loop
146            // desugaring, as this will detect a break if there's a while loop
147            // or a for loop inside the expression.
148            _ if e.span.from_expansion() => ControlFlow::Break(()),
149            _ => ControlFlow::Continue(()),
150        }
151    })
152    .is_some()
153}
154
155pub fn local_used_in<'tcx>(cx: &LateContext<'tcx>, local_id: HirId, v: impl Visitable<'tcx>) -> bool {
156    for_each_expr(cx, v, |e| {
157        if utils::path_to_local_id(e, local_id) {
158            ControlFlow::Break(())
159        } else {
160            ControlFlow::Continue(())
161        }
162    })
163    .is_some()
164}
165
166pub fn local_used_after_expr(cx: &LateContext<'_>, local_id: HirId, after: &Expr<'_>) -> bool {
167    let Some(block) = utils::get_enclosing_block(cx, local_id) else {
168        return false;
169    };
170
171    // for _ in 1..3 {
172    //    local
173    // }
174    //
175    // let closure = || local;
176    // closure();
177    // closure();
178    let loop_start = get_enclosing_loop_or_multi_call_closure(cx, after).map(|e| e.hir_id);
179
180    let mut past_expr = false;
181    for_each_expr(cx, block, |e| {
182        if past_expr {
183            if utils::path_to_local_id(e, local_id) {
184                ControlFlow::Break(())
185            } else {
186                ControlFlow::Continue(Descend::Yes)
187            }
188        } else if e.hir_id == after.hir_id {
189            past_expr = true;
190            ControlFlow::Continue(Descend::No)
191        } else {
192            past_expr = Some(e.hir_id) == loop_start;
193            ControlFlow::Continue(Descend::Yes)
194        }
195    })
196    .is_some()
197}