Skip to content

Commit 4c398e0

Browse files
committed
suggest &mut for redundant FnMut closures
1 parent 8d427b6 commit 4c398e0

File tree

5 files changed

+120
-5
lines changed

5 files changed

+120
-5
lines changed

clippy_lints/src/eta_reduction.rs

+16-4
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
2-
use clippy_utils::higher;
32
use clippy_utils::higher::VecArgs;
43
use clippy_utils::source::snippet_opt;
54
use clippy_utils::ty::{implements_trait, type_is_unsafe_function};
5+
use clippy_utils::usage::UsedAfterExprVisitor;
6+
use clippy_utils::{get_enclosing_loop_or_closure, higher};
67
use clippy_utils::{is_adjusted, iter_input_pats};
78
use if_chain::if_chain;
89
use rustc_errors::Applicability;
910
use rustc_hir::{def_id, Expr, ExprKind, Param, PatKind, QPath};
1011
use rustc_lint::{LateContext, LateLintPass, LintContext};
1112
use rustc_middle::lint::in_external_macro;
12-
use rustc_middle::ty::{self, Ty};
13+
use rustc_middle::ty::{self, ClosureKind, Ty};
1314
use rustc_session::{declare_lint_pass, declare_tool_lint};
1415

1516
declare_clippy_lint! {
@@ -86,7 +87,7 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
8687
}
8788
}
8889

89-
fn check_closure(cx: &LateContext<'_>, expr: &Expr<'_>) {
90+
fn check_closure<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
9091
if let ExprKind::Closure(_, decl, eid, _, _) = expr.kind {
9192
let body = cx.tcx.hir().body(eid);
9293
let ex = &body.value;
@@ -131,7 +132,18 @@ fn check_closure(cx: &LateContext<'_>, expr: &Expr<'_>) {
131132

132133
then {
133134
span_lint_and_then(cx, REDUNDANT_CLOSURE, expr.span, "redundant closure", |diag| {
134-
if let Some(snippet) = snippet_opt(cx, caller.span) {
135+
if let Some(mut snippet) = snippet_opt(cx, caller.span) {
136+
if_chain! {
137+
if let ty::Closure(_, substs) = fn_ty.kind();
138+
if let ClosureKind::FnMut = substs.as_closure().kind();
139+
if UsedAfterExprVisitor::is_found(cx, caller)
140+
|| get_enclosing_loop_or_closure(cx.tcx, expr).is_some();
141+
142+
then {
143+
// Mutable closure is used after current expr; we cannot consume it.
144+
snippet = format!("&mut {}", snippet);
145+
}
146+
}
135147
diag.span_suggestion(
136148
expr.span,
137149
"replace the closure with the function itself",

clippy_utils/src/usage.rs

+47
Original file line numberDiff line numberDiff line change
@@ -199,3 +199,50 @@ pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool {
199199
recursive_visitor.visit_expr(expression);
200200
recursive_visitor.seen_return_break_continue
201201
}
202+
203+
pub struct UsedAfterExprVisitor<'a, 'tcx> {
204+
cx: &'a LateContext<'tcx>,
205+
expr: &'tcx Expr<'tcx>,
206+
definition: HirId,
207+
past_expr: bool,
208+
used_after_expr: bool,
209+
}
210+
impl<'a, 'tcx> UsedAfterExprVisitor<'a, 'tcx> {
211+
pub fn is_found(cx: &'a LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
212+
utils::path_to_local(expr).map_or(false, |definition| {
213+
let mut visitor = UsedAfterExprVisitor {
214+
cx,
215+
expr,
216+
definition,
217+
past_expr: false,
218+
used_after_expr: false,
219+
};
220+
utils::get_enclosing_block(cx, definition).map_or(false, |block| {
221+
visitor.visit_block(block);
222+
visitor.used_after_expr
223+
})
224+
})
225+
}
226+
}
227+
228+
impl<'a, 'tcx> intravisit::Visitor<'tcx> for UsedAfterExprVisitor<'a, 'tcx> {
229+
type Map = Map<'tcx>;
230+
231+
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
232+
NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
233+
}
234+
235+
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
236+
if self.used_after_expr {
237+
return;
238+
}
239+
240+
if expr.hir_id == self.expr.hir_id {
241+
self.past_expr = true;
242+
} else if self.past_expr && utils::path_to_local_id(expr, self.definition) {
243+
self.used_after_expr = true;
244+
} else {
245+
intravisit::walk_expr(self, expr);
246+
}
247+
}
248+
}

tests/ui/eta.fixed

+16
Original file line numberDiff line numberDiff line change
@@ -220,3 +220,19 @@ impl std::ops::Deref for Bar {
220220
fn test_deref_with_trait_method() {
221221
let _ = [Bar].iter().map(|s| s.to_string()).collect::<Vec<_>>();
222222
}
223+
224+
fn mutable_closure_used_again(x: Vec<i32>, y: Vec<i32>, z: Vec<i32>) {
225+
let mut res = Vec::new();
226+
let mut add_to_res = |n| res.push(n);
227+
x.into_iter().for_each(&mut add_to_res);
228+
y.into_iter().for_each(&mut add_to_res);
229+
z.into_iter().for_each(add_to_res);
230+
}
231+
232+
fn mutable_closure_in_loop() {
233+
let mut value = 0;
234+
let mut closure = |n| value += n;
235+
for _ in 0..5 {
236+
Some(1).map(&mut closure);
237+
}
238+
}

tests/ui/eta.rs

+16
Original file line numberDiff line numberDiff line change
@@ -220,3 +220,19 @@ impl std::ops::Deref for Bar {
220220
fn test_deref_with_trait_method() {
221221
let _ = [Bar].iter().map(|s| s.to_string()).collect::<Vec<_>>();
222222
}
223+
224+
fn mutable_closure_used_again(x: Vec<i32>, y: Vec<i32>, z: Vec<i32>) {
225+
let mut res = Vec::new();
226+
let mut add_to_res = |n| res.push(n);
227+
x.into_iter().for_each(|x| add_to_res(x));
228+
y.into_iter().for_each(|x| add_to_res(x));
229+
z.into_iter().for_each(|x| add_to_res(x));
230+
}
231+
232+
fn mutable_closure_in_loop() {
233+
let mut value = 0;
234+
let mut closure = |n| value += n;
235+
for _ in 0..5 {
236+
Some(1).map(|n| closure(n));
237+
}
238+
}

tests/ui/eta.stderr

+25-1
Original file line numberDiff line numberDiff line change
@@ -82,5 +82,29 @@ error: redundant closure
8282
LL | let a = Some(1u8).map(|a| closure(a));
8383
| ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `closure`
8484

85-
error: aborting due to 13 previous errors
85+
error: redundant closure
86+
--> $DIR/eta.rs:227:28
87+
|
88+
LL | x.into_iter().for_each(|x| add_to_res(x));
89+
| ^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `&mut add_to_res`
90+
91+
error: redundant closure
92+
--> $DIR/eta.rs:228:28
93+
|
94+
LL | y.into_iter().for_each(|x| add_to_res(x));
95+
| ^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `&mut add_to_res`
96+
97+
error: redundant closure
98+
--> $DIR/eta.rs:229:28
99+
|
100+
LL | z.into_iter().for_each(|x| add_to_res(x));
101+
| ^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `add_to_res`
102+
103+
error: redundant closure
104+
--> $DIR/eta.rs:236:21
105+
|
106+
LL | Some(1).map(|n| closure(n));
107+
| ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `&mut closure`
108+
109+
error: aborting due to 17 previous errors
86110

0 commit comments

Comments
 (0)