1 use clippy_utils
::diagnostics
::span_lint_and_sugg
;
2 use clippy_utils
::source
::snippet
;
3 use clippy_utils
::ty
::is_type_diagnostic_item
;
4 use clippy_utils
::{get_parent_expr, match_def_path, paths, SpanlessEq}
;
5 use clippy_utils
::{meets_msrv, msrvs}
;
6 use rustc_errors
::Applicability
;
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
;
15 const ACCEPTABLE_METHODS
: [&[&str]; 4] = [
17 &paths
::BTREESET_ITER
,
19 &paths
::VEC_DEQUE_ITER
,
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
)),
27 (sym
::VecDeque
, None
),
30 declare_clippy_lint
! {
32 /// Checks for code to be replaced by `.retain()`.
33 /// ### Why is this bad?
34 /// `.retain()` is simpler and avoids needless allocation.
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();
43 /// let mut vec = vec![0, 1, 2];
44 /// vec.retain(|x| x % 2 == 0);
46 #[clippy::version = "1.63.0"]
49 "`retain()` is simpler and the same functionalitys"
52 pub struct ManualRetain
{
53 msrv
: Option
<RustcVersion
>,
58 pub fn new(msrv
: Option
<RustcVersion
>) -> Self {
63 impl_lint_pass
!(ManualRetain
=> [MANUAL_RETAIN
]);
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
69 && let hir
::ExprKind
::MethodCall(seg
, _
, _
) = &collect_expr
.kind
71 && let hir
::ExprKind
::MethodCall(_
, [target_expr
], _
) = &collect_expr
.kind
72 && let Some(collect_def_id
) = cx
.typeck_results().type_dependent_def_id(collect_expr
.hir_id
)
73 && match_def_path(cx
, collect_def_id
, &paths
::CORE_ITER_COLLECT
) {
74 check_into_iter(cx
, parent_expr
, left_expr
, target_expr
, self.msrv
);
75 check_iter(cx
, parent_expr
, left_expr
, target_expr
, self.msrv
);
76 check_to_owned(cx
, parent_expr
, left_expr
, target_expr
, self.msrv
);
80 extract_msrv_attr
!(LateContext
);
85 parent_expr
: &hir
::Expr
<'_
>,
86 left_expr
: &hir
::Expr
<'_
>,
87 target_expr
: &hir
::Expr
<'_
>,
88 msrv
: Option
<RustcVersion
>,
90 if let hir
::ExprKind
::MethodCall(_
, [into_iter_expr
, _
], _
) = &target_expr
.kind
91 && let Some(filter_def_id
) = cx
.typeck_results().type_dependent_def_id(target_expr
.hir_id
)
92 && match_def_path(cx
, filter_def_id
, &paths
::CORE_ITER_FILTER
)
93 && let hir
::ExprKind
::MethodCall(_
, [struct_expr
], _
) = &into_iter_expr
.kind
94 && let Some(into_iter_def_id
) = cx
.typeck_results().type_dependent_def_id(into_iter_expr
.hir_id
)
95 && match_def_path(cx
, into_iter_def_id
, &paths
::CORE_ITER_INTO_ITER
)
96 && match_acceptable_type(cx
, left_expr
, msrv
)
97 && SpanlessEq
::new(cx
).eq_expr(left_expr
, struct_expr
) {
98 suggest(cx
, parent_expr
, left_expr
, target_expr
);
103 cx
: &LateContext
<'_
>,
104 parent_expr
: &hir
::Expr
<'_
>,
105 left_expr
: &hir
::Expr
<'_
>,
106 target_expr
: &hir
::Expr
<'_
>,
107 msrv
: Option
<RustcVersion
>,
109 if let hir
::ExprKind
::MethodCall(_
, [filter_expr
], _
) = &target_expr
.kind
110 && let Some(copied_def_id
) = cx
.typeck_results().type_dependent_def_id(target_expr
.hir_id
)
111 && (match_def_path(cx
, copied_def_id
, &paths
::CORE_ITER_COPIED
)
112 || match_def_path(cx
, copied_def_id
, &paths
::CORE_ITER_CLONED
))
113 && let hir
::ExprKind
::MethodCall(_
, [iter_expr
, _
], _
) = &filter_expr
.kind
114 && let Some(filter_def_id
) = cx
.typeck_results().type_dependent_def_id(filter_expr
.hir_id
)
115 && match_def_path(cx
, filter_def_id
, &paths
::CORE_ITER_FILTER
)
116 && let hir
::ExprKind
::MethodCall(_
, [struct_expr
], _
) = &iter_expr
.kind
117 && let Some(iter_expr_def_id
) = cx
.typeck_results().type_dependent_def_id(iter_expr
.hir_id
)
118 && match_acceptable_def_path(cx
, iter_expr_def_id
)
119 && match_acceptable_type(cx
, left_expr
, msrv
)
120 && SpanlessEq
::new(cx
).eq_expr(left_expr
, struct_expr
) {
121 suggest(cx
, parent_expr
, left_expr
, filter_expr
);
126 cx
: &LateContext
<'_
>,
127 parent_expr
: &hir
::Expr
<'_
>,
128 left_expr
: &hir
::Expr
<'_
>,
129 target_expr
: &hir
::Expr
<'_
>,
130 msrv
: Option
<RustcVersion
>,
132 if meets_msrv(msrv
, msrvs
::STRING_RETAIN
)
133 && let hir
::ExprKind
::MethodCall(_
, [filter_expr
], _
) = &target_expr
.kind
134 && let Some(to_owned_def_id
) = cx
.typeck_results().type_dependent_def_id(target_expr
.hir_id
)
135 && match_def_path(cx
, to_owned_def_id
, &paths
::TO_OWNED_METHOD
)
136 && let hir
::ExprKind
::MethodCall(_
, [chars_expr
, _
], _
) = &filter_expr
.kind
137 && let Some(filter_def_id
) = cx
.typeck_results().type_dependent_def_id(filter_expr
.hir_id
)
138 && match_def_path(cx
, filter_def_id
, &paths
::CORE_ITER_FILTER
)
139 && let hir
::ExprKind
::MethodCall(_
, [str_expr
], _
) = &chars_expr
.kind
140 && let Some(chars_expr_def_id
) = cx
.typeck_results().type_dependent_def_id(chars_expr
.hir_id
)
141 && match_def_path(cx
, chars_expr_def_id
, &paths
::STR_CHARS
)
142 && let ty
= cx
.typeck_results().expr_ty(str_expr
).peel_refs()
143 && is_type_diagnostic_item(cx
, ty
, sym
::String
)
144 && SpanlessEq
::new(cx
).eq_expr(left_expr
, str_expr
) {
145 suggest(cx
, parent_expr
, left_expr
, filter_expr
);
149 fn suggest(cx
: &LateContext
<'_
>, parent_expr
: &hir
::Expr
<'_
>, left_expr
: &hir
::Expr
<'_
>, filter_expr
: &hir
::Expr
<'_
>) {
150 if let hir
::ExprKind
::MethodCall(_
, [_
, closure
], _
) = filter_expr
.kind
151 && let hir
::ExprKind
::Closure(&hir
::Closure { body, ..}
) = closure
.kind
152 && let filter_body
= cx
.tcx
.hir().body(body
)
153 && let [filter_params
] = filter_body
.params
154 && let Some(sugg
) = match filter_params
.pat
.kind
{
155 hir
::PatKind
::Binding(_
, _
, filter_param_ident
, None
) => {
156 Some(format
!("{}.retain(|{}| {})", snippet(cx
, left_expr
.span
, ".."), filter_param_ident
, snippet(cx
, filter_body
.value
.span
, "..")))
158 hir
::PatKind
::Tuple([key_pat
, value_pat
], _
) => {
159 make_sugg(cx
, key_pat
, value_pat
, left_expr
, filter_body
)
161 hir
::PatKind
::Ref(pat
, _
) => {
163 hir
::PatKind
::Binding(_
, _
, filter_param_ident
, None
) => {
164 Some(format
!("{}.retain(|{}| {})", snippet(cx
, left_expr
.span
, ".."), filter_param_ident
, snippet(cx
, filter_body
.value
.span
, "..")))
175 "this expression can be written more simply using `.retain()`",
176 "consider calling `.retain()` instead",
178 Applicability
::MachineApplicable
184 cx
: &LateContext
<'_
>,
185 key_pat
: &rustc_hir
::Pat
<'_
>,
186 value_pat
: &rustc_hir
::Pat
<'_
>,
187 left_expr
: &hir
::Expr
<'_
>,
188 filter_body
: &hir
::Body
<'_
>,
189 ) -> Option
<String
> {
190 match (&key_pat
.kind
, &value_pat
.kind
) {
191 (hir
::PatKind
::Binding(_
, _
, key_param_ident
, None
), hir
::PatKind
::Binding(_
, _
, value_param_ident
, None
)) => {
193 "{}.retain(|{}, &mut {}| {})",
194 snippet(cx
, left_expr
.span
, ".."),
197 snippet(cx
, filter_body
.value
.span
, "..")
200 (hir
::PatKind
::Binding(_
, _
, key_param_ident
, None
), hir
::PatKind
::Wild
) => Some(format
!(
201 "{}.retain(|{}, _| {})",
202 snippet(cx
, left_expr
.span
, ".."),
204 snippet(cx
, filter_body
.value
.span
, "..")
206 (hir
::PatKind
::Wild
, hir
::PatKind
::Binding(_
, _
, value_param_ident
, None
)) => Some(format
!(
207 "{}.retain(|_, &mut {}| {})",
208 snippet(cx
, left_expr
.span
, ".."),
210 snippet(cx
, filter_body
.value
.span
, "..")
216 fn match_acceptable_def_path(cx
: &LateContext
<'_
>, collect_def_id
: DefId
) -> bool
{
219 .any(|&method
| match_def_path(cx
, collect_def_id
, method
))
222 fn match_acceptable_type(cx
: &LateContext
<'_
>, expr
: &hir
::Expr
<'_
>, msrv
: Option
<RustcVersion
>) -> bool
{
223 let expr_ty
= cx
.typeck_results().expr_ty(expr
).peel_refs();
224 ACCEPTABLE_TYPES
.iter().any(|(ty
, acceptable_msrv
)| {
225 is_type_diagnostic_item(cx
, expr_ty
, *ty
)
226 && acceptable_msrv
.map_or(true, |acceptable_msrv
| meets_msrv(msrv
, acceptable_msrv
))