1 use super::NEEDLESS_COLLECT
;
2 use clippy_utils
::diagnostics
::{span_lint_and_sugg, span_lint_hir_and_then}
;
3 use clippy_utils
::source
::{snippet, snippet_with_applicability}
;
4 use clippy_utils
::sugg
::Sugg
;
5 use clippy_utils
::ty
::is_type_diagnostic_item
;
6 use clippy_utils
::{is_trait_method, path_to_local_id}
;
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, GenericArgs, HirId, Local, Pat, PatKind, QPath, StmtKind, Ty}
;
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 if let Some(ty
) = cx
.typeck_results().node_type_opt(ty
.hir_id
);
31 let mut applicability
= Applicability
::MachineApplicable
;
32 let is_empty_sugg
= "next().is_none()".to_string();
33 let method_name
= &*method
.ident
.name
.as_str();
34 let sugg
= if is_type_diagnostic_item(cx
, ty
, sym
::vec_type
) ||
35 is_type_diagnostic_item(cx
, ty
, sym
::vecdeque_type
) ||
36 is_type_diagnostic_item(cx
, ty
, sym
::LinkedList
) ||
37 is_type_diagnostic_item(cx
, ty
, sym
::BinaryHeap
) {
39 "len" => "count()".to_string(),
40 "is_empty" => is_empty_sugg
,
42 let contains_arg
= snippet_with_applicability(cx
, args
[1].span
, "??", &mut applicability
);
43 let (arg
, pred
) = contains_arg
45 .map_or(("&x", &*contains_arg
), |s
| ("x", s
));
46 format
!("any(|{}| x == {})", arg
, pred
)
51 else if is_type_diagnostic_item(cx
, ty
, sym
::BTreeMap
) ||
52 is_type_diagnostic_item(cx
, ty
, sym
::hashmap_type
) {
54 "is_empty" => is_empty_sugg
,
64 method0_span
.with_hi(expr
.span
.hi()),
74 fn check_needless_collect_indirect_usage
<'tcx
>(expr
: &'tcx Expr
<'_
>, cx
: &LateContext
<'tcx
>) {
75 fn get_hir_id
<'tcx
>(ty
: Option
<&Ty
<'tcx
>>, method_args
: Option
<&GenericArgs
<'tcx
>>) -> Option
<HirId
> {
76 if let Some(ty
) = ty
{
77 return Some(ty
.hir_id
);
80 if let Some(generic_args
) = method_args
{
81 if let Some(GenericArg
::Type(ref ty
)) = generic_args
.args
.get(0) {
82 return Some(ty
.hir_id
);
88 if let ExprKind
::Block(block
, _
) = expr
.kind
{
89 for stmt
in block
.stmts
{
91 if let StmtKind
::Local(
92 Local { pat: Pat { hir_id: pat_id, kind: PatKind::Binding(_, _, ident, .. ), .. }
,
93 init
: Some(init_expr
), ty
, .. }
95 if let ExprKind
::MethodCall(method_name
, collect_span
, &[ref iter_source
], ..) = init_expr
.kind
;
96 if method_name
.ident
.name
== sym
!(collect
) && is_trait_method(cx
, init_expr
, sym
::Iterator
);
97 if let Some(hir_id
) = get_hir_id(*ty
, method_name
.args
);
98 if let Some(ty
) = cx
.typeck_results().node_type_opt(hir_id
);
99 if is_type_diagnostic_item(cx
, ty
, sym
::vec_type
) ||
100 is_type_diagnostic_item(cx
, ty
, sym
::vecdeque_type
) ||
101 is_type_diagnostic_item(cx
, ty
, sym
::BinaryHeap
) ||
102 is_type_diagnostic_item(cx
, ty
, sym
::LinkedList
);
103 if let Some(iter_calls
) = detect_iter_and_into_iters(block
, *ident
);
104 if let [iter_call
] = &*iter_calls
;
106 let mut used_count_visitor
= UsedCountVisitor
{
111 walk_block(&mut used_count_visitor
, block
);
112 if used_count_visitor
.count
> 1 {
116 // Suggest replacing iter_call with iter_replacement, and removing stmt
117 let mut span
= MultiSpan
::from_span(collect_span
);
118 span
.push_span_label(iter_call
.span
, "the iterator could be used here instead".into());
119 span_lint_hir_and_then(
121 super::NEEDLESS_COLLECT
,
124 NEEDLESS_COLLECT_MSG
,
126 let iter_replacement
= format
!("{}{}", Sugg
::hir(cx
, iter_source
, ".."), iter_call
.get_iter_method(cx
));
127 diag
.multipart_suggestion(
128 iter_call
.get_suggestion_text(),
130 (stmt
.span
, String
::new()),
131 (iter_call
.span
, iter_replacement
)
133 Applicability
::MachineApplicable
,// MaybeIncorrect,
143 struct IterFunction
{
144 func
: IterFunctionKind
,
148 fn get_iter_method(&self, cx
: &LateContext
<'_
>) -> String
{
150 IterFunctionKind
::IntoIter
=> String
::new(),
151 IterFunctionKind
::Len
=> String
::from(".count()"),
152 IterFunctionKind
::IsEmpty
=> String
::from(".next().is_none()"),
153 IterFunctionKind
::Contains(span
) => {
154 let s
= snippet(cx
, *span
, "..");
155 if let Some(stripped
) = s
.strip_prefix('
&'
) {
156 format
!(".any(|x| x == {})", stripped
)
158 format
!(".any(|x| x == *{})", s
)
163 fn get_suggestion_text(&self) -> &'
static str {
165 IterFunctionKind
::IntoIter
=> {
166 "use the original Iterator instead of collecting it and then producing a new one"
168 IterFunctionKind
::Len
=> {
169 "take the original Iterator's count instead of collecting it and finding the length"
171 IterFunctionKind
::IsEmpty
=> {
172 "check if the original Iterator has anything instead of collecting it and seeing if it's empty"
174 IterFunctionKind
::Contains(_
) => {
175 "check if the original Iterator contains an element instead of collecting then checking"
180 enum IterFunctionKind
{
187 struct IterFunctionVisitor
{
188 uses
: Vec
<IterFunction
>,
192 impl<'tcx
> Visitor
<'tcx
> for IterFunctionVisitor
{
193 fn visit_expr(&mut self, expr
: &'tcx Expr
<'tcx
>) {
194 // Check function calls on our collection
196 if let ExprKind
::MethodCall(method_name
, _
, args
, _
) = &expr
.kind
;
197 if let Some(Expr { kind: ExprKind::Path(QPath::Resolved(_, path)), .. }
) = args
.get(0);
198 if let &[name
] = &path
.segments
;
199 if name
.ident
== self.target
;
202 let is_empty
= sym
!(is_empty
);
203 let contains
= sym
!(contains
);
204 match method_name
.ident
.name
{
205 sym
::into_iter
=> self.uses
.push(
206 IterFunction { func: IterFunctionKind::IntoIter, span: expr.span }
208 name
if name
== len
=> self.uses
.push(
209 IterFunction { func: IterFunctionKind::Len, span: expr.span }
211 name
if name
== is_empty
=> self.uses
.push(
212 IterFunction { func: IterFunctionKind::IsEmpty, span: expr.span }
214 name
if name
== contains
=> self.uses
.push(
215 IterFunction { func: IterFunctionKind::Contains(args[1].span), span: expr.span }
217 _
=> self.seen_other
= true,
222 // Check if the collection is used for anything else
224 if let Expr { kind: ExprKind::Path(QPath::Resolved(_, path)), .. }
= expr
;
225 if let &[name
] = &path
.segments
;
226 if name
.ident
== self.target
;
228 self.seen_other
= true;
230 walk_expr(self, expr
);
235 type Map
= Map
<'tcx
>;
236 fn nested_visit_map(&mut self) -> NestedVisitorMap
<Self::Map
> {
237 NestedVisitorMap
::None
241 struct UsedCountVisitor
<'a
, 'tcx
> {
242 cx
: &'a LateContext
<'tcx
>,
247 impl<'a
, 'tcx
> Visitor
<'tcx
> for UsedCountVisitor
<'a
, 'tcx
> {
248 type Map
= Map
<'tcx
>;
250 fn visit_expr(&mut self, expr
: &'tcx Expr
<'_
>) {
251 if path_to_local_id(expr
, self.id
) {
254 walk_expr(self, expr
);
258 fn nested_visit_map(&mut self) -> NestedVisitorMap
<Self::Map
> {
259 NestedVisitorMap
::OnlyBodies(self.cx
.tcx
.hir())
263 /// Detect the occurrences of calls to `iter` or `into_iter` for the
265 fn detect_iter_and_into_iters
<'tcx
>(block
: &'tcx Block
<'tcx
>, identifier
: Ident
) -> Option
<Vec
<IterFunction
>> {
266 let mut visitor
= IterFunctionVisitor
{
271 visitor
.visit_block(block
);
272 if visitor
.seen_other { None }
else { Some(visitor.uses) }