11use super :: WHILE_LET_LOOP ;
22use clippy_utils:: diagnostics:: span_lint_and_sugg;
3- use clippy_utils:: higher;
4- use clippy_utils:: source:: snippet_with_applicability;
3+ use clippy_utils:: source:: { snippet, snippet_indent, snippet_opt} ;
54use clippy_utils:: ty:: needs_ordered_drop;
65use clippy_utils:: visitors:: any_temporaries_need_ordered_drop;
6+ use clippy_utils:: { higher, peel_blocks} ;
7+ use rustc_ast:: BindingMode ;
78use rustc_errors:: Applicability ;
8- use rustc_hir:: { Block , Expr , ExprKind , LetStmt , MatchSource , Pat , StmtKind } ;
9+ use rustc_hir:: { Block , Expr , ExprKind , LetStmt , MatchSource , Pat , PatKind , Path , QPath , StmtKind , Ty } ;
910use rustc_lint:: LateContext ;
1011
1112pub ( super ) fn check < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > , loop_block : & ' tcx Block < ' _ > ) {
12- let ( init, has_trailing_exprs ) = match ( loop_block. stmts , loop_block. expr ) {
13- ( [ stmt, stmts @ ..] , expr ) => {
14- if let StmtKind :: Let ( & LetStmt {
13+ let ( init, let_info ) = match ( loop_block. stmts , loop_block. expr ) {
14+ ( [ stmt, ..] , _ ) => match stmt . kind {
15+ StmtKind :: Let ( LetStmt {
1516 init : Some ( e) ,
1617 els : None ,
18+ pat,
19+ ty,
1720 ..
18- } )
19- | StmtKind :: Semi ( e)
20- | StmtKind :: Expr ( e) = stmt. kind
21- {
22- ( e, !stmts. is_empty ( ) || expr. is_some ( ) )
23- } else {
24- return ;
25- }
21+ } ) => ( * e, Some ( ( * pat, * ty) ) ) ,
22+ StmtKind :: Semi ( e) | StmtKind :: Expr ( e) => ( e, None ) ,
23+ _ => return ,
2624 } ,
27- ( [ ] , Some ( e) ) => ( e, false ) ,
25+ ( [ ] , Some ( e) ) => ( e, None ) ,
2826 _ => return ,
2927 } ;
28+ let has_trailing_exprs = loop_block. stmts . len ( ) + usize:: from ( loop_block. expr . is_some ( ) ) > 1 ;
3029
3130 if let Some ( if_let) = higher:: IfLet :: hir ( cx, init)
3231 && let Some ( else_expr) = if_let. if_else
3332 && is_simple_break_expr ( else_expr)
3433 {
35- could_be_while_let ( cx, expr, if_let. let_pat , if_let. let_expr , has_trailing_exprs) ;
34+ could_be_while_let (
35+ cx,
36+ expr,
37+ if_let. let_pat ,
38+ if_let. let_expr ,
39+ has_trailing_exprs,
40+ let_info,
41+ if_let. if_then ,
42+ ) ;
3643 } else if let ExprKind :: Match ( scrutinee, [ arm1, arm2] , MatchSource :: Normal ) = init. kind
3744 && arm1. guard . is_none ( )
3845 && arm2. guard . is_none ( )
3946 && is_simple_break_expr ( arm2. body )
4047 {
41- could_be_while_let ( cx, expr, arm1. pat , scrutinee, has_trailing_exprs) ;
48+ could_be_while_let ( cx, expr, arm1. pat , scrutinee, has_trailing_exprs, let_info , arm1 . body ) ;
4249 }
4350}
4451
45- /// Returns `true` if expr contains a single break expression without a label or eub-expression.
52+ /// Returns `true` if expr contains a single break expression without a label or sub-expression,
53+ /// possibly embedded in blocks.
4654fn is_simple_break_expr ( e : & Expr < ' _ > ) -> bool {
47- matches ! ( peel_blocks( e) . kind, ExprKind :: Break ( dest, None ) if dest. label. is_none( ) )
48- }
49-
50- /// Removes any blocks containing only a single expression.
51- fn peel_blocks < ' tcx > ( e : & ' tcx Expr < ' tcx > ) -> & ' tcx Expr < ' tcx > {
5255 if let ExprKind :: Block ( b, _) = e. kind {
5356 match ( b. stmts , b. expr ) {
54- ( [ s] , None ) => {
55- if let StmtKind :: Expr ( e) | StmtKind :: Semi ( e) = s. kind {
56- peel_blocks ( e)
57- } else {
58- e
59- }
60- } ,
61- ( [ ] , Some ( e) ) => peel_blocks ( e) ,
62- _ => e,
57+ ( [ s] , None ) => matches ! ( s. kind, StmtKind :: Expr ( e) | StmtKind :: Semi ( e) if is_simple_break_expr( e) ) ,
58+ ( [ ] , Some ( e) ) => is_simple_break_expr ( e) ,
59+ _ => false ,
6360 }
6461 } else {
65- e
62+ matches ! ( e . kind , ExprKind :: Break ( dest , None ) if dest . label . is_none ( ) )
6663 }
6764}
6865
@@ -72,6 +69,8 @@ fn could_be_while_let<'tcx>(
7269 let_pat : & ' tcx Pat < ' _ > ,
7370 let_expr : & ' tcx Expr < ' _ > ,
7471 has_trailing_exprs : bool ,
72+ let_info : Option < ( & Pat < ' _ > , Option < & Ty < ' _ > > ) > ,
73+ inner_expr : & Expr < ' _ > ,
7574) {
7675 if has_trailing_exprs
7776 && ( needs_ordered_drop ( cx, cx. typeck_results ( ) . expr_ty ( let_expr) )
@@ -86,18 +85,46 @@ fn could_be_while_let<'tcx>(
8685 // 1) it was ugly with big bodies;
8786 // 2) it was not indented properly;
8887 // 3) it wasn’t very smart (see #675).
89- let mut applicability = Applicability :: HasPlaceholders ;
88+ let inner_content = if let Some ( ( pat, ty) ) = let_info
89+ // Prevent trivial reassignments such as `let x = x;` or `let _ = …;`, but
90+ // keep them if the type has been explicitly specified.
91+ && ( !is_trivial_assignment ( pat, peel_blocks ( inner_expr) ) || ty. is_some ( ) )
92+ && let Some ( pat_str) = snippet_opt ( cx, pat. span )
93+ && let Some ( init_str) = snippet_opt ( cx, peel_blocks ( inner_expr) . span )
94+ {
95+ let ty_str = ty
96+ . map ( |ty| format ! ( ": {}" , snippet( cx, ty. span, "_" ) ) )
97+ . unwrap_or_default ( ) ;
98+ format ! (
99+ "\n {indent} let {pat_str}{ty_str} = {init_str};\n {indent} ..\n {indent}" ,
100+ indent = snippet_indent( cx, expr. span) . unwrap_or_default( ) ,
101+ )
102+ } else {
103+ " .. " . into ( )
104+ } ;
105+
90106 span_lint_and_sugg (
91107 cx,
92108 WHILE_LET_LOOP ,
93109 expr. span ,
94110 "this loop could be written as a `while let` loop" ,
95111 "try" ,
96112 format ! (
97- "while let {} = {} {{ .. }}" ,
98- snippet_with_applicability ( cx, let_pat. span, ".." , & mut applicability ) ,
99- snippet_with_applicability ( cx, let_expr. span, ".." , & mut applicability ) ,
113+ "while let {} = {} {{{inner_content} }}" ,
114+ snippet ( cx, let_pat. span, ".." ) ,
115+ snippet ( cx, let_expr. span, ".." ) ,
100116 ) ,
101- applicability ,
117+ Applicability :: HasPlaceholders ,
102118 ) ;
103119}
120+
121+ fn is_trivial_assignment ( pat : & Pat < ' _ > , init : & Expr < ' _ > ) -> bool {
122+ match ( pat. kind , init. kind ) {
123+ ( PatKind :: Wild , _) => true ,
124+ (
125+ PatKind :: Binding ( BindingMode :: NONE , _, pat_ident, None ) ,
126+ ExprKind :: Path ( QPath :: Resolved ( None , Path { segments : [ init] , .. } ) ) ,
127+ ) => pat_ident. name == init. ident . name ,
128+ _ => false ,
129+ }
130+ }
0 commit comments