Skip to content

Commit 364bdc5

Browse files
committed
Merge pull request #427 from wartman4404/master
Prefer `.cloned()` over `.map(|x| x.clone())`
2 parents 4b14096 + 764eedd commit 364bdc5

File tree

6 files changed

+203
-6
lines changed

6 files changed

+203
-6
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ A collection of lints to catch common mistakes and improve your Rust code.
66
[Jump to usage instructions](#usage)
77

88
##Lints
9-
There are 71 lints included in this crate:
9+
There are 72 lints included in this crate:
1010

1111
name | default | meaning
1212
-------------------------------------------------------------------------------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -34,6 +34,7 @@ name
3434
[let_and_return](https://github.com/Manishearth/rust-clippy/wiki#let_and_return) | warn | creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block
3535
[let_unit_value](https://github.com/Manishearth/rust-clippy/wiki#let_unit_value) | warn | creating a let binding to a value of unit type, which usually can't be used afterwards
3636
[linkedlist](https://github.com/Manishearth/rust-clippy/wiki#linkedlist) | warn | usage of LinkedList, usually a vector is faster, or a more specialized data structure like a VecDeque
37+
[map_clone](https://github.com/Manishearth/rust-clippy/wiki#map_clone) | warn | using `.map(|x| x.clone())` to clone an iterator or option's contents (recommends `.cloned()` instead)
3738
[match_bool](https://github.com/Manishearth/rust-clippy/wiki#match_bool) | warn | a match on boolean expression; recommends `if..else` block instead
3839
[match_ref_pats](https://github.com/Manishearth/rust-clippy/wiki#match_ref_pats) | warn | a match has all arms prefixed with `&`; the match expression can be dereferenced instead
3940
[min_max](https://github.com/Manishearth/rust-clippy/wiki#min_max) | warn | `min(_, max(_, _))` (or vice versa) with bounds clamping the result to a constant

src/eta_reduction.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use rustc::lint::*;
22
use rustc_front::hir::*;
33
use rustc::middle::ty;
44

5-
use utils::{snippet, span_lint};
5+
use utils::{snippet, span_lint, is_adjusted};
66

77

88
#[allow(missing_copy_implementations)]
@@ -32,10 +32,6 @@ impl LateLintPass for EtaPass {
3232
}
3333
}
3434

35-
fn is_adjusted(cx: &LateContext, e: &Expr) -> bool {
36-
cx.tcx.tables.borrow().adjustments.get(&e.id).is_some()
37-
}
38-
3935
fn check_closure(cx: &LateContext, expr: &Expr) {
4036
if let ExprClosure(_, ref decl, ref blk) = expr.node {
4137
if !blk.stmts.is_empty() {

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ pub mod returns;
4545
pub mod lifetimes;
4646
pub mod loops;
4747
pub mod ranges;
48+
pub mod map_clone;
4849
pub mod matches;
4950
pub mod precedence;
5051
pub mod mutex_atomic;
@@ -100,6 +101,7 @@ pub fn plugin_registrar(reg: &mut Registry) {
100101
reg.register_late_lint_pass(box needless_features::NeedlessFeaturesPass);
101102
reg.register_late_lint_pass(box needless_update::NeedlessUpdatePass);
102103
reg.register_late_lint_pass(box no_effect::NoEffectPass);
104+
reg.register_late_lint_pass(box map_clone::MapClonePass);
103105

104106
reg.register_lint_group("clippy_pedantic", vec![
105107
methods::OPTION_UNWRAP_USED,
@@ -141,6 +143,7 @@ pub fn plugin_registrar(reg: &mut Registry) {
141143
loops::UNUSED_COLLECT,
142144
loops::WHILE_LET_LOOP,
143145
loops::WHILE_LET_ON_ITERATOR,
146+
map_clone::MAP_CLONE,
144147
matches::MATCH_BOOL,
145148
matches::MATCH_REF_PATS,
146149
matches::SINGLE_MATCH,

src/map_clone.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
use rustc::lint::*;
2+
use rustc_front::hir::*;
3+
use syntax::ast::Ident;
4+
use utils::OPTION_PATH;
5+
use utils::{is_adjusted, match_trait_method, match_type, snippet, span_help_and_lint};
6+
use utils::{walk_ptrs_ty, walk_ptrs_ty_depth};
7+
8+
declare_lint!(pub MAP_CLONE, Warn,
9+
"using `.map(|x| x.clone())` to clone an iterator or option's contents (recommends \
10+
`.cloned()` instead)");
11+
12+
#[derive(Copy, Clone)]
13+
pub struct MapClonePass;
14+
15+
impl LateLintPass for MapClonePass {
16+
fn check_expr(&mut self, cx: &LateContext, expr: &Expr) {
17+
if_let_chain! {
18+
[
19+
// call to .map()
20+
let ExprMethodCall(name, _, ref args) = expr.node,
21+
name.node.as_str() == "map" && args.len() == 2,
22+
let ExprClosure(_, ref decl, ref blk) = args[1].node,
23+
// just one expression in the closure
24+
blk.stmts.is_empty(),
25+
let Some(ref closure_expr) = blk.expr,
26+
// nothing special in the argument, besides reference bindings
27+
// (e.g. .map(|&x| x) )
28+
let Some(arg_ident) = get_arg_name(&*decl.inputs[0].pat),
29+
// the method is being called on a known type (option or iterator)
30+
let Some(type_name) = get_type_name(cx, expr, &args[0])
31+
], {
32+
// look for derefs, for .map(|x| *x)
33+
if only_derefs(cx, &*closure_expr, arg_ident) &&
34+
// .cloned() only removes one level of indirection, don't lint on more
35+
walk_ptrs_ty_depth(cx.tcx.pat_ty(&*decl.inputs[0].pat)).1 == 1
36+
{
37+
span_help_and_lint(cx, MAP_CLONE, expr.span, &format!(
38+
"you seem to be using .map() to clone the contents of an {}, consider \
39+
using `.cloned()`", type_name),
40+
&format!("try\n{}.cloned()", snippet(cx, args[0].span, "..")));
41+
}
42+
// explicit clone() calls ( .map(|x| x.clone()) )
43+
else if let ExprMethodCall(clone_call, _, ref clone_args) = closure_expr.node {
44+
if clone_call.node.as_str() == "clone" &&
45+
clone_args.len() == 1 &&
46+
match_trait_method(cx, closure_expr, &["core", "clone", "Clone"]) &&
47+
expr_eq_ident(&clone_args[0], arg_ident)
48+
{
49+
span_help_and_lint(cx, MAP_CLONE, expr.span, &format!(
50+
"you seem to be using .map() to clone the contents of an {}, consider \
51+
using `.cloned()`", type_name),
52+
&format!("try\n{}.cloned()", snippet(cx, args[0].span, "..")));
53+
}
54+
}
55+
}
56+
}
57+
}
58+
}
59+
60+
fn expr_eq_ident(expr: &Expr, id: Ident) -> bool {
61+
match expr.node {
62+
ExprPath(None, ref path) => {
63+
let arg_segment = [PathSegment { identifier: id, parameters: PathParameters::none() }];
64+
!path.global && path.segments == arg_segment
65+
},
66+
_ => false,
67+
}
68+
}
69+
70+
fn get_type_name(cx: &LateContext, expr: &Expr, arg: &Expr) -> Option<&'static str> {
71+
if match_trait_method(cx, expr, &["core", "iter", "Iterator"]) {
72+
Some("iterator")
73+
} else if match_type(cx, walk_ptrs_ty(cx.tcx.expr_ty(arg)), &OPTION_PATH) {
74+
Some("Option")
75+
} else {
76+
None
77+
}
78+
}
79+
80+
fn get_arg_name(pat: &Pat) -> Option<Ident> {
81+
match pat.node {
82+
PatIdent(_, ident, None) => Some(ident.node),
83+
PatRegion(ref subpat, _) => get_arg_name(subpat),
84+
_ => None,
85+
}
86+
}
87+
88+
fn only_derefs(cx: &LateContext, expr: &Expr, id: Ident) -> bool {
89+
match expr.node {
90+
ExprUnary(UnDeref, ref subexpr) if !is_adjusted(cx, subexpr) => {
91+
only_derefs(cx, subexpr, id)
92+
},
93+
_ => expr_eq_ident(expr, id),
94+
}
95+
}
96+
97+
impl LintPass for MapClonePass {
98+
fn get_lints(&self) -> LintArray {
99+
lint_array!(MAP_CLONE)
100+
}
101+
}

src/utils.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,10 @@ pub fn is_integer_literal(expr: &Expr, value: u64) -> bool
347347
false
348348
}
349349

350+
pub fn is_adjusted(cx: &LateContext, e: &Expr) -> bool {
351+
cx.tcx.tables.borrow().adjustments.get(&e.id).is_some()
352+
}
353+
350354
/// Produce a nested chain of if-lets and ifs from the patterns:
351355
///
352356
/// if_let_chain! {

tests/compile-fail/map_clone.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#![feature(plugin)]
2+
3+
#![plugin(clippy)]
4+
#![deny(map_clone)]
5+
6+
#![allow(unused)]
7+
8+
use std::ops::Deref;
9+
10+
fn map_clone_iter() {
11+
let x = [1,2,3];
12+
x.iter().map(|y| y.clone()); //~ ERROR you seem to be using .map()
13+
//~^ HELP try
14+
x.iter().map(|&y| y); //~ ERROR you seem to be using .map()
15+
//~^ HELP try
16+
x.iter().map(|y| *y); //~ ERROR you seem to be using .map()
17+
//~^ HELP try
18+
}
19+
20+
fn map_clone_option() {
21+
let x = Some(4);
22+
x.as_ref().map(|y| y.clone()); //~ ERROR you seem to be using .map()
23+
//~^ HELP try
24+
x.as_ref().map(|&y| y); //~ ERROR you seem to be using .map()
25+
//~^ HELP try
26+
x.as_ref().map(|y| *y); //~ ERROR you seem to be using .map()
27+
//~^ HELP try
28+
}
29+
30+
fn not_linted_option() {
31+
let x = Some(5);
32+
33+
// Not linted: other statements
34+
x.as_ref().map(|y| {
35+
println!("y: {}", y);
36+
y.clone()
37+
});
38+
39+
// Not linted: argument bindings
40+
let x = Some((6, 7));
41+
x.map(|(y, _)| y.clone());
42+
43+
// Not linted: cloning something else
44+
x.map(|y| y.0.clone());
45+
46+
// Not linted: no dereferences
47+
x.map(|y| y);
48+
49+
// Not linted: multiple dereferences
50+
let _: Option<(i32, i32)> = x.as_ref().as_ref().map(|&&x| x);
51+
}
52+
53+
#[derive(Copy, Clone)]
54+
struct Wrapper<T>(T);
55+
impl<T> Wrapper<T> {
56+
fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Wrapper<U> {
57+
Wrapper(f(self.0))
58+
}
59+
}
60+
61+
fn map_clone_other() {
62+
let eight = 8;
63+
let x = Wrapper(&eight);
64+
65+
// Not linted: not a linted type
66+
x.map(|y| y.clone());
67+
x.map(|&y| y);
68+
x.map(|y| *y);
69+
}
70+
71+
#[derive(Copy, Clone)]
72+
struct UnusualDeref;
73+
static NINE: i32 = 9;
74+
75+
impl Deref for UnusualDeref {
76+
type Target = i32;
77+
fn deref(&self) -> &i32 { &NINE }
78+
}
79+
80+
fn map_clone_deref() {
81+
let x = Some(UnusualDeref);
82+
let _: Option<UnusualDeref> = x.as_ref().map(|y| *y); //~ ERROR you seem to be using .map()
83+
//~^ HELP try
84+
85+
// Not linted: using deref conversion
86+
let _: Option<i32> = x.map(|y| *y);
87+
88+
// Not linted: using regular deref but also deref conversion
89+
let _: Option<i32> = x.as_ref().map(|y| **y);
90+
}
91+
92+
fn main() { }

0 commit comments

Comments
 (0)