1use crate::macros::root_macro_call_first_node;
2use crate::visitors::{Descend, Visitable, for_each_expr, for_each_expr_without_closures};
3use crate::{self as utils, get_enclosing_loop_or_multi_call_closure};
4use core::ops::ControlFlow;
5use hir::def::Res;
6use rustc_hir::intravisit::{self, Visitor};
7use rustc_hir::{self as hir, Expr, ExprKind, HirId, HirIdSet};
8use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, Place, PlaceBase, PlaceWithHirId};
9use rustc_lint::LateContext;
10use rustc_middle::hir::nested_filter;
11use rustc_middle::mir::FakeReadCause;
12use rustc_middle::ty;
13use rustc_span::sym;
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 {
32 mutated_variables(expr, cx).is_none_or(|mutated| mutated.contains(&variable))
33}
34
35pub fn is_potentially_local_place(local_id: HirId, place: &Place<'_>) -> bool {
36 match place.base {
37 PlaceBase::Local(id) => id == local_id,
38 PlaceBase::Upvar(_) => {
39 true
41 },
42 _ => false,
43 }
44}
45
46struct MutVarsDelegate {
47 used_mutably: HirIdSet,
48 skip: bool,
49}
50
51impl MutVarsDelegate {
52 fn update(&mut self, cat: &PlaceWithHirId<'_>) {
53 match cat.place.base {
54 PlaceBase::Local(id) => {
55 self.used_mutably.insert(id);
56 },
57 PlaceBase::Upvar(_) => {
58 self.skip = true;
62 },
63 _ => {},
64 }
65 }
66}
67
68impl<'tcx> Delegate<'tcx> for MutVarsDelegate {
69 fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
70
71 fn use_cloned(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
72
73 fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, bk: ty::BorrowKind) {
74 if bk == ty::BorrowKind::Mutable {
75 self.update(cmt);
76 }
77 }
78
79 fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) {
80 self.update(cmt);
81 }
82
83 fn fake_read(&mut self, _: &PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
84}
85
86pub struct ParamBindingIdCollector {
87 pub binding_hir_ids: Vec<HirId>,
88}
89impl<'tcx> ParamBindingIdCollector {
90 fn collect_binding_hir_ids(body: &'tcx hir::Body<'tcx>) -> Vec<HirId> {
91 let mut hir_ids: Vec<HirId> = Vec::new();
92 for param in body.params {
93 let mut finder = ParamBindingIdCollector {
94 binding_hir_ids: Vec::new(),
95 };
96 finder.visit_param(param);
97 for hir_id in &finder.binding_hir_ids {
98 hir_ids.push(*hir_id);
99 }
100 }
101 hir_ids
102 }
103}
104impl<'tcx> Visitor<'tcx> for ParamBindingIdCollector {
105 fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) {
106 if let hir::PatKind::Binding(_, hir_id, ..) = pat.kind {
107 self.binding_hir_ids.push(hir_id);
108 }
109 intravisit::walk_pat(self, pat);
110 }
111}
112
113pub struct BindingUsageFinder<'a, 'tcx> {
114 cx: &'a LateContext<'tcx>,
115 binding_ids: Vec<HirId>,
116}
117impl<'a, 'tcx> BindingUsageFinder<'a, 'tcx> {
118 pub fn are_params_used(cx: &'a LateContext<'tcx>, body: &'tcx hir::Body<'tcx>) -> bool {
119 let mut finder = BindingUsageFinder {
120 cx,
121 binding_ids: ParamBindingIdCollector::collect_binding_hir_ids(body),
122 };
123 finder.visit_body(body).is_break()
124 }
125}
126impl<'tcx> Visitor<'tcx> for BindingUsageFinder<'_, 'tcx> {
127 type Result = ControlFlow<()>;
128 type NestedFilter = nested_filter::OnlyBodies;
129
130 fn visit_path(&mut self, path: &hir::Path<'tcx>, _: HirId) -> Self::Result {
131 if let Res::Local(id) = path.res
132 && self.binding_ids.contains(&id)
133 {
134 return ControlFlow::Break(());
135 }
136
137 ControlFlow::Continue(())
138 }
139
140 fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
141 self.cx.tcx
142 }
143}
144
145pub fn is_todo_unimplemented_macro(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
147 root_macro_call_first_node(cx, expr)
148 .and_then(|macro_call| cx.tcx.get_diagnostic_name(macro_call.def_id))
149 .is_some_and(|macro_name| matches!(macro_name, sym::todo_macro | sym::unimplemented_macro))
150}
151
152pub fn is_todo_unimplemented_stub(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
155 if let ExprKind::Block(block, _) = expr.kind {
156 if let Some(last_expr) = block.expr {
157 return is_todo_unimplemented_macro(cx, last_expr);
158 }
159
160 return block.stmts.last().is_some_and(|stmt| {
161 if let hir::StmtKind::Expr(expr) | hir::StmtKind::Semi(expr) = stmt.kind {
162 return is_todo_unimplemented_macro(cx, expr);
163 }
164 false
165 });
166 }
167
168 is_todo_unimplemented_macro(cx, expr)
169}
170
171pub fn contains_todo_unimplement_macro(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool {
173 for_each_expr_without_closures(expr, |e| {
174 if is_todo_unimplemented_macro(cx, e) {
175 ControlFlow::Break(())
176 } else {
177 ControlFlow::Continue(())
178 }
179 })
180 .is_some()
181}
182
183pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool {
184 for_each_expr_without_closures(expression, |e| {
185 match e.kind {
186 ExprKind::Ret(..) | ExprKind::Break(..) | ExprKind::Continue(..) => ControlFlow::Break(()),
187 _ if e.span.from_expansion() => ControlFlow::Break(()),
191 _ => ControlFlow::Continue(()),
192 }
193 })
194 .is_some()
195}
196
197pub fn local_used_in<'tcx>(cx: &LateContext<'tcx>, local_id: HirId, v: impl Visitable<'tcx>) -> bool {
198 for_each_expr(cx, v, |e| {
199 if utils::path_to_local_id(e, local_id) {
200 ControlFlow::Break(())
201 } else {
202 ControlFlow::Continue(())
203 }
204 })
205 .is_some()
206}
207
208pub fn local_used_after_expr(cx: &LateContext<'_>, local_id: HirId, after: &Expr<'_>) -> bool {
209 let Some(block) = utils::get_enclosing_block(cx, local_id) else {
210 return false;
211 };
212
213 let loop_start = get_enclosing_loop_or_multi_call_closure(cx, after).map(|e| e.hir_id);
221
222 let mut past_expr = false;
223 for_each_expr(cx, block, |e| {
224 if past_expr {
225 if utils::path_to_local_id(e, local_id) {
226 ControlFlow::Break(())
227 } else {
228 ControlFlow::Continue(Descend::Yes)
229 }
230 } else if e.hir_id == after.hir_id {
231 past_expr = true;
232 ControlFlow::Continue(Descend::No)
233 } else {
234 past_expr = Some(e.hir_id) == loop_start;
235 ControlFlow::Continue(Descend::Yes)
236 }
237 })
238 .is_some()
239}