1
1
use super :: utils:: make_iterator_snippet;
2
2
use super :: NEVER_LOOP ;
3
- use clippy_utils:: consts:: { constant, Constant } ;
4
3
use clippy_utils:: diagnostics:: span_lint_and_then;
5
4
use clippy_utils:: higher:: ForLoop ;
6
5
use clippy_utils:: source:: snippet;
@@ -18,7 +17,7 @@ pub(super) fn check<'tcx>(
18
17
for_loop : Option < & ForLoop < ' _ > > ,
19
18
) {
20
19
match never_loop_block ( cx, block, & mut Vec :: new ( ) , loop_id) {
21
- NeverLoopResult :: AlwaysBreak => {
20
+ NeverLoopResult :: Diverging => {
22
21
span_lint_and_then ( cx, NEVER_LOOP , span, "this loop never actually loops" , |diag| {
23
22
if let Some ( ForLoop {
24
23
arg : iterator,
@@ -39,83 +38,98 @@ pub(super) fn check<'tcx>(
39
38
}
40
39
} ) ;
41
40
} ,
42
- NeverLoopResult :: MayContinueMainLoop | NeverLoopResult :: Otherwise => ( ) ,
43
- NeverLoopResult :: IgnoreUntilEnd ( _) => unreachable ! ( ) ,
41
+ NeverLoopResult :: MayContinueMainLoop | NeverLoopResult :: Normal => ( ) ,
44
42
}
45
43
}
46
44
45
+ /// The `never_loop` analysis keeps track of three things:
46
+ ///
47
+ /// * Has any (reachable) code path hit a `continue` of the main loop?
48
+ /// * Is the current code path diverging (that is, the next expression is not reachable)
49
+ /// * For each block label `'a` inside the main loop, has any (reachable) code path encountered a
50
+ /// `break 'a`?
51
+ ///
52
+ /// The first two bits of information are in this enum, and the last part is in the
53
+ /// `local_labels` variable, which contains a list of `(block_id, reachable)` pairs ordered by
54
+ /// scope.
47
55
#[ derive( Copy , Clone ) ]
48
56
enum NeverLoopResult {
49
- // A break/return always get triggered but not necessarily for the main loop.
50
- AlwaysBreak ,
51
- // A continue may occur for the main loop.
57
+ /// A continue may occur for the main loop.
52
58
MayContinueMainLoop ,
53
- // Ignore everything until the end of the block with this id
54
- IgnoreUntilEnd ( HirId ) ,
55
- Otherwise ,
59
+ /// We have not encountered any main loop continue,
60
+ /// but we are diverging (subsequent control flow is not reachable)
61
+ Diverging ,
62
+ /// We have not encountered any main loop continue,
63
+ /// and subsequent control flow is (possibly) reachable
64
+ Normal ,
56
65
}
57
66
58
67
#[ must_use]
59
68
fn absorb_break ( arg : NeverLoopResult ) -> NeverLoopResult {
60
69
match arg {
61
- NeverLoopResult :: AlwaysBreak | NeverLoopResult :: Otherwise => NeverLoopResult :: Otherwise ,
70
+ NeverLoopResult :: Diverging | NeverLoopResult :: Normal => NeverLoopResult :: Normal ,
62
71
NeverLoopResult :: MayContinueMainLoop => NeverLoopResult :: MayContinueMainLoop ,
63
- NeverLoopResult :: IgnoreUntilEnd ( id) => NeverLoopResult :: IgnoreUntilEnd ( id) ,
64
72
}
65
73
}
66
74
67
75
// Combine two results for parts that are called in order.
68
76
#[ must_use]
69
- fn combine_seq ( first : NeverLoopResult , second : NeverLoopResult ) -> NeverLoopResult {
77
+ fn combine_seq ( first : NeverLoopResult , second : impl FnOnce ( ) -> NeverLoopResult ) -> NeverLoopResult {
70
78
match first {
71
- NeverLoopResult :: AlwaysBreak | NeverLoopResult :: MayContinueMainLoop | NeverLoopResult :: IgnoreUntilEnd ( _) => {
72
- first
73
- } ,
74
- NeverLoopResult :: Otherwise => second,
79
+ NeverLoopResult :: Diverging | NeverLoopResult :: MayContinueMainLoop => first,
80
+ NeverLoopResult :: Normal => second ( ) ,
81
+ }
82
+ }
83
+
84
+ // Combine an iterator of results for parts that are called in order.
85
+ #[ must_use]
86
+ fn combine_seq_many ( iter : impl IntoIterator < Item = NeverLoopResult > ) -> NeverLoopResult {
87
+ for e in iter {
88
+ if let NeverLoopResult :: Diverging | NeverLoopResult :: MayContinueMainLoop = e {
89
+ return e;
90
+ }
75
91
}
92
+ NeverLoopResult :: Normal
76
93
}
77
94
78
95
// Combine two results where only one of the part may have been executed.
79
96
#[ must_use]
80
- fn combine_branches ( b1 : NeverLoopResult , b2 : NeverLoopResult , ignore_ids : & [ HirId ] ) -> NeverLoopResult {
97
+ fn combine_branches ( b1 : NeverLoopResult , b2 : NeverLoopResult ) -> NeverLoopResult {
81
98
match ( b1, b2) {
82
- ( NeverLoopResult :: IgnoreUntilEnd ( a) , NeverLoopResult :: IgnoreUntilEnd ( b) ) => {
83
- if ignore_ids. iter ( ) . find ( |& e| e == & a || e == & b) . unwrap ( ) == & a {
84
- NeverLoopResult :: IgnoreUntilEnd ( b)
85
- } else {
86
- NeverLoopResult :: IgnoreUntilEnd ( a)
87
- }
88
- } ,
89
- ( i @ NeverLoopResult :: IgnoreUntilEnd ( _) , NeverLoopResult :: AlwaysBreak )
90
- | ( NeverLoopResult :: AlwaysBreak , i @ NeverLoopResult :: IgnoreUntilEnd ( _) ) => i,
91
- ( NeverLoopResult :: AlwaysBreak , NeverLoopResult :: AlwaysBreak ) => NeverLoopResult :: AlwaysBreak ,
92
99
( NeverLoopResult :: MayContinueMainLoop , _) | ( _, NeverLoopResult :: MayContinueMainLoop ) => {
93
100
NeverLoopResult :: MayContinueMainLoop
94
101
} ,
95
- ( NeverLoopResult :: Otherwise , _) | ( _, NeverLoopResult :: Otherwise ) => NeverLoopResult :: Otherwise ,
102
+ ( NeverLoopResult :: Normal , _) | ( _, NeverLoopResult :: Normal ) => NeverLoopResult :: Normal ,
103
+ ( NeverLoopResult :: Diverging , NeverLoopResult :: Diverging ) => NeverLoopResult :: Diverging ,
96
104
}
97
105
}
98
106
99
107
fn never_loop_block < ' tcx > (
100
108
cx : & LateContext < ' tcx > ,
101
109
block : & Block < ' tcx > ,
102
- ignore_ids : & mut Vec < HirId > ,
110
+ local_labels : & mut Vec < ( HirId , bool ) > ,
103
111
main_loop_id : HirId ,
104
112
) -> NeverLoopResult {
105
113
let iter = block
106
114
. stmts
107
115
. iter ( )
108
116
. filter_map ( stmt_to_expr)
109
117
. chain ( block. expr . map ( |expr| ( expr, None ) ) ) ;
110
-
111
- iter. map ( |( e, els) | {
112
- let e = never_loop_expr ( cx, e, ignore_ids, main_loop_id) ;
118
+ combine_seq_many ( iter. map ( |( e, els) | {
119
+ let e = never_loop_expr ( cx, e, local_labels, main_loop_id) ;
113
120
// els is an else block in a let...else binding
114
121
els. map_or ( e, |els| {
115
- combine_branches ( e, never_loop_block ( cx, els, ignore_ids, main_loop_id) , ignore_ids)
122
+ combine_seq ( e, || match never_loop_block ( cx, els, local_labels, main_loop_id) {
123
+ // Returning MayContinueMainLoop here means that
124
+ // we will not evaluate the rest of the body
125
+ NeverLoopResult :: MayContinueMainLoop => NeverLoopResult :: MayContinueMainLoop ,
126
+ // An else block always diverges, so the Normal case should not happen,
127
+ // but the analysis is approximate so it might return Normal anyway.
128
+ // Returning Normal here says that nothing more happens on the main path
129
+ NeverLoopResult :: Diverging | NeverLoopResult :: Normal => NeverLoopResult :: Normal ,
130
+ } )
116
131
} )
117
- } )
118
- . fold ( NeverLoopResult :: Otherwise , combine_seq)
132
+ } ) )
119
133
}
120
134
121
135
fn stmt_to_expr < ' tcx > ( stmt : & Stmt < ' tcx > ) -> Option < ( & ' tcx Expr < ' tcx > , Option < & ' tcx Block < ' tcx > > ) > {
@@ -131,7 +145,7 @@ fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<(&'tcx Expr<'tcx>, Option<&'t
131
145
fn never_loop_expr < ' tcx > (
132
146
cx : & LateContext < ' tcx > ,
133
147
expr : & Expr < ' tcx > ,
134
- ignore_ids : & mut Vec < HirId > ,
148
+ local_labels : & mut Vec < ( HirId , bool ) > ,
135
149
main_loop_id : HirId ,
136
150
) -> NeverLoopResult {
137
151
match expr. kind {
@@ -141,66 +155,61 @@ fn never_loop_expr<'tcx>(
141
155
| ExprKind :: Field ( e, _)
142
156
| ExprKind :: AddrOf ( _, _, e)
143
157
| ExprKind :: Repeat ( e, _)
144
- | ExprKind :: DropTemps ( e) => never_loop_expr ( cx, e, ignore_ids , main_loop_id) ,
145
- ExprKind :: Let ( let_expr) => never_loop_expr ( cx, let_expr. init , ignore_ids , main_loop_id) ,
146
- ExprKind :: Array ( es) | ExprKind :: Tup ( es) => never_loop_expr_all ( cx, & mut es. iter ( ) , ignore_ids , main_loop_id) ,
158
+ | ExprKind :: DropTemps ( e) => never_loop_expr ( cx, e, local_labels , main_loop_id) ,
159
+ ExprKind :: Let ( let_expr) => never_loop_expr ( cx, let_expr. init , local_labels , main_loop_id) ,
160
+ ExprKind :: Array ( es) | ExprKind :: Tup ( es) => never_loop_expr_all ( cx, es. iter ( ) , local_labels , main_loop_id) ,
147
161
ExprKind :: MethodCall ( _, receiver, es, _) => never_loop_expr_all (
148
162
cx,
149
- & mut std:: iter:: once ( receiver) . chain ( es. iter ( ) ) ,
150
- ignore_ids ,
163
+ std:: iter:: once ( receiver) . chain ( es. iter ( ) ) ,
164
+ local_labels ,
151
165
main_loop_id,
152
166
) ,
153
167
ExprKind :: Struct ( _, fields, base) => {
154
- let fields = never_loop_expr_all ( cx, & mut fields. iter ( ) . map ( |f| f. expr ) , ignore_ids , main_loop_id) ;
168
+ let fields = never_loop_expr_all ( cx, fields. iter ( ) . map ( |f| f. expr ) , local_labels , main_loop_id) ;
155
169
if let Some ( base) = base {
156
- combine_seq ( fields, never_loop_expr ( cx, base, ignore_ids , main_loop_id) )
170
+ combine_seq ( fields, || never_loop_expr ( cx, base, local_labels , main_loop_id) )
157
171
} else {
158
172
fields
159
173
}
160
174
} ,
161
- ExprKind :: Call ( e, es) => never_loop_expr_all ( cx, & mut once ( e) . chain ( es. iter ( ) ) , ignore_ids , main_loop_id) ,
175
+ ExprKind :: Call ( e, es) => never_loop_expr_all ( cx, once ( e) . chain ( es. iter ( ) ) , local_labels , main_loop_id) ,
162
176
ExprKind :: Binary ( _, e1, e2)
163
177
| ExprKind :: Assign ( e1, e2, _)
164
178
| ExprKind :: AssignOp ( _, e1, e2)
165
- | ExprKind :: Index ( e1, e2, _) => {
166
- never_loop_expr_all ( cx, & mut [ e1, e2] . iter ( ) . copied ( ) , ignore_ids, main_loop_id)
167
- } ,
179
+ | ExprKind :: Index ( e1, e2, _) => never_loop_expr_all ( cx, [ e1, e2] . iter ( ) . copied ( ) , local_labels, main_loop_id) ,
168
180
ExprKind :: Loop ( b, _, _, _) => {
169
- // Break can come from the inner loop so remove them.
170
- absorb_break ( never_loop_block ( cx, b, ignore_ids, main_loop_id) )
181
+ // We don't attempt to track reachability after a loop,
182
+ // just assume there may have been a break somewhere
183
+ absorb_break ( never_loop_block ( cx, b, local_labels, main_loop_id) )
171
184
} ,
172
185
ExprKind :: If ( e, e2, e3) => {
173
- let e1 = never_loop_expr ( cx, e, ignore_ids, main_loop_id) ;
174
- let e2 = never_loop_expr ( cx, e2, ignore_ids, main_loop_id) ;
175
- // If we know the `if` condition evaluates to `true`, don't check everything past it; it
176
- // should just return whatever's evaluated for `e1` and `e2` since `e3` is unreachable
177
- if let Some ( Constant :: Bool ( true ) ) = constant ( cx, cx. typeck_results ( ) , e) {
178
- return combine_seq ( e1, e2) ;
179
- }
180
- let e3 = e3. as_ref ( ) . map_or ( NeverLoopResult :: Otherwise , |e| {
181
- never_loop_expr ( cx, e, ignore_ids, main_loop_id)
182
- } ) ;
183
- combine_seq ( e1, combine_branches ( e2, e3, ignore_ids) )
186
+ let e1 = never_loop_expr ( cx, e, local_labels, main_loop_id) ;
187
+ combine_seq ( e1, || {
188
+ let e2 = never_loop_expr ( cx, e2, local_labels, main_loop_id) ;
189
+ let e3 = e3. as_ref ( ) . map_or ( NeverLoopResult :: Normal , |e| {
190
+ never_loop_expr ( cx, e, local_labels, main_loop_id)
191
+ } ) ;
192
+ combine_branches ( e2, e3)
193
+ } )
184
194
} ,
185
195
ExprKind :: Match ( e, arms, _) => {
186
- let e = never_loop_expr ( cx, e, ignore_ids , main_loop_id) ;
196
+ let e = never_loop_expr ( cx, e, local_labels , main_loop_id) ;
187
197
if arms. is_empty ( ) {
188
198
e
189
199
} else {
190
- let arms = never_loop_expr_branch ( cx, & mut arms. iter ( ) . map ( |a| a. body ) , ignore_ids, main_loop_id) ;
191
- combine_seq ( e, arms)
200
+ combine_seq ( e, || {
201
+ never_loop_expr_branch ( cx, & mut arms. iter ( ) . map ( |a| a. body ) , local_labels, main_loop_id)
202
+ } )
192
203
}
193
204
} ,
194
205
ExprKind :: Block ( b, l) => {
195
206
if l. is_some ( ) {
196
- ignore_ids. push ( b. hir_id ) ;
197
- }
198
- let ret = never_loop_block ( cx, b, ignore_ids, main_loop_id) ;
199
- if l. is_some ( ) {
200
- ignore_ids. pop ( ) ;
207
+ local_labels. push ( ( b. hir_id , false ) ) ;
201
208
}
209
+ let ret = never_loop_block ( cx, b, local_labels, main_loop_id) ;
210
+ let jumped_to = l. is_some ( ) && local_labels. pop ( ) . unwrap ( ) . 1 ;
202
211
match ret {
203
- NeverLoopResult :: IgnoreUntilEnd ( a ) if a == b . hir_id = > NeverLoopResult :: Otherwise ,
212
+ NeverLoopResult :: Diverging if jumped_to = > NeverLoopResult :: Normal ,
204
213
_ => ret,
205
214
}
206
215
} ,
@@ -211,73 +220,70 @@ fn never_loop_expr<'tcx>(
211
220
if id == main_loop_id {
212
221
NeverLoopResult :: MayContinueMainLoop
213
222
} else {
214
- NeverLoopResult :: AlwaysBreak
223
+ NeverLoopResult :: Diverging
215
224
}
216
225
} ,
217
- // checks if break targets a block instead of a loop
218
- ExprKind :: Break ( Destination { target_id : Ok ( t) , .. } , e) if ignore_ids. contains ( & t) => e
219
- . map_or ( NeverLoopResult :: IgnoreUntilEnd ( t) , |e| {
220
- never_loop_expr ( cx, e, ignore_ids, main_loop_id)
221
- } ) ,
222
- ExprKind :: Break ( _, e) | ExprKind :: Ret ( e) => e. as_ref ( ) . map_or ( NeverLoopResult :: AlwaysBreak , |e| {
223
- combine_seq (
224
- never_loop_expr ( cx, e, ignore_ids, main_loop_id) ,
225
- NeverLoopResult :: AlwaysBreak ,
226
- )
227
- } ) ,
228
- ExprKind :: Become ( e) => combine_seq (
229
- never_loop_expr ( cx, e, ignore_ids, main_loop_id) ,
230
- NeverLoopResult :: AlwaysBreak ,
231
- ) ,
232
- ExprKind :: InlineAsm ( asm) => asm
233
- . operands
234
- . iter ( )
235
- . map ( |( o, _) | match o {
236
- InlineAsmOperand :: In { expr, .. } | InlineAsmOperand :: InOut { expr, .. } => {
237
- never_loop_expr ( cx, expr, ignore_ids, main_loop_id)
238
- } ,
239
- InlineAsmOperand :: Out { expr, .. } => {
240
- never_loop_expr_all ( cx, & mut expr. iter ( ) . copied ( ) , ignore_ids, main_loop_id)
241
- } ,
242
- InlineAsmOperand :: SplitInOut { in_expr, out_expr, .. } => never_loop_expr_all (
243
- cx,
244
- & mut once ( * in_expr) . chain ( out_expr. iter ( ) . copied ( ) ) ,
245
- ignore_ids,
246
- main_loop_id,
247
- ) ,
248
- InlineAsmOperand :: Const { .. }
249
- | InlineAsmOperand :: SymFn { .. }
250
- | InlineAsmOperand :: SymStatic { .. } => NeverLoopResult :: Otherwise ,
226
+ ExprKind :: Break ( _, e) | ExprKind :: Ret ( e) => {
227
+ let first = e. as_ref ( ) . map_or ( NeverLoopResult :: Normal , |e| {
228
+ never_loop_expr ( cx, e, local_labels, main_loop_id)
229
+ } ) ;
230
+ combine_seq ( first, || {
231
+ // checks if break targets a block instead of a loop
232
+ if let ExprKind :: Break ( Destination { target_id : Ok ( t) , .. } , _) = expr. kind {
233
+ if let Some ( ( _, reachable) ) = local_labels. iter_mut ( ) . find ( |( label, _) | * label == t) {
234
+ * reachable = true ;
235
+ }
236
+ }
237
+ NeverLoopResult :: Diverging
251
238
} )
252
- . fold ( NeverLoopResult :: Otherwise , combine_seq) ,
239
+ } ,
240
+ ExprKind :: Become ( e) => combine_seq ( never_loop_expr ( cx, e, local_labels, main_loop_id) , || {
241
+ NeverLoopResult :: Diverging
242
+ } ) ,
243
+ ExprKind :: InlineAsm ( asm) => combine_seq_many ( asm. operands . iter ( ) . map ( |( o, _) | match o {
244
+ InlineAsmOperand :: In { expr, .. } | InlineAsmOperand :: InOut { expr, .. } => {
245
+ never_loop_expr ( cx, expr, local_labels, main_loop_id)
246
+ } ,
247
+ InlineAsmOperand :: Out { expr, .. } => {
248
+ never_loop_expr_all ( cx, expr. iter ( ) . copied ( ) , local_labels, main_loop_id)
249
+ } ,
250
+ InlineAsmOperand :: SplitInOut { in_expr, out_expr, .. } => never_loop_expr_all (
251
+ cx,
252
+ once ( * in_expr) . chain ( out_expr. iter ( ) . copied ( ) ) ,
253
+ local_labels,
254
+ main_loop_id,
255
+ ) ,
256
+ InlineAsmOperand :: Const { .. } | InlineAsmOperand :: SymFn { .. } | InlineAsmOperand :: SymStatic { .. } => {
257
+ NeverLoopResult :: Normal
258
+ } ,
259
+ } ) ) ,
253
260
ExprKind :: OffsetOf ( _, _)
254
261
| ExprKind :: Yield ( _, _)
255
262
| ExprKind :: Closure { .. }
256
263
| ExprKind :: Path ( _)
257
264
| ExprKind :: ConstBlock ( _)
258
265
| ExprKind :: Lit ( _)
259
- | ExprKind :: Err ( _) => NeverLoopResult :: Otherwise ,
266
+ | ExprKind :: Err ( _) => NeverLoopResult :: Normal ,
260
267
}
261
268
}
262
269
263
270
fn never_loop_expr_all < ' tcx , T : Iterator < Item = & ' tcx Expr < ' tcx > > > (
264
271
cx : & LateContext < ' tcx > ,
265
- es : & mut T ,
266
- ignore_ids : & mut Vec < HirId > ,
272
+ es : T ,
273
+ local_labels : & mut Vec < ( HirId , bool ) > ,
267
274
main_loop_id : HirId ,
268
275
) -> NeverLoopResult {
269
- es. map ( |e| never_loop_expr ( cx, e, ignore_ids, main_loop_id) )
270
- . fold ( NeverLoopResult :: Otherwise , combine_seq)
276
+ combine_seq_many ( es. map ( |e| never_loop_expr ( cx, e, local_labels, main_loop_id) ) )
271
277
}
272
278
273
279
fn never_loop_expr_branch < ' tcx , T : Iterator < Item = & ' tcx Expr < ' tcx > > > (
274
280
cx : & LateContext < ' tcx > ,
275
281
e : & mut T ,
276
- ignore_ids : & mut Vec < HirId > ,
282
+ local_labels : & mut Vec < ( HirId , bool ) > ,
277
283
main_loop_id : HirId ,
278
284
) -> NeverLoopResult {
279
- e. fold ( NeverLoopResult :: AlwaysBreak , |a, b| {
280
- combine_branches ( a, never_loop_expr ( cx, b, ignore_ids , main_loop_id) , ignore_ids )
285
+ e. fold ( NeverLoopResult :: Diverging , |a, b| {
286
+ combine_branches ( a, never_loop_expr ( cx, b, local_labels , main_loop_id) )
281
287
} )
282
288
}
283
289
0 commit comments