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
13pub 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 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 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 _ 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 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}