]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
1 | use crate::utils::paths; |
2 | use crate::utils::{ | |
3 | is_copy, is_type_diagnostic_item, match_trait_method, remove_blocks, snippet_with_applicability, span_lint_and_sugg, | |
4 | }; | |
5 | use if_chain::if_chain; | |
6 | use rustc_errors::Applicability; | |
7 | use rustc_hir as hir; | |
8 | use rustc_lint::{LateContext, LateLintPass}; | |
9 | use rustc_middle::mir::Mutability; | |
10 | use rustc_middle::ty; | |
11 | use rustc_middle::ty::adjustment::Adjust; | |
12 | use rustc_session::{declare_lint_pass, declare_tool_lint}; | |
13 | use rustc_span::symbol::Ident; | |
14 | use rustc_span::{sym, Span}; | |
15 | ||
16 | declare_clippy_lint! { | |
17 | /// **What it does:** Checks for usage of `map(|x| x.clone())` or | |
18 | /// dereferencing closures for `Copy` types, on `Iterator` or `Option`, | |
19 | /// and suggests `cloned()` or `copied()` instead | |
20 | /// | |
21 | /// **Why is this bad?** Readability, this can be written more concisely | |
22 | /// | |
23 | /// **Known problems:** None | |
24 | /// | |
25 | /// **Example:** | |
26 | /// | |
27 | /// ```rust | |
28 | /// let x = vec![42, 43]; | |
29 | /// let y = x.iter(); | |
30 | /// let z = y.map(|i| *i); | |
31 | /// ``` | |
32 | /// | |
33 | /// The correct use would be: | |
34 | /// | |
35 | /// ```rust | |
36 | /// let x = vec![42, 43]; | |
37 | /// let y = x.iter(); | |
38 | /// let z = y.cloned(); | |
39 | /// ``` | |
40 | pub MAP_CLONE, | |
41 | style, | |
42 | "using `iterator.map(|x| x.clone())`, or dereferencing closures for `Copy` types" | |
43 | } | |
44 | ||
45 | declare_lint_pass!(MapClone => [MAP_CLONE]); | |
46 | ||
47 | impl<'tcx> LateLintPass<'tcx> for MapClone { | |
48 | fn check_expr(&mut self, cx: &LateContext<'_>, e: &hir::Expr<'_>) { | |
49 | if e.span.from_expansion() { | |
50 | return; | |
51 | } | |
52 | ||
53 | if_chain! { | |
54 | if let hir::ExprKind::MethodCall(ref method, _, ref args, _) = e.kind; | |
55 | if args.len() == 2; | |
56 | if method.ident.name == sym::map; | |
57 | let ty = cx.typeck_results().expr_ty(&args[0]); | |
58 | if is_type_diagnostic_item(cx, ty, sym::option_type) || match_trait_method(cx, e, &paths::ITERATOR); | |
59 | if let hir::ExprKind::Closure(_, _, body_id, _, _) = args[1].kind; | |
60 | let closure_body = cx.tcx.hir().body(body_id); | |
61 | let closure_expr = remove_blocks(&closure_body.value); | |
62 | then { | |
63 | match closure_body.params[0].pat.kind { | |
64 | hir::PatKind::Ref(ref inner, hir::Mutability::Not) => if let hir::PatKind::Binding( | |
65 | hir::BindingAnnotation::Unannotated, .., name, None | |
66 | ) = inner.kind { | |
67 | if ident_eq(name, closure_expr) { | |
68 | lint(cx, e.span, args[0].span, true); | |
69 | } | |
70 | }, | |
71 | hir::PatKind::Binding(hir::BindingAnnotation::Unannotated, .., name, None) => { | |
72 | match closure_expr.kind { | |
73 | hir::ExprKind::Unary(hir::UnOp::Deref, ref inner) => { | |
74 | if ident_eq(name, inner) { | |
75 | if let ty::Ref(.., Mutability::Not) = cx.typeck_results().expr_ty(inner).kind() { | |
76 | lint(cx, e.span, args[0].span, true); | |
77 | } | |
78 | } | |
79 | }, | |
80 | hir::ExprKind::MethodCall(ref method, _, [obj], _) => if_chain! { | |
81 | if ident_eq(name, obj) && method.ident.name == sym::clone; | |
82 | if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id); | |
83 | if let Some(trait_id) = cx.tcx.trait_of_item(fn_id); | |
84 | if cx.tcx.lang_items().clone_trait().map_or(false, |id| id == trait_id); | |
85 | // no autoderefs | |
86 | if !cx.typeck_results().expr_adjustments(obj).iter() | |
87 | .any(|a| matches!(a.kind, Adjust::Deref(Some(..)))); | |
88 | then { | |
89 | let obj_ty = cx.typeck_results().expr_ty(obj); | |
90 | if let ty::Ref(_, ty, mutability) = obj_ty.kind() { | |
91 | if matches!(mutability, Mutability::Not) { | |
92 | let copy = is_copy(cx, ty); | |
93 | lint(cx, e.span, args[0].span, copy); | |
94 | } | |
95 | } else { | |
96 | lint_needless_cloning(cx, e.span, args[0].span); | |
97 | } | |
98 | } | |
99 | }, | |
100 | _ => {}, | |
101 | } | |
102 | }, | |
103 | _ => {}, | |
104 | } | |
105 | } | |
106 | } | |
107 | } | |
108 | } | |
109 | ||
110 | fn ident_eq(name: Ident, path: &hir::Expr<'_>) -> bool { | |
111 | if let hir::ExprKind::Path(hir::QPath::Resolved(None, ref path)) = path.kind { | |
112 | path.segments.len() == 1 && path.segments[0].ident == name | |
113 | } else { | |
114 | false | |
115 | } | |
116 | } | |
117 | ||
118 | fn lint_needless_cloning(cx: &LateContext<'_>, root: Span, receiver: Span) { | |
119 | span_lint_and_sugg( | |
120 | cx, | |
121 | MAP_CLONE, | |
122 | root.trim_start(receiver).unwrap(), | |
123 | "you are needlessly cloning iterator elements", | |
124 | "remove the `map` call", | |
125 | String::new(), | |
126 | Applicability::MachineApplicable, | |
127 | ) | |
128 | } | |
129 | ||
130 | fn lint(cx: &LateContext<'_>, replace: Span, root: Span, copied: bool) { | |
131 | let mut applicability = Applicability::MachineApplicable; | |
132 | if copied { | |
133 | span_lint_and_sugg( | |
134 | cx, | |
135 | MAP_CLONE, | |
136 | replace, | |
137 | "you are using an explicit closure for copying elements", | |
138 | "consider calling the dedicated `copied` method", | |
139 | format!( | |
140 | "{}.copied()", | |
141 | snippet_with_applicability(cx, root, "..", &mut applicability) | |
142 | ), | |
143 | applicability, | |
144 | ) | |
145 | } else { | |
146 | span_lint_and_sugg( | |
147 | cx, | |
148 | MAP_CLONE, | |
149 | replace, | |
150 | "you are using an explicit closure for cloning elements", | |
151 | "consider calling the dedicated `cloned` method", | |
152 | format!( | |
153 | "{}.cloned()", | |
154 | snippet_with_applicability(cx, root, "..", &mut applicability) | |
155 | ), | |
156 | applicability, | |
157 | ) | |
158 | } | |
159 | } |