1
+ use std:: ops:: ControlFlow ;
2
+
1
3
use super :: NEEDLESS_COLLECT ;
2
4
use clippy_utils:: diagnostics:: { span_lint_and_sugg, span_lint_hir_and_then} ;
3
5
use clippy_utils:: source:: { snippet, snippet_with_applicability} ;
@@ -9,9 +11,9 @@ use clippy_utils::{
9
11
} ;
10
12
use rustc_data_structures:: fx:: FxHashMap ;
11
13
use rustc_errors:: { Applicability , MultiSpan } ;
12
- use rustc_hir:: intravisit:: { Visitor , walk_block, walk_expr} ;
14
+ use rustc_hir:: intravisit:: { Visitor , walk_block, walk_expr, walk_stmt } ;
13
15
use rustc_hir:: {
14
- BindingMode , Block , Expr , ExprKind , HirId , HirIdSet , LetStmt , Mutability , Node , PatKind , Stmt , StmtKind ,
16
+ BindingMode , Block , Expr , ExprKind , HirId , HirIdSet , LetStmt , Mutability , Node , Pat , PatKind , Stmt , StmtKind ,
15
17
} ;
16
18
use rustc_lint:: LateContext ;
17
19
use rustc_middle:: hir:: nested_filter;
@@ -103,6 +105,12 @@ pub(super) fn check<'tcx>(
103
105
return ;
104
106
}
105
107
108
+ if let IterFunctionKind :: IntoIter ( hir_id) = iter_call. func
109
+ && !check_iter_expr_used_only_as_iterator ( cx, hir_id, block)
110
+ {
111
+ return ;
112
+ }
113
+
106
114
// Suggest replacing iter_call with iter_replacement, and removing stmt
107
115
let mut span = MultiSpan :: from_span ( name_span) ;
108
116
span. push_span_label ( iter_call. span , "the iterator could be used here instead" ) ;
@@ -253,7 +261,7 @@ struct IterFunction {
253
261
impl IterFunction {
254
262
fn get_iter_method ( & self , cx : & LateContext < ' _ > ) -> String {
255
263
match & self . func {
256
- IterFunctionKind :: IntoIter => String :: new ( ) ,
264
+ IterFunctionKind :: IntoIter ( _ ) => String :: new ( ) ,
257
265
IterFunctionKind :: Len => String :: from ( ".count()" ) ,
258
266
IterFunctionKind :: IsEmpty => String :: from ( ".next().is_none()" ) ,
259
267
IterFunctionKind :: Contains ( span) => {
@@ -268,7 +276,7 @@ impl IterFunction {
268
276
}
269
277
fn get_suggestion_text ( & self ) -> & ' static str {
270
278
match & self . func {
271
- IterFunctionKind :: IntoIter => {
279
+ IterFunctionKind :: IntoIter ( _ ) => {
272
280
"use the original Iterator instead of collecting it and then producing a new one"
273
281
} ,
274
282
IterFunctionKind :: Len => {
@@ -284,7 +292,7 @@ impl IterFunction {
284
292
}
285
293
}
286
294
enum IterFunctionKind {
287
- IntoIter ,
295
+ IntoIter ( HirId ) ,
288
296
Len ,
289
297
IsEmpty ,
290
298
Contains ( Span ) ,
@@ -343,7 +351,7 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> {
343
351
}
344
352
match method_name. ident . name . as_str ( ) {
345
353
"into_iter" => self . uses . push ( Some ( IterFunction {
346
- func : IterFunctionKind :: IntoIter ,
354
+ func : IterFunctionKind :: IntoIter ( expr . hir_id ) ,
347
355
span : expr. span ,
348
356
} ) ) ,
349
357
"len" => self . uses . push ( Some ( IterFunction {
@@ -520,3 +528,61 @@ fn get_captured_ids(cx: &LateContext<'_>, ty: Ty<'_>) -> HirIdSet {
520
528
521
529
set
522
530
}
531
+
532
+ struct IteratorMethodCheckVisitor < ' a , ' tcx > {
533
+ cx : & ' a LateContext < ' tcx > ,
534
+ hir_id_of_expr : HirId ,
535
+ hir_id_of_let_binding : Option < HirId > ,
536
+ }
537
+
538
+ impl < ' tcx > Visitor < ' tcx > for IteratorMethodCheckVisitor < ' _ , ' tcx > {
539
+ type Result = ControlFlow < ( ) > ;
540
+ fn visit_expr ( & mut self , expr : & ' tcx Expr < ' tcx > ) -> ControlFlow < ( ) > {
541
+ if let ExprKind :: MethodCall ( _method_name, recv, _args, _) = & expr. kind
542
+ && ( recv. hir_id == self . hir_id_of_expr
543
+ || self
544
+ . hir_id_of_let_binding
545
+ . is_some_and ( |hid| path_to_local_id ( recv, hid) ) )
546
+ && !is_trait_method ( self . cx , expr, sym:: Iterator )
547
+ {
548
+ return ControlFlow :: Break ( ( ) ) ;
549
+ } else if let ExprKind :: Assign ( place, value, _span) = & expr. kind
550
+ && value. hir_id == self . hir_id_of_expr
551
+ && let Some ( id) = path_to_local ( place)
552
+ {
553
+ // our iterator was directly assigned to a variable
554
+ self . hir_id_of_let_binding = Some ( id) ;
555
+ }
556
+ walk_expr ( self , expr)
557
+ }
558
+ fn visit_stmt ( & mut self , stmt : & ' tcx Stmt < ' tcx > ) -> ControlFlow < ( ) > {
559
+ if let StmtKind :: Let ( LetStmt {
560
+ init : Some ( expr) ,
561
+ pat :
562
+ Pat {
563
+ kind : PatKind :: Binding ( BindingMode :: NONE | BindingMode :: MUT , id, _, None ) ,
564
+ ..
565
+ } ,
566
+ ..
567
+ } ) = & stmt. kind
568
+ && expr. hir_id == self . hir_id_of_expr
569
+ {
570
+ // our iterator was directly assigned to a variable
571
+ self . hir_id_of_let_binding = Some ( * id) ;
572
+ }
573
+ walk_stmt ( self , stmt)
574
+ }
575
+ }
576
+
577
+ fn check_iter_expr_used_only_as_iterator < ' tcx > (
578
+ cx : & LateContext < ' tcx > ,
579
+ hir_id_of_expr : HirId ,
580
+ block : & ' tcx Block < ' tcx > ,
581
+ ) -> bool {
582
+ let mut visitor = IteratorMethodCheckVisitor {
583
+ cx,
584
+ hir_id_of_expr,
585
+ hir_id_of_let_binding : None ,
586
+ } ;
587
+ visitor. visit_block ( block) . is_continue ( )
588
+ }
0 commit comments