@@ -6,9 +6,11 @@ use clippy_utils::macros::root_macro_call_first_node;
6
6
use clippy_utils:: source:: snippet;
7
7
use clippy_utils:: visitors:: { Descend , for_each_expr_without_closures} ;
8
8
use rustc_errors:: Applicability ;
9
- use rustc_hir:: { Block , Destination , Expr , ExprKind , HirId , InlineAsmOperand , Pat , Stmt , StmtKind , StructTailExpr } ;
9
+ use rustc_hir:: {
10
+ Block , Destination , Expr , ExprKind , HirId , InlineAsmOperand , Node , Pat , Stmt , StmtKind , StructTailExpr ,
11
+ } ;
10
12
use rustc_lint:: LateContext ;
11
- use rustc_span:: { Span , sym} ;
13
+ use rustc_span:: { BytePos , Span , sym} ;
12
14
use std:: iter:: once;
13
15
use std:: ops:: ControlFlow ;
14
16
@@ -20,7 +22,7 @@ pub(super) fn check<'tcx>(
20
22
for_loop : Option < & ForLoop < ' _ > > ,
21
23
) {
22
24
match never_loop_block ( cx, block, & mut Vec :: new ( ) , loop_id) {
23
- NeverLoopResult :: Diverging => {
25
+ NeverLoopResult :: Diverging { ref break_spans } => {
24
26
span_lint_and_then ( cx, NEVER_LOOP , span, "this loop never actually loops" , |diag| {
25
27
if let Some ( ForLoop {
26
28
arg : iterator,
@@ -38,10 +40,15 @@ pub(super) fn check<'tcx>(
38
40
Applicability :: Unspecified
39
41
} ;
40
42
41
- diag . span_suggestion_verbose (
43
+ let mut suggestions = vec ! [ (
42
44
for_span. with_hi( iterator. span. hi( ) ) ,
43
- "if you need the first element of the iterator, try writing" ,
44
45
for_to_if_let_sugg( cx, iterator, pat) ,
46
+ ) ] ;
47
+ // Make sure to clear up the diverging sites when we remove a loopp.
48
+ suggestions. extend ( break_spans. iter ( ) . map ( |span| ( * span, String :: new ( ) ) ) ) ;
49
+ diag. multipart_suggestion_verbose (
50
+ "if you need the first element of the iterator, try writing" ,
51
+ suggestions,
45
52
app,
46
53
) ;
47
54
}
@@ -70,22 +77,22 @@ fn contains_any_break_or_continue(block: &Block<'_>) -> bool {
70
77
/// The first two bits of information are in this enum, and the last part is in the
71
78
/// `local_labels` variable, which contains a list of `(block_id, reachable)` pairs ordered by
72
79
/// scope.
73
- #[ derive( Copy , Clone ) ]
80
+ #[ derive( Clone ) ]
74
81
enum NeverLoopResult {
75
82
/// A continue may occur for the main loop.
76
83
MayContinueMainLoop ,
77
84
/// We have not encountered any main loop continue,
78
85
/// but we are diverging (subsequent control flow is not reachable)
79
- Diverging ,
86
+ Diverging { break_spans : Vec < Span > } ,
80
87
/// We have not encountered any main loop continue,
81
88
/// and subsequent control flow is (possibly) reachable
82
89
Normal ,
83
90
}
84
91
85
92
#[ must_use]
86
- fn absorb_break ( arg : NeverLoopResult ) -> NeverLoopResult {
93
+ fn absorb_break ( arg : & NeverLoopResult ) -> NeverLoopResult {
87
94
match arg {
88
- NeverLoopResult :: Diverging | NeverLoopResult :: Normal => NeverLoopResult :: Normal ,
95
+ NeverLoopResult :: Diverging { .. } | NeverLoopResult :: Normal => NeverLoopResult :: Normal ,
89
96
NeverLoopResult :: MayContinueMainLoop => NeverLoopResult :: MayContinueMainLoop ,
90
97
}
91
98
}
@@ -94,7 +101,7 @@ fn absorb_break(arg: NeverLoopResult) -> NeverLoopResult {
94
101
#[ must_use]
95
102
fn combine_seq ( first : NeverLoopResult , second : impl FnOnce ( ) -> NeverLoopResult ) -> NeverLoopResult {
96
103
match first {
97
- NeverLoopResult :: Diverging | NeverLoopResult :: MayContinueMainLoop => first,
104
+ NeverLoopResult :: Diverging { .. } | NeverLoopResult :: MayContinueMainLoop => first,
98
105
NeverLoopResult :: Normal => second ( ) ,
99
106
}
100
107
}
@@ -103,7 +110,7 @@ fn combine_seq(first: NeverLoopResult, second: impl FnOnce() -> NeverLoopResult)
103
110
#[ must_use]
104
111
fn combine_seq_many ( iter : impl IntoIterator < Item = NeverLoopResult > ) -> NeverLoopResult {
105
112
for e in iter {
106
- if let NeverLoopResult :: Diverging | NeverLoopResult :: MayContinueMainLoop = e {
113
+ if let NeverLoopResult :: Diverging { .. } | NeverLoopResult :: MayContinueMainLoop = e {
107
114
return e;
108
115
}
109
116
}
@@ -118,7 +125,19 @@ fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult) -> NeverLoopResult
118
125
NeverLoopResult :: MayContinueMainLoop
119
126
} ,
120
127
( NeverLoopResult :: Normal , _) | ( _, NeverLoopResult :: Normal ) => NeverLoopResult :: Normal ,
121
- ( NeverLoopResult :: Diverging , NeverLoopResult :: Diverging ) => NeverLoopResult :: Diverging ,
128
+ (
129
+ NeverLoopResult :: Diverging {
130
+ break_spans : mut break_spans1,
131
+ } ,
132
+ NeverLoopResult :: Diverging {
133
+ break_spans : mut break_spans2,
134
+ } ,
135
+ ) => {
136
+ break_spans1. append ( & mut break_spans2) ;
137
+ NeverLoopResult :: Diverging {
138
+ break_spans : break_spans1,
139
+ }
140
+ } ,
122
141
}
123
142
}
124
143
@@ -136,15 +155,15 @@ fn never_loop_block<'tcx>(
136
155
combine_seq_many ( iter. map ( |( e, els) | {
137
156
let e = never_loop_expr ( cx, e, local_labels, main_loop_id) ;
138
157
// els is an else block in a let...else binding
139
- els. map_or ( e, |els| {
158
+ els. map_or ( e. clone ( ) , |els| {
140
159
combine_seq ( e, || match never_loop_block ( cx, els, local_labels, main_loop_id) {
141
160
// Returning MayContinueMainLoop here means that
142
161
// we will not evaluate the rest of the body
143
162
NeverLoopResult :: MayContinueMainLoop => NeverLoopResult :: MayContinueMainLoop ,
144
163
// An else block always diverges, so the Normal case should not happen,
145
164
// but the analysis is approximate so it might return Normal anyway.
146
165
// Returning Normal here says that nothing more happens on the main path
147
- NeverLoopResult :: Diverging | NeverLoopResult :: Normal => NeverLoopResult :: Normal ,
166
+ NeverLoopResult :: Diverging { .. } | NeverLoopResult :: Normal => NeverLoopResult :: Normal ,
148
167
} )
149
168
} )
150
169
} ) )
@@ -159,6 +178,45 @@ fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<(&'tcx Expr<'tcx>, Option<&'t
159
178
}
160
179
}
161
180
181
+ fn stmt_source_span ( stmt : & Stmt < ' _ > ) -> Span {
182
+ let call_span = stmt. span . source_callsite ( ) ;
183
+ // if it is a macro call, the span will be missing the trailing semicolon
184
+ if stmt. span == call_span {
185
+ return call_span;
186
+ }
187
+
188
+ // An expression without a trailing semi-colon (must have unit type).
189
+ if let StmtKind :: Expr ( ..) = stmt. kind {
190
+ return call_span;
191
+ }
192
+
193
+ call_span. with_hi ( call_span. hi ( ) + BytePos ( 1 ) )
194
+ }
195
+
196
+ /// Returns a Vec of all the individual spans after the highlighted expression in a block
197
+ fn all_spans_after_expr ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) -> Vec < Span > {
198
+ if let Node :: Stmt ( stmt) = cx. tcx . parent_hir_node ( expr. hir_id ) {
199
+ if let Node :: Block ( block) = cx. tcx . parent_hir_node ( stmt. hir_id ) {
200
+ return block
201
+ . stmts
202
+ . iter ( )
203
+ . skip_while ( |inner| inner. hir_id != stmt. hir_id )
204
+ . map ( stmt_source_span)
205
+ . chain ( if let Some ( e) = block. expr { vec ! [ e. span] } else { vec ! [ ] } )
206
+ . collect ( ) ;
207
+ }
208
+
209
+ return vec ! [ stmt. span] ;
210
+ }
211
+
212
+ vec ! [ ]
213
+ }
214
+
215
+ fn is_label_for_block ( cx : & LateContext < ' _ > , dest : & Destination ) -> bool {
216
+ dest. target_id
217
+ . is_ok_and ( |hir_id| matches ! ( cx. tcx. hir_node( hir_id) , Node :: Block ( _) ) )
218
+ }
219
+
162
220
#[ allow( clippy:: too_many_lines) ]
163
221
fn never_loop_expr < ' tcx > (
164
222
cx : & LateContext < ' tcx > ,
@@ -197,7 +255,7 @@ fn never_loop_expr<'tcx>(
197
255
ExprKind :: Loop ( b, _, _, _) => {
198
256
// We don't attempt to track reachability after a loop,
199
257
// just assume there may have been a break somewhere
200
- absorb_break ( never_loop_block ( cx, b, local_labels, main_loop_id) )
258
+ absorb_break ( & never_loop_block ( cx, b, local_labels, main_loop_id) )
201
259
} ,
202
260
ExprKind :: If ( e, e2, e3) => {
203
261
let e1 = never_loop_expr ( cx, e, local_labels, main_loop_id) ;
@@ -212,9 +270,10 @@ fn never_loop_expr<'tcx>(
212
270
ExprKind :: Match ( e, arms, _) => {
213
271
let e = never_loop_expr ( cx, e, local_labels, main_loop_id) ;
214
272
combine_seq ( e, || {
215
- arms. iter ( ) . fold ( NeverLoopResult :: Diverging , |a, b| {
216
- combine_branches ( a, never_loop_expr ( cx, b. body , local_labels, main_loop_id) )
217
- } )
273
+ arms. iter ( )
274
+ . fold ( NeverLoopResult :: Diverging { break_spans : vec ! [ ] } , |a, b| {
275
+ combine_branches ( a, never_loop_expr ( cx, b. body , local_labels, main_loop_id) )
276
+ } )
218
277
} )
219
278
} ,
220
279
ExprKind :: Block ( b, _) => {
@@ -224,7 +283,7 @@ fn never_loop_expr<'tcx>(
224
283
let ret = never_loop_block ( cx, b, local_labels, main_loop_id) ;
225
284
let jumped_to = b. targeted_by_break && local_labels. pop ( ) . unwrap ( ) . 1 ;
226
285
match ret {
227
- NeverLoopResult :: Diverging if jumped_to => NeverLoopResult :: Normal ,
286
+ NeverLoopResult :: Diverging { .. } if jumped_to => NeverLoopResult :: Normal ,
228
287
_ => ret,
229
288
}
230
289
} ,
@@ -235,25 +294,39 @@ fn never_loop_expr<'tcx>(
235
294
if id == main_loop_id {
236
295
NeverLoopResult :: MayContinueMainLoop
237
296
} else {
238
- NeverLoopResult :: Diverging
297
+ NeverLoopResult :: Diverging {
298
+ break_spans : all_spans_after_expr ( cx, expr) ,
299
+ }
239
300
}
240
301
} ,
241
- ExprKind :: Break ( _ , e ) | ExprKind :: Ret ( e) => {
302
+ ExprKind :: Ret ( e) => {
242
303
let first = e. as_ref ( ) . map_or ( NeverLoopResult :: Normal , |e| {
243
304
never_loop_expr ( cx, e, local_labels, main_loop_id)
244
305
} ) ;
245
306
combine_seq ( first, || {
246
307
// checks if break targets a block instead of a loop
247
- if let ExprKind :: Break ( Destination { target_id : Ok ( t) , .. } , _) = expr. kind
248
- && let Some ( ( _, reachable) ) = local_labels. iter_mut ( ) . find ( |( label, _) | * label == t)
249
- {
250
- * reachable = true ;
308
+ mark_block_as_reachable ( expr, local_labels) ;
309
+ NeverLoopResult :: Diverging { break_spans : vec ! [ ] }
310
+ } )
311
+ } ,
312
+ ExprKind :: Break ( dest, e) => {
313
+ let first = e. as_ref ( ) . map_or ( NeverLoopResult :: Normal , |e| {
314
+ never_loop_expr ( cx, e, local_labels, main_loop_id)
315
+ } ) ;
316
+ combine_seq ( first, || {
317
+ // checks if break targets a block instead of a loop
318
+ mark_block_as_reachable ( expr, local_labels) ;
319
+ NeverLoopResult :: Diverging {
320
+ break_spans : if is_label_for_block ( cx, & dest) {
321
+ vec ! [ ]
322
+ } else {
323
+ all_spans_after_expr ( cx, expr)
324
+ } ,
251
325
}
252
- NeverLoopResult :: Diverging
253
326
} )
254
327
} ,
255
328
ExprKind :: Become ( e) => combine_seq ( never_loop_expr ( cx, e, local_labels, main_loop_id) , || {
256
- NeverLoopResult :: Diverging
329
+ NeverLoopResult :: Diverging { break_spans : vec ! [ ] }
257
330
} ) ,
258
331
ExprKind :: InlineAsm ( asm) => combine_seq_many ( asm. operands . iter ( ) . map ( |( o, _) | match o {
259
332
InlineAsmOperand :: In { expr, .. } | InlineAsmOperand :: InOut { expr, .. } => {
@@ -283,12 +356,12 @@ fn never_loop_expr<'tcx>(
283
356
} ;
284
357
let result = combine_seq ( result, || {
285
358
if cx. typeck_results ( ) . expr_ty ( expr) . is_never ( ) {
286
- NeverLoopResult :: Diverging
359
+ NeverLoopResult :: Diverging { break_spans : vec ! [ ] }
287
360
} else {
288
361
NeverLoopResult :: Normal
289
362
}
290
363
} ) ;
291
- if let NeverLoopResult :: Diverging = result
364
+ if let NeverLoopResult :: Diverging { .. } = result
292
365
&& let Some ( macro_call) = root_macro_call_first_node ( cx, expr)
293
366
&& let Some ( sym:: todo_macro) = cx. tcx . get_diagnostic_name ( macro_call. def_id )
294
367
{
@@ -316,3 +389,11 @@ fn for_to_if_let_sugg(cx: &LateContext<'_>, iterator: &Expr<'_>, pat: &Pat<'_>)
316
389
317
390
format ! ( "if let Some({pat_snippet}) = {iter_snippet}.next()" )
318
391
}
392
+
393
+ fn mark_block_as_reachable ( expr : & Expr < ' _ > , local_labels : & mut [ ( HirId , bool ) ] ) {
394
+ if let ExprKind :: Break ( Destination { target_id : Ok ( t) , .. } , _) = expr. kind
395
+ && let Some ( ( _, reachable) ) = local_labels. iter_mut ( ) . find ( |( label, _) | * label == t)
396
+ {
397
+ * reachable = true ;
398
+ }
399
+ }
0 commit comments