1+ use std:: ops:: ControlFlow ;
2+
13use super :: NEEDLESS_COLLECT ;
24use clippy_utils:: diagnostics:: { span_lint_and_sugg, span_lint_hir_and_then} ;
35use clippy_utils:: source:: { snippet, snippet_with_applicability} ;
@@ -9,9 +11,9 @@ use clippy_utils::{
911} ;
1012use rustc_data_structures:: fx:: FxHashMap ;
1113use 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 } ;
1315use 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 ,
1517} ;
1618use rustc_lint:: LateContext ;
1719use rustc_middle:: hir:: nested_filter;
@@ -103,6 +105,12 @@ pub(super) fn check<'tcx>(
103105 return ;
104106 }
105107
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+
106114 // Suggest replacing iter_call with iter_replacement, and removing stmt
107115 let mut span = MultiSpan :: from_span ( name_span) ;
108116 span. push_span_label ( iter_call. span , "the iterator could be used here instead" ) ;
@@ -253,7 +261,7 @@ struct IterFunction {
253261impl IterFunction {
254262 fn get_iter_method ( & self , cx : & LateContext < ' _ > ) -> String {
255263 match & self . func {
256- IterFunctionKind :: IntoIter => String :: new ( ) ,
264+ IterFunctionKind :: IntoIter ( _ ) => String :: new ( ) ,
257265 IterFunctionKind :: Len => String :: from ( ".count()" ) ,
258266 IterFunctionKind :: IsEmpty => String :: from ( ".next().is_none()" ) ,
259267 IterFunctionKind :: Contains ( span) => {
@@ -268,7 +276,7 @@ impl IterFunction {
268276 }
269277 fn get_suggestion_text ( & self ) -> & ' static str {
270278 match & self . func {
271- IterFunctionKind :: IntoIter => {
279+ IterFunctionKind :: IntoIter ( _ ) => {
272280 "use the original Iterator instead of collecting it and then producing a new one"
273281 } ,
274282 IterFunctionKind :: Len => {
@@ -284,7 +292,7 @@ impl IterFunction {
284292 }
285293}
286294enum IterFunctionKind {
287- IntoIter ,
295+ IntoIter ( HirId ) ,
288296 Len ,
289297 IsEmpty ,
290298 Contains ( Span ) ,
@@ -343,7 +351,7 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> {
343351 }
344352 match method_name. ident . name . as_str ( ) {
345353 "into_iter" => self . uses . push ( Some ( IterFunction {
346- func : IterFunctionKind :: IntoIter ,
354+ func : IterFunctionKind :: IntoIter ( expr . hir_id ) ,
347355 span : expr. span ,
348356 } ) ) ,
349357 "len" => self . uses . push ( Some ( IterFunction {
@@ -520,3 +528,61 @@ fn get_captured_ids(cx: &LateContext<'_>, ty: Ty<'_>) -> HirIdSet {
520528
521529 set
522530}
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