|
| 1 | +use crate::rustc::hir; |
1 | 2 | use crate::rustc::lint::{LateContext, LateLintPass, LintArray, LintPass}; |
2 | 3 | use crate::rustc::{declare_tool_lint, lint_array}; |
| 4 | +use crate::syntax::source_map::Span; |
| 5 | +use crate::utils::paths; |
| 6 | +use crate::utils::{ |
| 7 | + in_macro, match_trait_method, match_type, |
| 8 | + remove_blocks, snippet, |
| 9 | + span_lint_and_sugg, |
| 10 | +}; |
3 | 11 | use if_chain::if_chain; |
4 | | -use crate::rustc::hir::*; |
5 | | -use crate::rustc::ty; |
6 | | -use crate::syntax::ast; |
7 | | -use crate::utils::{get_arg_ident, is_adjusted, iter_input_pats, match_qpath, match_trait_method, match_type, |
8 | | - paths, remove_blocks, snippet, span_help_and_lint, walk_ptrs_ty, walk_ptrs_ty_depth, SpanlessEq}; |
| 12 | +use crate::syntax::ast::Ident; |
9 | 13 |
|
10 | | -/// **What it does:** Checks for mapping `clone()` over an iterator. |
| 14 | +#[derive(Clone)] |
| 15 | +pub struct Pass; |
| 16 | + |
| 17 | +/// **What it does:** Checks for usage of `iterator.map(|x| x.clone())` and suggests |
| 18 | +/// `iterator.cloned()` instead |
11 | 19 | /// |
12 | | -/// **Why is this bad?** It makes the code less readable than using the |
13 | | -/// `.cloned()` adapter. |
| 20 | +/// **Why is this bad?** Readability, this can be written more concisely |
14 | 21 | /// |
15 | | -/// **Known problems:** Sometimes `.cloned()` requires stricter trait |
16 | | -/// bound than `.map(|e| e.clone())` (which works because of the coercion). |
17 | | -/// See [#498](https://github.com/rust-lang-nursery/rust-clippy/issues/498). |
| 22 | +/// **Known problems:** None. |
18 | 23 | /// |
19 | 24 | /// **Example:** |
| 25 | +/// |
| 26 | +/// ```rust |
| 27 | +/// let x = vec![42, 43]; |
| 28 | +/// let y = x.iter(); |
| 29 | +/// let z = y.map(|i| *i); |
| 30 | +/// ``` |
| 31 | +/// |
| 32 | +/// The correct use would be: |
| 33 | +/// |
20 | 34 | /// ```rust |
21 | | -/// x.map(|e| e.clone()); |
| 35 | +/// let x = vec![42, 43]; |
| 36 | +/// let y = x.iter(); |
| 37 | +/// let z = y.cloned(); |
22 | 38 | /// ``` |
23 | 39 | declare_clippy_lint! { |
24 | 40 | pub MAP_CLONE, |
25 | 41 | style, |
26 | | - "using `.map(|x| x.clone())` to clone an iterator or option's contents" |
| 42 | + "using `iterator.map(|x| x.clone())`, or dereferencing closures for `Copy` types" |
27 | 43 | } |
28 | 44 |
|
29 | | -#[derive(Copy, Clone)] |
30 | | -pub struct Pass; |
| 45 | +impl LintPass for Pass { |
| 46 | + fn get_lints(&self) -> LintArray { |
| 47 | + lint_array!(MAP_CLONE) |
| 48 | + } |
| 49 | +} |
31 | 50 |
|
32 | 51 | impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass { |
33 | | - fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) { |
34 | | - // call to .map() |
35 | | - if let ExprKind::MethodCall(ref method, _, ref args) = expr.node { |
36 | | - if method.ident.name == "map" && args.len() == 2 { |
37 | | - match args[1].node { |
38 | | - ExprKind::Closure(_, ref decl, closure_eid, _, _) => { |
39 | | - let body = cx.tcx.hir.body(closure_eid); |
40 | | - let closure_expr = remove_blocks(&body.value); |
41 | | - if_chain! { |
42 | | - // nothing special in the argument, besides reference bindings |
43 | | - // (e.g. .map(|&x| x) ) |
44 | | - if let Some(first_arg) = iter_input_pats(decl, body).next(); |
45 | | - if let Some(arg_ident) = get_arg_ident(&first_arg.pat); |
46 | | - // the method is being called on a known type (option or iterator) |
47 | | - if let Some(type_name) = get_type_name(cx, expr, &args[0]); |
48 | | - then { |
49 | | - // We know that body.arguments is not empty at this point |
50 | | - let ty = cx.tables.pat_ty(&body.arguments[0].pat); |
51 | | - // look for derefs, for .map(|x| *x) |
52 | | - if only_derefs(cx, &*closure_expr, arg_ident) && |
53 | | - // .cloned() only removes one level of indirection, don't lint on more |
54 | | - walk_ptrs_ty_depth(cx.tables.pat_ty(&first_arg.pat)).1 == 1 |
55 | | - { |
56 | | - // the argument is not an &mut T |
57 | | - if let ty::Ref(_, _, mutbl) = ty.sty { |
58 | | - if mutbl == MutImmutable { |
59 | | - span_help_and_lint(cx, MAP_CLONE, expr.span, &format!( |
60 | | - "you seem to be using .map() to clone the contents of an {}, consider \ |
61 | | - using `.cloned()`", type_name), |
62 | | - &format!("try\n{}.cloned()", snippet(cx, args[0].span, ".."))); |
63 | | - } |
64 | | - } |
65 | | - } |
66 | | - // explicit clone() calls ( .map(|x| x.clone()) ) |
67 | | - else if let ExprKind::MethodCall(ref clone_call, _, ref clone_args) = closure_expr.node { |
68 | | - if clone_call.ident.name == "clone" && |
69 | | - clone_args.len() == 1 && |
70 | | - match_trait_method(cx, closure_expr, &paths::CLONE_TRAIT) && |
71 | | - expr_eq_name(cx, &clone_args[0], arg_ident) |
72 | | - { |
73 | | - span_help_and_lint(cx, MAP_CLONE, expr.span, &format!( |
74 | | - "you seem to be using .map() to clone the contents of an {}, consider \ |
75 | | - using `.cloned()`", type_name), |
76 | | - &format!("try\n{}.cloned()", snippet(cx, args[0].span, ".."))); |
77 | | - } |
78 | | - } |
79 | | - } |
80 | | - } |
| 52 | + fn check_expr(&mut self, cx: &LateContext<'_, '_>, e: &hir::Expr) { |
| 53 | + if in_macro(e.span) { |
| 54 | + return; |
| 55 | + } |
| 56 | + |
| 57 | + if_chain! { |
| 58 | + if let hir::ExprKind::MethodCall(ref method, _, ref args) = e.node; |
| 59 | + if args.len() == 2; |
| 60 | + if method.ident.as_str() == "map"; |
| 61 | + let ty = cx.tables.expr_ty(&args[0]); |
| 62 | + if match_type(cx, ty, &paths::OPTION) || match_trait_method(cx, e, &paths::ITERATOR); |
| 63 | + if let hir::ExprKind::Closure(_, _, body_id, _, _) = args[1].node; |
| 64 | + let closure_body = cx.tcx.hir.body(body_id); |
| 65 | + let closure_expr = remove_blocks(&closure_body.value); |
| 66 | + then { |
| 67 | + match closure_body.arguments[0].pat.node { |
| 68 | + hir::PatKind::Ref(ref inner, _) => if let hir::PatKind::Binding(hir::BindingAnnotation::Unannotated, _, name, None) = inner.node { |
| 69 | + lint(cx, e.span, args[0].span, name, closure_expr); |
81 | 70 | }, |
82 | | - ExprKind::Path(ref path) => if match_qpath(path, &paths::CLONE) { |
83 | | - let type_name = get_type_name(cx, expr, &args[0]).unwrap_or("_"); |
84 | | - span_help_and_lint( |
85 | | - cx, |
86 | | - MAP_CLONE, |
87 | | - expr.span, |
88 | | - &format!( |
89 | | - "you seem to be using .map() to clone the contents of an \ |
90 | | - {}, consider using `.cloned()`", |
91 | | - type_name |
92 | | - ), |
93 | | - &format!("try\n{}.cloned()", snippet(cx, args[0].span, "..")), |
94 | | - ); |
| 71 | + hir::PatKind::Binding(hir::BindingAnnotation::Unannotated, _, name, None) => match closure_expr.node { |
| 72 | + hir::ExprKind::Unary(hir::UnOp::UnDeref, ref inner) => lint(cx, e.span, args[0].span, name, inner), |
| 73 | + hir::ExprKind::MethodCall(ref method, _, ref obj) => if method.ident.as_str() == "clone" && match_trait_method(cx, closure_expr, &paths::CLONE_TRAIT) { |
| 74 | + lint(cx, e.span, args[0].span, name, &obj[0]); |
| 75 | + } |
| 76 | + _ => {}, |
95 | 77 | }, |
96 | | - _ => (), |
| 78 | + _ => {}, |
97 | 79 | } |
98 | 80 | } |
99 | 81 | } |
100 | 82 | } |
101 | 83 | } |
102 | 84 |
|
103 | | -fn expr_eq_name(cx: &LateContext<'_, '_>, expr: &Expr, id: ast::Ident) -> bool { |
104 | | - match expr.node { |
105 | | - ExprKind::Path(QPath::Resolved(None, ref path)) => { |
106 | | - let arg_segment = [ |
107 | | - PathSegment { |
108 | | - ident: id, |
109 | | - args: None, |
110 | | - infer_types: true, |
111 | | - }, |
112 | | - ]; |
113 | | - !path.is_global() && SpanlessEq::new(cx).eq_path_segments(&path.segments[..], &arg_segment) |
114 | | - }, |
115 | | - _ => false, |
116 | | - } |
117 | | -} |
118 | | - |
119 | | -fn get_type_name(cx: &LateContext<'_, '_>, expr: &Expr, arg: &Expr) -> Option<&'static str> { |
120 | | - if match_trait_method(cx, expr, &paths::ITERATOR) { |
121 | | - Some("iterator") |
122 | | - } else if match_type(cx, walk_ptrs_ty(cx.tables.expr_ty(arg)), &paths::OPTION) { |
123 | | - Some("Option") |
124 | | - } else { |
125 | | - None |
126 | | - } |
127 | | -} |
128 | | - |
129 | | -fn only_derefs(cx: &LateContext<'_, '_>, expr: &Expr, id: ast::Ident) -> bool { |
130 | | - match expr.node { |
131 | | - ExprKind::Unary(UnDeref, ref subexpr) if !is_adjusted(cx, subexpr) => only_derefs(cx, subexpr, id), |
132 | | - _ => expr_eq_name(cx, expr, id), |
133 | | - } |
134 | | -} |
135 | | - |
136 | | -impl LintPass for Pass { |
137 | | - fn get_lints(&self) -> LintArray { |
138 | | - lint_array!(MAP_CLONE) |
| 85 | +fn lint(cx: &LateContext<'_, '_>, replace: Span, root: Span, name: Ident, path: &hir::Expr) { |
| 86 | + if let hir::ExprKind::Path(hir::QPath::Resolved(None, ref path)) = path.node { |
| 87 | + if path.segments.len() == 1 && path.segments[0].ident == name { |
| 88 | + span_lint_and_sugg( |
| 89 | + cx, |
| 90 | + MAP_CLONE, |
| 91 | + replace, |
| 92 | + "You are using an explicit closure for cloning elements", |
| 93 | + "Consider calling the dedicated `cloned` method", |
| 94 | + format!("{}.cloned()", snippet(cx, root, "..")), |
| 95 | + ) |
| 96 | + } |
139 | 97 | } |
140 | 98 | } |
0 commit comments