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
15pub 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 {
34 mutated_variables(expr, cx).is_none_or(|mutated| mutated.contains(&variable))
35}
36
37pub 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 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 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
89pub struct ParamBindingIdCollector {
91 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
118pub struct BindingUsageFinder<'a, 'tcx> {
121 cx: &'a LateContext<'tcx>,
122 binding_ids: Vec<HirId>,
123}
124impl<'a, 'tcx> BindingUsageFinder<'a, 'tcx> {
125 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
153pub 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
160pub 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
179pub 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
191pub 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 _ if e.span.from_expansion() => ControlFlow::Break(()),
201 _ => ControlFlow::Continue(()),
202 }
203 })
204 .is_some()
205}
206
207pub 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
219pub 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 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}