]>
Commit | Line | Data |
---|---|---|
064997fb | 1 | use clippy_utils::diagnostics::span_lint_and_sugg; |
6522a427 | 2 | use clippy_utils::msrvs::{self, Msrv}; |
064997fb | 3 | use clippy_utils::source::snippet; |
6522a427 | 4 | use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; |
064997fb | 5 | use clippy_utils::{get_parent_expr, match_def_path, paths, SpanlessEq}; |
064997fb FG |
6 | use rustc_errors::Applicability; |
7 | use rustc_hir as hir; | |
8 | use rustc_hir::def_id::DefId; | |
9 | use rustc_hir::ExprKind::Assign; | |
10 | use rustc_lint::{LateContext, LateLintPass}; | |
11 | use rustc_semver::RustcVersion; | |
12 | use rustc_session::{declare_tool_lint, impl_lint_pass}; | |
13 | use rustc_span::symbol::sym; | |
14 | ||
15 | const ACCEPTABLE_METHODS: [&[&str]; 4] = [ | |
16 | &paths::HASHSET_ITER, | |
17 | &paths::BTREESET_ITER, | |
18 | &paths::SLICE_INTO, | |
19 | &paths::VEC_DEQUE_ITER, | |
20 | ]; | |
21 | const ACCEPTABLE_TYPES: [(rustc_span::Symbol, Option<RustcVersion>); 6] = [ | |
22 | (sym::BTreeSet, Some(msrvs::BTREE_SET_RETAIN)), | |
23 | (sym::BTreeMap, Some(msrvs::BTREE_MAP_RETAIN)), | |
24 | (sym::HashSet, Some(msrvs::HASH_SET_RETAIN)), | |
25 | (sym::HashMap, Some(msrvs::HASH_MAP_RETAIN)), | |
26 | (sym::Vec, None), | |
27 | (sym::VecDeque, None), | |
28 | ]; | |
29 | ||
30 | declare_clippy_lint! { | |
31 | /// ### What it does | |
32 | /// Checks for code to be replaced by `.retain()`. | |
33 | /// ### Why is this bad? | |
34 | /// `.retain()` is simpler and avoids needless allocation. | |
35 | /// ### Example | |
36 | /// ```rust | |
37 | /// let mut vec = vec![0, 1, 2]; | |
38 | /// vec = vec.iter().filter(|&x| x % 2 == 0).copied().collect(); | |
39 | /// vec = vec.into_iter().filter(|x| x % 2 == 0).collect(); | |
40 | /// ``` | |
41 | /// Use instead: | |
42 | /// ```rust | |
43 | /// let mut vec = vec![0, 1, 2]; | |
44 | /// vec.retain(|x| x % 2 == 0); | |
45 | /// ``` | |
2b03887a | 46 | #[clippy::version = "1.64.0"] |
064997fb FG |
47 | pub MANUAL_RETAIN, |
48 | perf, | |
49 | "`retain()` is simpler and the same functionalitys" | |
50 | } | |
51 | ||
52 | pub struct ManualRetain { | |
6522a427 | 53 | msrv: Msrv, |
064997fb FG |
54 | } |
55 | ||
56 | impl ManualRetain { | |
57 | #[must_use] | |
6522a427 | 58 | pub fn new(msrv: Msrv) -> Self { |
064997fb FG |
59 | Self { msrv } |
60 | } | |
61 | } | |
62 | ||
63 | impl_lint_pass!(ManualRetain => [MANUAL_RETAIN]); | |
64 | ||
65 | impl<'tcx> LateLintPass<'tcx> for ManualRetain { | |
66 | fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { | |
67 | if let Some(parent_expr) = get_parent_expr(cx, expr) | |
68 | && let Assign(left_expr, collect_expr, _) = &parent_expr.kind | |
f2b60f7d | 69 | && let hir::ExprKind::MethodCall(seg, ..) = &collect_expr.kind |
064997fb | 70 | && seg.args.is_none() |
f2b60f7d | 71 | && let hir::ExprKind::MethodCall(_, target_expr, [], _) = &collect_expr.kind |
064997fb | 72 | && let Some(collect_def_id) = cx.typeck_results().type_dependent_def_id(collect_expr.hir_id) |
6522a427 EL |
73 | && cx.tcx.is_diagnostic_item(sym::iterator_collect_fn, collect_def_id) |
74 | { | |
75 | check_into_iter(cx, parent_expr, left_expr, target_expr, &self.msrv); | |
76 | check_iter(cx, parent_expr, left_expr, target_expr, &self.msrv); | |
77 | check_to_owned(cx, parent_expr, left_expr, target_expr, &self.msrv); | |
064997fb FG |
78 | } |
79 | } | |
80 | ||
81 | extract_msrv_attr!(LateContext); | |
82 | } | |
83 | ||
84 | fn check_into_iter( | |
85 | cx: &LateContext<'_>, | |
86 | parent_expr: &hir::Expr<'_>, | |
87 | left_expr: &hir::Expr<'_>, | |
88 | target_expr: &hir::Expr<'_>, | |
6522a427 | 89 | msrv: &Msrv, |
064997fb | 90 | ) { |
f2b60f7d | 91 | if let hir::ExprKind::MethodCall(_, into_iter_expr, [_], _) = &target_expr.kind |
064997fb FG |
92 | && let Some(filter_def_id) = cx.typeck_results().type_dependent_def_id(target_expr.hir_id) |
93 | && match_def_path(cx, filter_def_id, &paths::CORE_ITER_FILTER) | |
f2b60f7d | 94 | && let hir::ExprKind::MethodCall(_, struct_expr, [], _) = &into_iter_expr.kind |
064997fb | 95 | && let Some(into_iter_def_id) = cx.typeck_results().type_dependent_def_id(into_iter_expr.hir_id) |
6522a427 | 96 | && Some(into_iter_def_id) == cx.tcx.lang_items().into_iter_fn() |
064997fb FG |
97 | && match_acceptable_type(cx, left_expr, msrv) |
98 | && SpanlessEq::new(cx).eq_expr(left_expr, struct_expr) { | |
99 | suggest(cx, parent_expr, left_expr, target_expr); | |
100 | } | |
101 | } | |
102 | ||
103 | fn check_iter( | |
104 | cx: &LateContext<'_>, | |
105 | parent_expr: &hir::Expr<'_>, | |
106 | left_expr: &hir::Expr<'_>, | |
107 | target_expr: &hir::Expr<'_>, | |
6522a427 | 108 | msrv: &Msrv, |
064997fb | 109 | ) { |
f2b60f7d | 110 | if let hir::ExprKind::MethodCall(_, filter_expr, [], _) = &target_expr.kind |
064997fb FG |
111 | && let Some(copied_def_id) = cx.typeck_results().type_dependent_def_id(target_expr.hir_id) |
112 | && (match_def_path(cx, copied_def_id, &paths::CORE_ITER_COPIED) | |
113 | || match_def_path(cx, copied_def_id, &paths::CORE_ITER_CLONED)) | |
f2b60f7d | 114 | && let hir::ExprKind::MethodCall(_, iter_expr, [_], _) = &filter_expr.kind |
064997fb FG |
115 | && let Some(filter_def_id) = cx.typeck_results().type_dependent_def_id(filter_expr.hir_id) |
116 | && match_def_path(cx, filter_def_id, &paths::CORE_ITER_FILTER) | |
f2b60f7d | 117 | && let hir::ExprKind::MethodCall(_, struct_expr, [], _) = &iter_expr.kind |
064997fb FG |
118 | && let Some(iter_expr_def_id) = cx.typeck_results().type_dependent_def_id(iter_expr.hir_id) |
119 | && match_acceptable_def_path(cx, iter_expr_def_id) | |
120 | && match_acceptable_type(cx, left_expr, msrv) | |
121 | && SpanlessEq::new(cx).eq_expr(left_expr, struct_expr) { | |
122 | suggest(cx, parent_expr, left_expr, filter_expr); | |
123 | } | |
124 | } | |
125 | ||
126 | fn check_to_owned( | |
127 | cx: &LateContext<'_>, | |
128 | parent_expr: &hir::Expr<'_>, | |
129 | left_expr: &hir::Expr<'_>, | |
130 | target_expr: &hir::Expr<'_>, | |
6522a427 | 131 | msrv: &Msrv, |
064997fb | 132 | ) { |
6522a427 | 133 | if msrv.meets(msrvs::STRING_RETAIN) |
f2b60f7d | 134 | && let hir::ExprKind::MethodCall(_, filter_expr, [], _) = &target_expr.kind |
064997fb FG |
135 | && let Some(to_owned_def_id) = cx.typeck_results().type_dependent_def_id(target_expr.hir_id) |
136 | && match_def_path(cx, to_owned_def_id, &paths::TO_OWNED_METHOD) | |
f2b60f7d | 137 | && let hir::ExprKind::MethodCall(_, chars_expr, [_], _) = &filter_expr.kind |
064997fb FG |
138 | && let Some(filter_def_id) = cx.typeck_results().type_dependent_def_id(filter_expr.hir_id) |
139 | && match_def_path(cx, filter_def_id, &paths::CORE_ITER_FILTER) | |
f2b60f7d | 140 | && let hir::ExprKind::MethodCall(_, str_expr, [], _) = &chars_expr.kind |
064997fb FG |
141 | && let Some(chars_expr_def_id) = cx.typeck_results().type_dependent_def_id(chars_expr.hir_id) |
142 | && match_def_path(cx, chars_expr_def_id, &paths::STR_CHARS) | |
143 | && let ty = cx.typeck_results().expr_ty(str_expr).peel_refs() | |
6522a427 | 144 | && is_type_lang_item(cx, ty, hir::LangItem::String) |
064997fb FG |
145 | && SpanlessEq::new(cx).eq_expr(left_expr, str_expr) { |
146 | suggest(cx, parent_expr, left_expr, filter_expr); | |
147 | } | |
148 | } | |
149 | ||
150 | fn suggest(cx: &LateContext<'_>, parent_expr: &hir::Expr<'_>, left_expr: &hir::Expr<'_>, filter_expr: &hir::Expr<'_>) { | |
f2b60f7d | 151 | if let hir::ExprKind::MethodCall(_, _, [closure], _) = filter_expr.kind |
064997fb FG |
152 | && let hir::ExprKind::Closure(&hir::Closure { body, ..}) = closure.kind |
153 | && let filter_body = cx.tcx.hir().body(body) | |
154 | && let [filter_params] = filter_body.params | |
155 | && let Some(sugg) = match filter_params.pat.kind { | |
156 | hir::PatKind::Binding(_, _, filter_param_ident, None) => { | |
2b03887a | 157 | Some(format!("{}.retain(|{filter_param_ident}| {})", snippet(cx, left_expr.span, ".."), snippet(cx, filter_body.value.span, ".."))) |
064997fb FG |
158 | }, |
159 | hir::PatKind::Tuple([key_pat, value_pat], _) => { | |
160 | make_sugg(cx, key_pat, value_pat, left_expr, filter_body) | |
161 | }, | |
162 | hir::PatKind::Ref(pat, _) => { | |
163 | match pat.kind { | |
164 | hir::PatKind::Binding(_, _, filter_param_ident, None) => { | |
2b03887a | 165 | Some(format!("{}.retain(|{filter_param_ident}| {})", snippet(cx, left_expr.span, ".."), snippet(cx, filter_body.value.span, ".."))) |
064997fb FG |
166 | }, |
167 | _ => None | |
168 | } | |
169 | }, | |
170 | _ => None | |
171 | } { | |
172 | span_lint_and_sugg( | |
173 | cx, | |
174 | MANUAL_RETAIN, | |
175 | parent_expr.span, | |
176 | "this expression can be written more simply using `.retain()`", | |
177 | "consider calling `.retain()` instead", | |
178 | sugg, | |
179 | Applicability::MachineApplicable | |
180 | ); | |
181 | } | |
182 | } | |
183 | ||
184 | fn make_sugg( | |
185 | cx: &LateContext<'_>, | |
186 | key_pat: &rustc_hir::Pat<'_>, | |
187 | value_pat: &rustc_hir::Pat<'_>, | |
188 | left_expr: &hir::Expr<'_>, | |
189 | filter_body: &hir::Body<'_>, | |
190 | ) -> Option<String> { | |
191 | match (&key_pat.kind, &value_pat.kind) { | |
192 | (hir::PatKind::Binding(_, _, key_param_ident, None), hir::PatKind::Binding(_, _, value_param_ident, None)) => { | |
193 | Some(format!( | |
2b03887a | 194 | "{}.retain(|{key_param_ident}, &mut {value_param_ident}| {})", |
064997fb | 195 | snippet(cx, left_expr.span, ".."), |
064997fb FG |
196 | snippet(cx, filter_body.value.span, "..") |
197 | )) | |
198 | }, | |
199 | (hir::PatKind::Binding(_, _, key_param_ident, None), hir::PatKind::Wild) => Some(format!( | |
2b03887a | 200 | "{}.retain(|{key_param_ident}, _| {})", |
064997fb | 201 | snippet(cx, left_expr.span, ".."), |
064997fb FG |
202 | snippet(cx, filter_body.value.span, "..") |
203 | )), | |
204 | (hir::PatKind::Wild, hir::PatKind::Binding(_, _, value_param_ident, None)) => Some(format!( | |
2b03887a | 205 | "{}.retain(|_, &mut {value_param_ident}| {})", |
064997fb | 206 | snippet(cx, left_expr.span, ".."), |
064997fb FG |
207 | snippet(cx, filter_body.value.span, "..") |
208 | )), | |
209 | _ => None, | |
210 | } | |
211 | } | |
212 | ||
213 | fn match_acceptable_def_path(cx: &LateContext<'_>, collect_def_id: DefId) -> bool { | |
214 | ACCEPTABLE_METHODS | |
215 | .iter() | |
216 | .any(|&method| match_def_path(cx, collect_def_id, method)) | |
217 | } | |
218 | ||
6522a427 | 219 | fn match_acceptable_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>, msrv: &Msrv) -> bool { |
064997fb FG |
220 | let expr_ty = cx.typeck_results().expr_ty(expr).peel_refs(); |
221 | ACCEPTABLE_TYPES.iter().any(|(ty, acceptable_msrv)| { | |
222 | is_type_diagnostic_item(cx, expr_ty, *ty) | |
6522a427 | 223 | && acceptable_msrv.map_or(true, |acceptable_msrv| msrv.meets(acceptable_msrv)) |
064997fb FG |
224 | }) |
225 | } |