@@ -4,13 +4,14 @@ use clippy_utils::diagnostics::span_lint_and_then;
44use clippy_utils:: higher:: ForLoop ;
55use clippy_utils:: macros:: root_macro_call_first_node;
66use clippy_utils:: source:: snippet;
7+ use clippy_utils:: sym;
78use clippy_utils:: visitors:: { Descend , for_each_expr_without_closures} ;
89use rustc_errors:: Applicability ;
910use rustc_hir:: {
1011 Block , Destination , Expr , ExprKind , HirId , InlineAsm , InlineAsmOperand , Node , Pat , Stmt , StmtKind , StructTailExpr ,
1112} ;
1213use rustc_lint:: LateContext ;
13- use rustc_span:: { BytePos , Span , sym } ;
14+ use rustc_span:: { BytePos , Span } ;
1415use std:: iter:: once;
1516use std:: ops:: ControlFlow ;
1617
@@ -72,6 +73,55 @@ pub(super) fn check<'tcx>(
7273 }
7374}
7475
76+ pub ( super ) fn check_iterator_reduction < ' tcx > (
77+ cx : & LateContext < ' tcx > ,
78+ expr : & ' tcx Expr < ' tcx > ,
79+ recv : & ' tcx Expr < ' tcx > ,
80+ args : & ' tcx [ Expr < ' tcx > ] ,
81+ ) {
82+ // identify which argument is the closure based on the method kind
83+ let Some ( method_name) = ( match expr. kind {
84+ ExprKind :: MethodCall ( path, ..) => Some ( path. ident . name ) ,
85+ _ => None ,
86+ } ) else {
87+ return ;
88+ } ;
89+
90+ let closure_arg = match method_name {
91+ sym:: for_each | sym:: try_for_each | sym:: reduce | sym:: all | sym:: any => args. get ( 0 ) ,
92+ sym:: fold | sym:: try_fold => args. get ( 1 ) ,
93+ _ => None ,
94+ } ;
95+
96+ let Some ( closure_arg) = closure_arg else {
97+ return ;
98+ } ;
99+
100+ // extract the closure body
101+ let closure_body = match closure_arg. kind {
102+ ExprKind :: Closure ( closure) => cx. tcx . hir_body ( closure. body ) . value ,
103+ _ => return ,
104+ } ;
105+
106+ let body_ty = cx. typeck_results ( ) . expr_ty ( closure_body) ;
107+ if body_ty. is_never ( ) {
108+ span_lint_and_then (
109+ cx,
110+ NEVER_LOOP ,
111+ expr. span ,
112+ "this iterator reduction never loops (closure always diverges)" ,
113+ |diag| {
114+ let mut app = Applicability :: Unspecified ;
115+ let recv_snip = make_iterator_snippet ( cx, recv, & mut app) ;
116+ diag. note ( "if you only need one element, `if let Some(x) = iter.next()` is clearer" ) ;
117+ let sugg = format ! ( "if let Some(x) = {recv_snip}.next() {{ ... }}" ) ;
118+ diag. span_suggestion_verbose ( expr. span , "consider this pattern" , sugg, app) ;
119+ } ,
120+ ) ;
121+ return ;
122+ }
123+ }
124+
75125fn contains_any_break_or_continue ( block : & Block < ' _ > ) -> bool {
76126 for_each_expr_without_closures ( block, |e| match e. kind {
77127 ExprKind :: Break ( ..) | ExprKind :: Continue ( ..) => ControlFlow :: Break ( ( ) ) ,
0 commit comments