1
1
use super :: WHILE_LET_LOOP ;
2
2
use 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} ;
5
4
use clippy_utils:: ty:: needs_ordered_drop;
6
5
use clippy_utils:: visitors:: any_temporaries_need_ordered_drop;
6
+ use clippy_utils:: { higher, peel_blocks} ;
7
+ use rustc_ast:: BindingMode ;
7
8
use 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 } ;
9
10
use rustc_lint:: LateContext ;
10
11
11
12
pub ( 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 {
15
16
init : Some ( e) ,
16
17
els : None ,
18
+ pat,
19
+ ty,
17
20
..
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 ,
26
24
} ,
27
- ( [ ] , Some ( e) ) => ( e, false ) ,
25
+ ( [ ] , Some ( e) ) => ( e, None ) ,
28
26
_ => return ,
29
27
} ;
28
+ let has_trailing_exprs = loop_block. stmts . len ( ) + usize:: from ( loop_block. expr . is_some ( ) ) > 1 ;
30
29
31
30
if let Some ( if_let) = higher:: IfLet :: hir ( cx, init)
32
31
&& let Some ( else_expr) = if_let. if_else
33
32
&& is_simple_break_expr ( else_expr)
34
33
{
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
+ ) ;
36
43
} else if let ExprKind :: Match ( scrutinee, [ arm1, arm2] , MatchSource :: Normal ) = init. kind
37
44
&& arm1. guard . is_none ( )
38
45
&& arm2. guard . is_none ( )
39
46
&& is_simple_break_expr ( arm2. body )
40
47
{
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 ) ;
42
49
}
43
50
}
44
51
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.
46
54
fn 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 > {
52
55
if let ExprKind :: Block ( b, _) = e. kind {
53
56
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 ,
63
60
}
64
61
} else {
65
- e
62
+ matches ! ( e . kind , ExprKind :: Break ( dest , None ) if dest . label . is_none ( ) )
66
63
}
67
64
}
68
65
@@ -72,6 +69,8 @@ fn could_be_while_let<'tcx>(
72
69
let_pat : & ' tcx Pat < ' _ > ,
73
70
let_expr : & ' tcx Expr < ' _ > ,
74
71
has_trailing_exprs : bool ,
72
+ let_info : Option < ( & Pat < ' _ > , Option < & Ty < ' _ > > ) > ,
73
+ inner_expr : & Expr < ' _ > ,
75
74
) {
76
75
if has_trailing_exprs
77
76
&& ( needs_ordered_drop ( cx, cx. typeck_results ( ) . expr_ty ( let_expr) )
@@ -86,18 +85,46 @@ fn could_be_while_let<'tcx>(
86
85
// 1) it was ugly with big bodies;
87
86
// 2) it was not indented properly;
88
87
// 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
+
90
106
span_lint_and_sugg (
91
107
cx,
92
108
WHILE_LET_LOOP ,
93
109
expr. span ,
94
110
"this loop could be written as a `while let` loop" ,
95
111
"try" ,
96
112
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, ".." ) ,
100
116
) ,
101
- applicability ,
117
+ Applicability :: HasPlaceholders ,
102
118
) ;
103
119
}
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