Skip to content

Commit ece3878

Browse files
committed
Auto merge of #11492 - GuillaumeGomez:async-fn-returned-closure, r=Centri3
Fix mutaby used async function argument in closure for `needless_pass_by_ref_mut` Fixes #11380. The problem was that it needed to go through closures as well in async functions to correctly find the mutable usage of async function arguments. changelog: Correctly handle mutable usage of async function arguments in closures. r? `@Centri3`
2 parents f464149 + b8b420c commit ece3878

File tree

3 files changed

+98
-20
lines changed

3 files changed

+98
-20
lines changed

clippy_lints/src/needless_pass_by_ref_mut.rs

+46-18
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use super::needless_pass_by_value::requires_exact_signature;
22
use clippy_utils::diagnostics::span_lint_hir_and_then;
33
use clippy_utils::source::snippet;
4+
use clippy_utils::visitors::for_each_expr_with_closures;
45
use clippy_utils::{get_parent_node, inherits_cfg, is_from_proc_macro, is_self};
56
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
67
use rustc_errors::Applicability;
@@ -9,7 +10,7 @@ use rustc_hir::{
910
Body, Closure, Expr, ExprKind, FnDecl, HirId, HirIdMap, HirIdSet, Impl, ItemKind, Mutability, Node, PatKind, QPath,
1011
};
1112
use rustc_hir_typeck::expr_use_visitor as euv;
12-
use rustc_infer::infer::TyCtxtInferExt;
13+
use rustc_infer::infer::{InferCtxt, TyCtxtInferExt};
1314
use rustc_lint::{LateContext, LateLintPass};
1415
use rustc_middle::hir::map::associated_body;
1516
use rustc_middle::hir::nested_filter::OnlyBodies;
@@ -21,6 +22,8 @@ use rustc_span::symbol::kw;
2122
use rustc_span::Span;
2223
use rustc_target::spec::abi::Abi;
2324

25+
use core::ops::ControlFlow;
26+
2427
declare_clippy_lint! {
2528
/// ### What it does
2629
/// Check if a `&mut` function argument is actually used mutably.
@@ -95,6 +98,30 @@ fn should_skip<'tcx>(
9598
is_from_proc_macro(cx, &input)
9699
}
97100

101+
fn check_closures<'tcx>(
102+
ctx: &mut MutablyUsedVariablesCtxt<'tcx>,
103+
cx: &LateContext<'tcx>,
104+
infcx: &InferCtxt<'tcx>,
105+
checked_closures: &mut FxHashSet<LocalDefId>,
106+
closures: FxHashSet<LocalDefId>,
107+
) {
108+
let hir = cx.tcx.hir();
109+
for closure in closures {
110+
if !checked_closures.insert(closure) {
111+
continue;
112+
}
113+
ctx.prev_bind = None;
114+
ctx.prev_move_to_closure.clear();
115+
if let Some(body) = hir
116+
.find_by_def_id(closure)
117+
.and_then(associated_body)
118+
.map(|(_, body_id)| hir.body(body_id))
119+
{
120+
euv::ExprUseVisitor::new(ctx, infcx, closure, cx.param_env, cx.typeck_results()).consume_body(body);
121+
}
122+
}
123+
}
124+
98125
impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> {
99126
fn check_fn(
100127
&mut self,
@@ -161,25 +188,22 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> {
161188
euv::ExprUseVisitor::new(&mut ctx, &infcx, fn_def_id, cx.param_env, cx.typeck_results()).consume_body(body);
162189
if is_async {
163190
let mut checked_closures = FxHashSet::default();
191+
192+
// We retrieve all the closures declared in the async function because they will
193+
// not be found by `euv::Delegate`.
194+
let mut closures: FxHashSet<LocalDefId> = FxHashSet::default();
195+
for_each_expr_with_closures(cx, body, |expr| {
196+
if let ExprKind::Closure(closure) = expr.kind {
197+
closures.insert(closure.def_id);
198+
}
199+
ControlFlow::<()>::Continue(())
200+
});
201+
check_closures(&mut ctx, cx, &infcx, &mut checked_closures, closures);
202+
164203
while !ctx.async_closures.is_empty() {
165-
let closures = ctx.async_closures.clone();
204+
let async_closures = ctx.async_closures.clone();
166205
ctx.async_closures.clear();
167-
let hir = cx.tcx.hir();
168-
for closure in closures {
169-
if !checked_closures.insert(closure) {
170-
continue;
171-
}
172-
ctx.prev_bind = None;
173-
ctx.prev_move_to_closure.clear();
174-
if let Some(body) = hir
175-
.find_by_def_id(closure)
176-
.and_then(associated_body)
177-
.map(|(_, body_id)| hir.body(body_id))
178-
{
179-
euv::ExprUseVisitor::new(&mut ctx, &infcx, closure, cx.param_env, cx.typeck_results())
180-
.consume_body(body);
181-
}
182-
}
206+
check_closures(&mut ctx, cx, &infcx, &mut checked_closures, async_closures);
183207
}
184208
}
185209
ctx
@@ -244,6 +268,10 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> {
244268
struct MutablyUsedVariablesCtxt<'tcx> {
245269
mutably_used_vars: HirIdSet,
246270
prev_bind: Option<HirId>,
271+
/// In async functions, the inner AST is composed of multiple layers until we reach the code
272+
/// defined by the user. Because of that, some variables are marked as mutably borrowed even
273+
/// though they're not. This field lists the `HirId` that should not be considered as mutable
274+
/// use of a variable.
247275
prev_move_to_closure: HirIdSet,
248276
aliases: HirIdMap<HirId>,
249277
async_closures: FxHashSet<LocalDefId>,

tests/ui/needless_pass_by_ref_mut.rs

+27-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#![allow(clippy::if_same_then_else, clippy::no_effect)]
1+
#![allow(clippy::if_same_then_else, clippy::no_effect, clippy::redundant_closure_call)]
22
#![feature(lint_reasons)]
33
//@no-rustfix
44
use std::ptr::NonNull;
@@ -231,6 +231,32 @@ async fn async_vec2(b: &mut Vec<bool>) {
231231
b.push(true);
232232
}
233233

234+
// Should not warn.
235+
pub async fn closure(n: &mut usize) -> impl '_ + FnMut() {
236+
|| {
237+
*n += 1;
238+
}
239+
}
240+
241+
// Should warn.
242+
pub fn closure2(n: &mut usize) -> impl '_ + FnMut() -> usize {
243+
//~^ ERROR: this argument is a mutable reference, but not used mutably
244+
|| *n + 1
245+
}
246+
247+
// Should not warn.
248+
pub async fn closure3(n: &mut usize) {
249+
(|| *n += 1)();
250+
}
251+
252+
// Should warn.
253+
pub async fn closure4(n: &mut usize) {
254+
//~^ ERROR: this argument is a mutable reference, but not used mutably
255+
(|| {
256+
let _x = *n + 1;
257+
})();
258+
}
259+
234260
fn main() {
235261
let mut u = 0;
236262
let mut v = vec![0];

tests/ui/needless_pass_by_ref_mut.stderr

+25-1
Original file line numberDiff line numberDiff line change
@@ -107,5 +107,29 @@ error: this argument is a mutable reference, but not used mutably
107107
LL | async fn inner_async3(x: &mut i32, y: &mut u32) {
108108
| ^^^^^^^^ help: consider changing to: `&i32`
109109

110-
error: aborting due to 17 previous errors
110+
error: this argument is a mutable reference, but not used mutably
111+
--> $DIR/needless_pass_by_ref_mut.rs:235:25
112+
|
113+
LL | pub async fn closure(n: &mut usize) -> impl '_ + FnMut() {
114+
| ^^^^^^^^^^ help: consider changing to: `&usize`
115+
|
116+
= warning: changing this function will impact semver compatibility
117+
118+
error: this argument is a mutable reference, but not used mutably
119+
--> $DIR/needless_pass_by_ref_mut.rs:242:20
120+
|
121+
LL | pub fn closure2(n: &mut usize) -> impl '_ + FnMut() -> usize {
122+
| ^^^^^^^^^^ help: consider changing to: `&usize`
123+
|
124+
= warning: changing this function will impact semver compatibility
125+
126+
error: this argument is a mutable reference, but not used mutably
127+
--> $DIR/needless_pass_by_ref_mut.rs:253:26
128+
|
129+
LL | pub async fn closure4(n: &mut usize) {
130+
| ^^^^^^^^^^ help: consider changing to: `&usize`
131+
|
132+
= warning: changing this function will impact semver compatibility
133+
134+
error: aborting due to 20 previous errors
111135

0 commit comments

Comments
 (0)