1 use super::NEEDLESS_COLLECT
;
2 use clippy_utils
::diagnostics
::{span_lint_and_sugg, span_lint_and_then}
;
3 use clippy_utils
::source
::snippet
;
4 use clippy_utils
::sugg
::Sugg
;
5 use clippy_utils
::ty
::{is_type_diagnostic_item, match_type}
;
6 use clippy_utils
::{is_trait_method, path_to_local_id, paths}
;
7 use if_chain
::if_chain
;
8 use rustc_errors
::Applicability
;
9 use rustc_hir
::intravisit
::{walk_block, walk_expr, NestedVisitorMap, Visitor}
;
10 use rustc_hir
::{Block, Expr, ExprKind, GenericArg, HirId, Local, Pat, PatKind, QPath, StmtKind}
;
11 use rustc_lint
::LateContext
;
12 use rustc_middle
::hir
::map
::Map
;
13 use rustc_span
::symbol
::{sym, Ident}
;
14 use rustc_span
::{MultiSpan, Span}
;
16 const NEEDLESS_COLLECT_MSG
: &str = "avoid using `collect()` when not needed";
18 pub(super) fn check
<'tcx
>(expr
: &'tcx Expr
<'_
>, cx
: &LateContext
<'tcx
>) {
19 check_needless_collect_direct_usage(expr
, cx
);
20 check_needless_collect_indirect_usage(expr
, cx
);
22 fn check_needless_collect_direct_usage
<'tcx
>(expr
: &'tcx Expr
<'_
>, cx
: &LateContext
<'tcx
>) {
24 if let ExprKind
::MethodCall(method
, _
, args
, _
) = expr
.kind
;
25 if let ExprKind
::MethodCall(chain_method
, method0_span
, _
, _
) = args
[0].kind
;
26 if chain_method
.ident
.name
== sym
!(collect
) && is_trait_method(cx
, &args
[0], sym
::Iterator
);
27 if let Some(generic_args
) = chain_method
.args
;
28 if let Some(GenericArg
::Type(ref ty
)) = generic_args
.args
.get(0);
29 let ty
= cx
.typeck_results().node_type(ty
.hir_id
);
30 if is_type_diagnostic_item(cx
, ty
, sym
::vec_type
)
31 || is_type_diagnostic_item(cx
, ty
, sym
::vecdeque_type
)
32 || match_type(cx
, ty
, &paths
::BTREEMAP
)
33 || is_type_diagnostic_item(cx
, ty
, sym
::hashmap_type
);
34 if let Some(sugg
) = match &*method
.ident
.name
.as_str() {
35 "len" => Some("count()".to_string()),
36 "is_empty" => Some("next().is_none()".to_string()),
38 let contains_arg
= snippet(cx
, args
[1].span
, "??");
39 let (arg
, pred
) = contains_arg
41 .map_or(("&x", &*contains_arg
), |s
| ("x", s
));
42 Some(format
!("any(|{}| x == {})", arg
, pred
))
50 method0_span
.with_hi(expr
.span
.hi()),
54 Applicability
::MachineApplicable
,
60 fn check_needless_collect_indirect_usage
<'tcx
>(expr
: &'tcx Expr
<'_
>, cx
: &LateContext
<'tcx
>) {
61 if let ExprKind
::Block(block
, _
) = expr
.kind
{
62 for stmt
in block
.stmts
{
64 if let StmtKind
::Local(
65 Local { pat: Pat { hir_id: pat_id, kind: PatKind::Binding(_, _, ident, .. ), .. }
,
66 init
: Some(init_expr
), .. }
68 if let ExprKind
::MethodCall(method_name
, collect_span
, &[ref iter_source
], ..) = init_expr
.kind
;
69 if method_name
.ident
.name
== sym
!(collect
) && is_trait_method(cx
, init_expr
, sym
::Iterator
);
70 if let Some(generic_args
) = method_name
.args
;
71 if let Some(GenericArg
::Type(ref ty
)) = generic_args
.args
.get(0);
72 if let ty
= cx
.typeck_results().node_type(ty
.hir_id
);
73 if is_type_diagnostic_item(cx
, ty
, sym
::vec_type
) ||
74 is_type_diagnostic_item(cx
, ty
, sym
::vecdeque_type
) ||
75 match_type(cx
, ty
, &paths
::LINKED_LIST
);
76 if let Some(iter_calls
) = detect_iter_and_into_iters(block
, *ident
);
77 if let [iter_call
] = &*iter_calls
;
79 let mut used_count_visitor
= UsedCountVisitor
{
84 walk_block(&mut used_count_visitor
, block
);
85 if used_count_visitor
.count
> 1 {
89 // Suggest replacing iter_call with iter_replacement, and removing stmt
90 let mut span
= MultiSpan
::from_span(collect_span
);
91 span
.push_span_label(iter_call
.span
, "the iterator could be used here instead".into());
94 super::NEEDLESS_COLLECT
,
98 let iter_replacement
= format
!("{}{}", Sugg
::hir(cx
, iter_source
, ".."), iter_call
.get_iter_method(cx
));
99 diag
.multipart_suggestion(
100 iter_call
.get_suggestion_text(),
102 (stmt
.span
, String
::new()),
103 (iter_call
.span
, iter_replacement
)
105 Applicability
::MachineApplicable
,// MaybeIncorrect,
115 struct IterFunction
{
116 func
: IterFunctionKind
,
120 fn get_iter_method(&self, cx
: &LateContext
<'_
>) -> String
{
122 IterFunctionKind
::IntoIter
=> String
::new(),
123 IterFunctionKind
::Len
=> String
::from(".count()"),
124 IterFunctionKind
::IsEmpty
=> String
::from(".next().is_none()"),
125 IterFunctionKind
::Contains(span
) => {
126 let s
= snippet(cx
, *span
, "..");
127 if let Some(stripped
) = s
.strip_prefix('
&'
) {
128 format
!(".any(|x| x == {})", stripped
)
130 format
!(".any(|x| x == *{})", s
)
135 fn get_suggestion_text(&self) -> &'
static str {
137 IterFunctionKind
::IntoIter
=> {
138 "use the original Iterator instead of collecting it and then producing a new one"
140 IterFunctionKind
::Len
=> {
141 "take the original Iterator's count instead of collecting it and finding the length"
143 IterFunctionKind
::IsEmpty
=> {
144 "check if the original Iterator has anything instead of collecting it and seeing if it's empty"
146 IterFunctionKind
::Contains(_
) => {
147 "check if the original Iterator contains an element instead of collecting then checking"
152 enum IterFunctionKind
{
159 struct IterFunctionVisitor
{
160 uses
: Vec
<IterFunction
>,
164 impl<'tcx
> Visitor
<'tcx
> for IterFunctionVisitor
{
165 fn visit_expr(&mut self, expr
: &'tcx Expr
<'tcx
>) {
166 // Check function calls on our collection
168 if let ExprKind
::MethodCall(method_name
, _
, args
, _
) = &expr
.kind
;
169 if let Some(Expr { kind: ExprKind::Path(QPath::Resolved(_, path)), .. }
) = args
.get(0);
170 if let &[name
] = &path
.segments
;
171 if name
.ident
== self.target
;
174 let is_empty
= sym
!(is_empty
);
175 let contains
= sym
!(contains
);
176 match method_name
.ident
.name
{
177 sym
::into_iter
=> self.uses
.push(
178 IterFunction { func: IterFunctionKind::IntoIter, span: expr.span }
180 name
if name
== len
=> self.uses
.push(
181 IterFunction { func: IterFunctionKind::Len, span: expr.span }
183 name
if name
== is_empty
=> self.uses
.push(
184 IterFunction { func: IterFunctionKind::IsEmpty, span: expr.span }
186 name
if name
== contains
=> self.uses
.push(
187 IterFunction { func: IterFunctionKind::Contains(args[1].span), span: expr.span }
189 _
=> self.seen_other
= true,
194 // Check if the collection is used for anything else
196 if let Expr { kind: ExprKind::Path(QPath::Resolved(_, path)), .. }
= expr
;
197 if let &[name
] = &path
.segments
;
198 if name
.ident
== self.target
;
200 self.seen_other
= true;
202 walk_expr(self, expr
);
207 type Map
= Map
<'tcx
>;
208 fn nested_visit_map(&mut self) -> NestedVisitorMap
<Self::Map
> {
209 NestedVisitorMap
::None
213 struct UsedCountVisitor
<'a
, 'tcx
> {
214 cx
: &'a LateContext
<'tcx
>,
219 impl<'a
, 'tcx
> Visitor
<'tcx
> for UsedCountVisitor
<'a
, 'tcx
> {
220 type Map
= Map
<'tcx
>;
222 fn visit_expr(&mut self, expr
: &'tcx Expr
<'_
>) {
223 if path_to_local_id(expr
, self.id
) {
226 walk_expr(self, expr
);
230 fn nested_visit_map(&mut self) -> NestedVisitorMap
<Self::Map
> {
231 NestedVisitorMap
::OnlyBodies(self.cx
.tcx
.hir())
235 /// Detect the occurrences of calls to `iter` or `into_iter` for the
237 fn detect_iter_and_into_iters
<'tcx
>(block
: &'tcx Block
<'tcx
>, identifier
: Ident
) -> Option
<Vec
<IterFunction
>> {
238 let mut visitor
= IterFunctionVisitor
{
243 visitor
.visit_block(block
);
244 if visitor
.seen_other { None }
else { Some(visitor.uses) }