|
| 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