Skip to content

Commit b38c587

Browse files
author
Grzegorz
committed
redundant closure implemented for closures containing method calls
1 parent 5725726 commit b38c587

File tree

3 files changed

+219
-48
lines changed

3 files changed

+219
-48
lines changed

clippy_lints/src/eta_reduction.rs

Lines changed: 123 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
use crate::utils::{is_adjusted, iter_input_pats, snippet_opt, span_lint_and_then};
1+
use crate::utils::{is_adjusted, iter_input_pats, snippet_opt, span_lint_and_then, type_is_unsafe_function};
2+
use if_chain::if_chain;
23
use rustc::hir::*;
34
use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
45
use rustc::ty;
@@ -59,56 +60,136 @@ fn check_closure(cx: &LateContext<'_, '_>, expr: &Expr) {
5960
if let ExprKind::Closure(_, ref decl, eid, _, _) = expr.node {
6061
let body = cx.tcx.hir().body(eid);
6162
let ex = &body.value;
62-
if let ExprKind::Call(ref caller, ref args) = ex.node {
63-
if args.len() != decl.inputs.len() {
64-
// Not the same number of arguments, there
65-
// is no way the closure is the same as the function
66-
return;
67-
}
68-
if is_adjusted(cx, ex) || args.iter().any(|arg| is_adjusted(cx, arg)) {
69-
// Are the expression or the arguments type-adjusted? Then we need the closure
70-
return;
71-
}
63+
64+
if_chain!(
65+
if let ExprKind::Call(ref caller, ref args) = ex.node;
66+
67+
// Not the same number of arguments, there is no way the closure is the same as the function return;
68+
if args.len() == decl.inputs.len();
69+
70+
// Are the expression or the arguments type-adjusted? Then we need the closure
71+
if !(is_adjusted(cx, ex) || args.iter().any(|arg| is_adjusted(cx, arg)));
72+
7273
let fn_ty = cx.tables.expr_ty(caller);
73-
match fn_ty.sty {
74-
// Is it an unsafe function? They don't implement the closure traits
75-
ty::FnDef(..) | ty::FnPtr(_) => {
76-
let sig = fn_ty.fn_sig(cx.tcx);
77-
if sig.skip_binder().unsafety == Unsafety::Unsafe || sig.skip_binder().output().sty == ty::Never {
78-
return;
74+
if !type_is_unsafe_function(cx, fn_ty);
75+
76+
if compare_inputs(&mut iter_input_pats(decl, body), &mut args.into_iter());
77+
78+
then {
79+
span_lint_and_then(cx, REDUNDANT_CLOSURE, expr.span, "redundant closure found", |db| {
80+
if let Some(snippet) = snippet_opt(cx, caller.span) {
81+
db.span_suggestion(
82+
expr.span,
83+
"remove closure as shown",
84+
snippet,
85+
Applicability::MachineApplicable,
86+
);
7987
}
80-
},
81-
_ => (),
88+
});
8289
}
83-
for (a1, a2) in iter_input_pats(decl, body).zip(args) {
84-
if let PatKind::Binding(.., ident, _) = a1.pat.node {
85-
// XXXManishearth Should I be checking the binding mode here?
86-
if let ExprKind::Path(QPath::Resolved(None, ref p)) = a2.node {
87-
if p.segments.len() != 1 {
88-
// If it's a proper path, it can't be a local variable
89-
return;
90-
}
91-
if p.segments[0].ident.name != ident.name {
92-
// The two idents should be the same
93-
return;
94-
}
95-
} else {
96-
return;
97-
}
98-
} else {
99-
return;
100-
}
101-
}
102-
span_lint_and_then(cx, REDUNDANT_CLOSURE, expr.span, "redundant closure found", |db| {
103-
if let Some(snippet) = snippet_opt(cx, caller.span) {
90+
);
91+
92+
if_chain!(
93+
if let ExprKind::MethodCall(ref path, _, ref args) = ex.node;
94+
95+
// Not the same number of arguments, there is no way the closure is the same as the function return;
96+
if args.len() == decl.inputs.len();
97+
98+
// Are the expression or the arguments type-adjusted? Then we need the closure
99+
if !(is_adjusted(cx, ex) || args.iter().skip(1).any(|arg| is_adjusted(cx, arg)));
100+
101+
let method_def_id = cx.tables.type_dependent_defs()[ex.hir_id].def_id();
102+
if !type_is_unsafe_function(cx, cx.tcx.type_of(method_def_id));
103+
104+
if compare_inputs(&mut iter_input_pats(decl, body), &mut args.into_iter());
105+
106+
if let Some(name) = get_ufcs_type_name(cx, method_def_id, &args[0]);
107+
108+
then {
109+
span_lint_and_then(cx, REDUNDANT_CLOSURE, expr.span, "redundant closure found", |db| {
104110
db.span_suggestion(
105111
expr.span,
106112
"remove closure as shown",
107-
snippet,
113+
format!("{}::{}", name, path.ident.name),
108114
Applicability::MachineApplicable,
109115
);
116+
});
117+
}
118+
);
119+
}
120+
}
121+
122+
/// Tries to determine the type for universal function call to be used instead of the closure
123+
fn get_ufcs_type_name(
124+
cx: &LateContext<'_, '_>,
125+
method_def_id: def_id::DefId,
126+
self_arg: &Expr,
127+
) -> std::option::Option<String> {
128+
let expected_type_of_self = &cx.tcx.fn_sig(method_def_id).inputs_and_output().skip_binder()[0].sty;
129+
let actual_type_of_self = &cx.tables.node_id_to_type(self_arg.hir_id).sty;
130+
131+
if let Some(trait_id) = cx.tcx.trait_of_item(method_def_id) {
132+
//if the method expectes &self, ufcs requires explicit borrowing so closure can't be removed
133+
return match (expected_type_of_self, actual_type_of_self) {
134+
(ty::Ref(_, _, _), ty::Ref(_, _, _)) => Some(cx.tcx.item_path_str(trait_id)),
135+
(l, r) => match (l, r) {
136+
(ty::Ref(_, _, _), _) | (_, ty::Ref(_, _, _)) => None,
137+
(_, _) => Some(cx.tcx.item_path_str(trait_id)),
138+
},
139+
};
140+
}
141+
142+
cx.tcx.impl_of_method(method_def_id).and_then(|_| {
143+
//a type may implicitly implement other types methods (e.g. Deref)
144+
if match_types(expected_type_of_self, actual_type_of_self) {
145+
return Some(get_type_name(cx, &actual_type_of_self));
146+
}
147+
None
148+
})
149+
}
150+
151+
fn match_types(lhs: &ty::TyKind<'_>, rhs: &ty::TyKind<'_>) -> bool {
152+
match (lhs, rhs) {
153+
(ty::Bool, ty::Bool)
154+
| (ty::Char, ty::Char)
155+
| (ty::Int(_), ty::Int(_))
156+
| (ty::Uint(_), ty::Uint(_))
157+
| (ty::Str, ty::Str) => true,
158+
(ty::Ref(_, t1, _), ty::Ref(_, t2, _))
159+
| (ty::Array(t1, _), ty::Array(t2, _))
160+
| (ty::Slice(t1), ty::Slice(t2)) => match_types(&t1.sty, &t2.sty),
161+
(ty::Adt(def1, _), ty::Adt(def2, _)) => def1 == def2,
162+
(_, _) => false,
163+
}
164+
}
165+
166+
fn get_type_name(cx: &LateContext<'_, '_>, kind: &ty::TyKind<'_>) -> String {
167+
match kind {
168+
ty::Adt(t, _) => cx.tcx.item_path_str(t.did),
169+
ty::Ref(_, r, _) => get_type_name(cx, &r.sty),
170+
_ => kind.to_string(),
171+
}
172+
}
173+
174+
fn compare_inputs(closure_inputs: &mut dyn Iterator<Item = &Arg>, call_args: &mut dyn Iterator<Item = &Expr>) -> bool {
175+
for (closure_input, function_arg) in closure_inputs.zip(call_args) {
176+
if let PatKind::Binding(_, _, _, ident, _) = closure_input.pat.node {
177+
// XXXManishearth Should I be checking the binding mode here?
178+
if let ExprKind::Path(QPath::Resolved(None, ref p)) = function_arg.node {
179+
if p.segments.len() != 1 {
180+
// If it's a proper path, it can't be a local variable
181+
return false;
110182
}
111-
});
183+
if p.segments[0].ident.name != ident.name {
184+
// The two idents should be the same
185+
return false;
186+
}
187+
} else {
188+
return false;
189+
}
190+
} else {
191+
return false;
112192
}
113193
}
194+
true
114195
}

tests/ui/eta.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
)]
1010
#![warn(clippy::redundant_closure, clippy::needless_borrow)]
1111

12+
use std::path::PathBuf;
13+
1214
fn main() {
1315
let a = Some(1u8).map(|a| foo(a));
1416
meta(|a| foo(a));
@@ -26,6 +28,57 @@ fn main() {
2628
// See #515
2729
let a: Option<Box<::std::ops::Deref<Target = [i32]>>> =
2830
Some(vec![1i32, 2]).map(|v| -> Box<::std::ops::Deref<Target = [i32]>> { Box::new(v) });
31+
32+
}
33+
34+
trait TestTrait {
35+
fn trait_foo(self) -> bool;
36+
fn trait_foo_ref(&self) -> bool;
37+
}
38+
39+
struct TestStruct<'a> {
40+
some_ref: &'a i32
41+
}
42+
43+
impl<'a> TestStruct<'a> {
44+
fn foo(self) -> bool { false }
45+
unsafe fn foo_unsafe(self) -> bool { true }
46+
}
47+
48+
impl<'a> TestTrait for TestStruct<'a> {
49+
fn trait_foo(self) -> bool { false }
50+
fn trait_foo_ref(&self) -> bool { false }
51+
}
52+
53+
impl<'a> std::ops::Deref for TestStruct<'a> {
54+
type Target = char;
55+
fn deref(&self) -> &char { &'a' }
56+
}
57+
58+
fn test_redundant_closures_containing_method_calls() {
59+
let i = 10;
60+
let e = Some(TestStruct{some_ref: &i}).map(|a| a.foo());
61+
let e = Some(TestStruct{some_ref: &i}).map(TestStruct::foo);
62+
let e = Some(TestStruct{some_ref: &i}).map(|a| a.trait_foo());
63+
let e = Some(TestStruct{some_ref: &i}).map(|a| a.trait_foo_ref());
64+
let e = Some(TestStruct{some_ref: &i}).map(TestTrait::trait_foo);
65+
let e = Some(&mut vec!(1,2,3)).map(|v| v.clear());
66+
let e = Some(&mut vec!(1,2,3)).map(std::vec::Vec::clear);
67+
unsafe {
68+
let e = Some(TestStruct{some_ref: &i}).map(|a| a.foo_unsafe());
69+
}
70+
let e = Some("str").map(|s| s.to_string());
71+
let e = Some("str").map(str::to_string);
72+
let e = Some('a').map(|s| s.to_uppercase());
73+
let e = Some('a').map(char::to_uppercase);
74+
let e: std::vec::Vec<usize> = vec!('a','b','c').iter().map(|c| c.len_utf8()).collect();
75+
let e: std::vec::Vec<char> = vec!('a','b','c').iter().map(|c| c.to_ascii_uppercase()).collect();
76+
let e: std::vec::Vec<char> = vec!('a','b','c').iter().map(char::to_ascii_uppercase).collect();
77+
let p = Some(PathBuf::new());
78+
let e = p.as_ref().and_then(|s| s.to_str());
79+
//let e = p.as_ref().and_then(std::path::Path::to_str);
80+
let c = Some(TestStruct{some_ref: &i}).as_ref().map(|c| c.to_ascii_uppercase());
81+
//let c = Some(TestStruct{some_ref: &i}).as_ref().map(char::to_ascii_uppercase);
2982
}
3083

3184
fn meta<F>(f: F)
@@ -61,3 +114,4 @@ fn divergent(_: u8) -> ! {
61114
fn generic<T>(_: T) -> u8 {
62115
0
63116
}
117+

tests/ui/eta.stderr

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,72 @@
11
error: redundant closure found
2-
--> $DIR/eta.rs:13:27
2+
--> $DIR/eta.rs:15:27
33
|
44
LL | let a = Some(1u8).map(|a| foo(a));
55
| ^^^^^^^^^^ help: remove closure as shown: `foo`
66
|
77
= note: `-D clippy::redundant-closure` implied by `-D warnings`
88

99
error: redundant closure found
10-
--> $DIR/eta.rs:14:10
10+
--> $DIR/eta.rs:16:10
1111
|
1212
LL | meta(|a| foo(a));
1313
| ^^^^^^^^^^ help: remove closure as shown: `foo`
1414

1515
error: redundant closure found
16-
--> $DIR/eta.rs:15:27
16+
--> $DIR/eta.rs:17:27
1717
|
1818
LL | let c = Some(1u8).map(|a| {1+2; foo}(a));
1919
| ^^^^^^^^^^^^^^^^^ help: remove closure as shown: `{1+2; foo}`
2020

2121
error: this expression borrows a reference that is immediately dereferenced by the compiler
22-
--> $DIR/eta.rs:17:21
22+
--> $DIR/eta.rs:19:21
2323
|
2424
LL | all(&[1, 2, 3], &&2, |x, y| below(x, y)); //is adjusted
2525
| ^^^ help: change this to: `&2`
2626
|
2727
= note: `-D clippy::needless-borrow` implied by `-D warnings`
2828

2929
error: redundant closure found
30-
--> $DIR/eta.rs:24:27
30+
--> $DIR/eta.rs:26:27
3131
|
3232
LL | let e = Some(1u8).map(|a| generic(a));
3333
| ^^^^^^^^^^^^^^ help: remove closure as shown: `generic`
3434

35-
error: aborting due to 5 previous errors
35+
error: redundant closure found
36+
--> $DIR/eta.rs:60:48
37+
|
38+
LL | let e = Some(TestStruct{some_ref: &i}).map(|a| a.foo());
39+
| ^^^^^^^^^^^ help: remove closure as shown: `TestStruct::foo`
40+
41+
error: redundant closure found
42+
--> $DIR/eta.rs:62:48
43+
|
44+
LL | let e = Some(TestStruct{some_ref: &i}).map(|a| a.trait_foo());
45+
| ^^^^^^^^^^^^^^^^^ help: remove closure as shown: `TestTrait::trait_foo`
46+
47+
error: redundant closure found
48+
--> $DIR/eta.rs:65:40
49+
|
50+
LL | let e = Some(&mut vec!(1,2,3)).map(|v| v.clear());
51+
| ^^^^^^^^^^^^^ help: remove closure as shown: `std::vec::Vec::clear`
52+
53+
error: redundant closure found
54+
--> $DIR/eta.rs:70:29
55+
|
56+
LL | let e = Some("str").map(|s| s.to_string());
57+
| ^^^^^^^^^^^^^^^^^ help: remove closure as shown: `std::string::ToString::to_string`
58+
59+
error: redundant closure found
60+
--> $DIR/eta.rs:72:27
61+
|
62+
LL | let e = Some('a').map(|s| s.to_uppercase());
63+
| ^^^^^^^^^^^^^^^^^^^^ help: remove closure as shown: `char::to_uppercase`
64+
65+
error: redundant closure found
66+
--> $DIR/eta.rs:75:63
67+
|
68+
LL | let e: std::vec::Vec<char> = vec!('a','b','c').iter().map(|c| c.to_ascii_uppercase()).collect();
69+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove closure as shown: `char::to_ascii_uppercase`
70+
71+
error: aborting due to 11 previous errors
3672

0 commit comments

Comments
 (0)