Skip to content

Commit 43577d5

Browse files
committed
Auto merge of rust-lang#11184 - GuillaumeGomez:needless_pass_by_ref_mut-async, r=llogiq
Fix async functions handling for `needless_pass_by_ref_mut` lint Fixes rust-lang/rust-clippy#11179. The problem with async is that "internals" are actually inside a closure from the `ExprUseVisitor` point of view, meaning we need to actually run the check on the closures' body as well. changelog: none r? `@llogiq`
2 parents 356768b + 5e9e462 commit 43577d5

File tree

3 files changed

+196
-33
lines changed

3 files changed

+196
-33
lines changed

clippy_lints/src/needless_pass_by_ref_mut.rs

+67-22
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@ use clippy_utils::diagnostics::span_lint_and_then;
33
use clippy_utils::source::snippet;
44
use clippy_utils::{is_from_proc_macro, is_self};
55
use if_chain::if_chain;
6+
use rustc_data_structures::fx::FxHashSet;
67
use rustc_errors::Applicability;
78
use rustc_hir::intravisit::FnKind;
89
use rustc_hir::{Body, FnDecl, HirId, HirIdMap, HirIdSet, Impl, ItemKind, Mutability, Node, PatKind};
910
use rustc_hir_typeck::expr_use_visitor as euv;
1011
use rustc_infer::infer::TyCtxtInferExt;
1112
use rustc_lint::{LateContext, LateLintPass};
13+
use rustc_middle::hir::map::associated_body;
1214
use rustc_middle::mir::FakeReadCause;
13-
use rustc_middle::ty::{self, Ty};
15+
use rustc_middle::ty::{self, Ty, UpvarId, UpvarPath};
1416
use rustc_session::{declare_tool_lint, impl_lint_pass};
1517
use rustc_span::def_id::LocalDefId;
1618
use rustc_span::symbol::kw;
@@ -102,17 +104,17 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut {
102104
}
103105

104106
let hir_id = cx.tcx.hir().local_def_id_to_hir_id(fn_def_id);
105-
106-
match kind {
107+
let is_async = match kind {
107108
FnKind::ItemFn(.., header) => {
108109
let attrs = cx.tcx.hir().attrs(hir_id);
109110
if header.abi != Abi::Rust || requires_exact_signature(attrs) {
110111
return;
111112
}
113+
header.is_async()
112114
},
113-
FnKind::Method(..) => (),
115+
FnKind::Method(.., sig) => sig.header.is_async(),
114116
FnKind::Closure => return,
115-
}
117+
};
116118

117119
// Exclude non-inherent impls
118120
if let Some(Node::Item(item)) = cx.tcx.hir().find_parent(hir_id) {
@@ -128,13 +130,14 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut {
128130
let fn_sig = cx.tcx.liberate_late_bound_regions(fn_def_id.to_def_id(), fn_sig);
129131

130132
// If there are no `&mut` argument, no need to go any further.
131-
if !decl
133+
let mut it = decl
132134
.inputs
133135
.iter()
134136
.zip(fn_sig.inputs())
135137
.zip(body.params)
136-
.any(|((&input, &ty), arg)| !should_skip(cx, input, ty, arg))
137-
{
138+
.filter(|((&input, &ty), arg)| !should_skip(cx, input, ty, arg))
139+
.peekable();
140+
if it.peek().is_none() {
138141
return;
139142
}
140143

@@ -144,19 +147,25 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut {
144147
let mut ctx = MutablyUsedVariablesCtxt::default();
145148
let infcx = cx.tcx.infer_ctxt().build();
146149
euv::ExprUseVisitor::new(&mut ctx, &infcx, fn_def_id, cx.param_env, cx.typeck_results()).consume_body(body);
150+
if is_async {
151+
let closures = ctx.async_closures.clone();
152+
let hir = cx.tcx.hir();
153+
for closure in closures {
154+
ctx.prev_bind = None;
155+
ctx.prev_move_to_closure.clear();
156+
if let Some(body) = hir
157+
.find_by_def_id(closure)
158+
.and_then(associated_body)
159+
.map(|(_, body_id)| hir.body(body_id))
160+
{
161+
euv::ExprUseVisitor::new(&mut ctx, &infcx, closure, cx.param_env, cx.typeck_results())
162+
.consume_body(body);
163+
}
164+
}
165+
}
147166
ctx
148167
};
149168

150-
let mut it = decl
151-
.inputs
152-
.iter()
153-
.zip(fn_sig.inputs())
154-
.zip(body.params)
155-
.filter(|((&input, &ty), arg)| !should_skip(cx, input, ty, arg))
156-
.peekable();
157-
if it.peek().is_none() {
158-
return;
159-
}
160169
let show_semver_warning = self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(fn_def_id);
161170
for ((&input, &_), arg) in it {
162171
// Only take `&mut` arguments.
@@ -197,7 +206,9 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut {
197206
struct MutablyUsedVariablesCtxt {
198207
mutably_used_vars: HirIdSet,
199208
prev_bind: Option<HirId>,
209+
prev_move_to_closure: HirIdSet,
200210
aliases: HirIdMap<HirId>,
211+
async_closures: FxHashSet<LocalDefId>,
201212
}
202213

203214
impl MutablyUsedVariablesCtxt {
@@ -213,16 +224,27 @@ impl MutablyUsedVariablesCtxt {
213224
impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt {
214225
fn consume(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, _id: HirId) {
215226
if let euv::Place {
216-
base: euv::PlaceBase::Local(vid),
227+
base:
228+
euv::PlaceBase::Local(vid)
229+
| euv::PlaceBase::Upvar(UpvarId {
230+
var_path: UpvarPath { hir_id: vid },
231+
..
232+
}),
217233
base_ty,
218234
..
219235
} = &cmt.place
220236
{
221237
if let Some(bind_id) = self.prev_bind.take() {
222-
self.aliases.insert(bind_id, *vid);
223-
} else if matches!(base_ty.ref_mutability(), Some(Mutability::Mut)) {
238+
if bind_id != *vid {
239+
self.aliases.insert(bind_id, *vid);
240+
}
241+
} else if !self.prev_move_to_closure.contains(vid)
242+
&& matches!(base_ty.ref_mutability(), Some(Mutability::Mut))
243+
{
224244
self.add_mutably_used_var(*vid);
225245
}
246+
self.prev_bind = None;
247+
self.prev_move_to_closure.remove(vid);
226248
}
227249
}
228250

@@ -265,7 +287,30 @@ impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt {
265287
self.prev_bind = None;
266288
}
267289

268-
fn fake_read(&mut self, _: &rustc_hir_typeck::expr_use_visitor::PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
290+
fn fake_read(
291+
&mut self,
292+
cmt: &rustc_hir_typeck::expr_use_visitor::PlaceWithHirId<'tcx>,
293+
cause: FakeReadCause,
294+
_id: HirId,
295+
) {
296+
if let euv::Place {
297+
base:
298+
euv::PlaceBase::Upvar(UpvarId {
299+
var_path: UpvarPath { hir_id: vid },
300+
..
301+
}),
302+
..
303+
} = &cmt.place
304+
{
305+
if let FakeReadCause::ForLet(Some(inner)) = cause {
306+
// Seems like we are inside an async function. We need to store the closure `DefId`
307+
// to go through it afterwards.
308+
self.async_closures.insert(inner);
309+
self.aliases.insert(cmt.hir_id, *vid);
310+
self.prev_move_to_closure.insert(*vid);
311+
}
312+
}
313+
}
269314

270315
fn bind(&mut self, _cmt: &euv::PlaceWithHirId<'tcx>, id: HirId) {
271316
self.prev_bind = Some(id);

tests/ui/needless_pass_by_ref_mut.rs

+74-10
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,79 @@ impl<T> Mut<T> {
9191
// Should not warn.
9292
fn unused(_: &mut u32, _b: &mut u8) {}
9393

94+
// Should not warn.
95+
async fn f1(x: &mut i32) {
96+
*x += 1;
97+
}
98+
// Should not warn.
99+
async fn f2(x: &mut i32, y: String) {
100+
*x += 1;
101+
}
102+
// Should not warn.
103+
async fn f3(x: &mut i32, y: String, z: String) {
104+
*x += 1;
105+
}
106+
// Should not warn.
107+
async fn f4(x: &mut i32, y: i32) {
108+
*x += 1;
109+
}
110+
// Should not warn.
111+
async fn f5(x: i32, y: &mut i32) {
112+
*y += 1;
113+
}
114+
// Should not warn.
115+
async fn f6(x: i32, y: &mut i32, z: &mut i32) {
116+
*y += 1;
117+
*z += 1;
118+
}
119+
// Should not warn.
120+
async fn f7(x: &mut i32, y: i32, z: &mut i32, a: i32) {
121+
*x += 1;
122+
*z += 1;
123+
}
124+
125+
// Should warn.
126+
async fn a1(x: &mut i32) {
127+
println!("{:?}", x);
128+
}
129+
// Should warn.
130+
async fn a2(x: &mut i32, y: String) {
131+
println!("{:?}", x);
132+
}
133+
// Should warn.
134+
async fn a3(x: &mut i32, y: String, z: String) {
135+
println!("{:?}", x);
136+
}
137+
// Should warn.
138+
async fn a4(x: &mut i32, y: i32) {
139+
println!("{:?}", x);
140+
}
141+
// Should warn.
142+
async fn a5(x: i32, y: &mut i32) {
143+
println!("{:?}", x);
144+
}
145+
// Should warn.
146+
async fn a6(x: i32, y: &mut i32) {
147+
println!("{:?}", x);
148+
}
149+
// Should warn.
150+
async fn a7(x: i32, y: i32, z: &mut i32) {
151+
println!("{:?}", z);
152+
}
153+
// Should warn.
154+
async fn a8(x: i32, a: &mut i32, y: i32, z: &mut i32) {
155+
println!("{:?}", z);
156+
}
157+
94158
fn main() {
95-
let mut u = 0;
96-
let mut v = vec![0];
97-
foo(&mut v, &0, &mut u);
98-
foo2(&mut v);
99-
foo3(&mut v);
100-
foo4(&mut v);
101-
foo5(&mut v);
102-
alias_check(&mut v);
103-
alias_check2(&mut v);
104-
println!("{u}");
159+
// let mut u = 0;
160+
// let mut v = vec![0];
161+
// foo(&mut v, &0, &mut u);
162+
// foo2(&mut v);
163+
// foo3(&mut v);
164+
// foo4(&mut v);
165+
// foo5(&mut v);
166+
// alias_check(&mut v);
167+
// alias_check2(&mut v);
168+
// println!("{u}");
105169
}

tests/ui/needless_pass_by_ref_mut.stderr

+55-1
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,59 @@ error: this argument is a mutable reference, but not used mutably
2424
LL | fn badger(&mut self, vec: &mut Vec<i32>) -> usize {
2525
| ^^^^^^^^^^^^^ help: consider changing to: `&Vec<i32>`
2626

27-
error: aborting due to 4 previous errors
27+
error: this argument is a mutable reference, but not used mutably
28+
--> $DIR/needless_pass_by_ref_mut.rs:126:16
29+
|
30+
LL | async fn a1(x: &mut i32) {
31+
| ^^^^^^^^ help: consider changing to: `&i32`
32+
33+
error: this argument is a mutable reference, but not used mutably
34+
--> $DIR/needless_pass_by_ref_mut.rs:130:16
35+
|
36+
LL | async fn a2(x: &mut i32, y: String) {
37+
| ^^^^^^^^ help: consider changing to: `&i32`
38+
39+
error: this argument is a mutable reference, but not used mutably
40+
--> $DIR/needless_pass_by_ref_mut.rs:134:16
41+
|
42+
LL | async fn a3(x: &mut i32, y: String, z: String) {
43+
| ^^^^^^^^ help: consider changing to: `&i32`
44+
45+
error: this argument is a mutable reference, but not used mutably
46+
--> $DIR/needless_pass_by_ref_mut.rs:138:16
47+
|
48+
LL | async fn a4(x: &mut i32, y: i32) {
49+
| ^^^^^^^^ help: consider changing to: `&i32`
50+
51+
error: this argument is a mutable reference, but not used mutably
52+
--> $DIR/needless_pass_by_ref_mut.rs:142:24
53+
|
54+
LL | async fn a5(x: i32, y: &mut i32) {
55+
| ^^^^^^^^ help: consider changing to: `&i32`
56+
57+
error: this argument is a mutable reference, but not used mutably
58+
--> $DIR/needless_pass_by_ref_mut.rs:146:24
59+
|
60+
LL | async fn a6(x: i32, y: &mut i32) {
61+
| ^^^^^^^^ help: consider changing to: `&i32`
62+
63+
error: this argument is a mutable reference, but not used mutably
64+
--> $DIR/needless_pass_by_ref_mut.rs:150:32
65+
|
66+
LL | async fn a7(x: i32, y: i32, z: &mut i32) {
67+
| ^^^^^^^^ help: consider changing to: `&i32`
68+
69+
error: this argument is a mutable reference, but not used mutably
70+
--> $DIR/needless_pass_by_ref_mut.rs:154:24
71+
|
72+
LL | async fn a8(x: i32, a: &mut i32, y: i32, z: &mut i32) {
73+
| ^^^^^^^^ help: consider changing to: `&i32`
74+
75+
error: this argument is a mutable reference, but not used mutably
76+
--> $DIR/needless_pass_by_ref_mut.rs:154:45
77+
|
78+
LL | async fn a8(x: i32, a: &mut i32, y: i32, z: &mut i32) {
79+
| ^^^^^^^^ help: consider changing to: `&i32`
80+
81+
error: aborting due to 13 previous errors
2882

0 commit comments

Comments
 (0)