1
+ use std:: collections:: BTreeMap ;
2
+ use std:: fmt;
1
3
use Context :: * ;
2
4
3
5
use rustc_hir as hir;
@@ -25,22 +27,55 @@ enum Context {
25
27
Closure ( Span ) ,
26
28
Coroutine { coroutine_span : Span , kind : hir:: CoroutineDesugaring , source : hir:: CoroutineSource } ,
27
29
UnlabeledBlock ( Span ) ,
30
+ UnlabeledIfBlock ( Span ) ,
28
31
LabeledBlock ,
29
32
Constant ,
30
33
}
31
34
32
- #[ derive( Copy , Clone ) ]
35
+ #[ derive( Clone ) ]
36
+ struct BlockInfo {
37
+ name : String ,
38
+ spans : Vec < Span > ,
39
+ suggs : Vec < Span > ,
40
+ }
41
+
42
+ #[ derive( PartialEq ) ]
43
+ enum BreakContextKind {
44
+ Break ,
45
+ Continue ,
46
+ }
47
+
48
+ impl fmt:: Display for BreakContextKind {
49
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
50
+ match self {
51
+ BreakContextKind :: Break => "break" ,
52
+ BreakContextKind :: Continue => "continue" ,
53
+ }
54
+ . fmt ( f)
55
+ }
56
+ }
57
+
58
+ #[ derive( Clone ) ]
33
59
struct CheckLoopVisitor < ' a , ' tcx > {
34
60
sess : & ' a Session ,
35
61
tcx : TyCtxt < ' tcx > ,
36
- cx : Context ,
62
+ // Keep track of a stack of contexts, so that suggestions
63
+ // are not made for contexts where it would be incorrect,
64
+ // such as adding a label for an `if`.
65
+ // e.g. `if 'foo: {}` would be incorrect.
66
+ cx_stack : Vec < Context > ,
67
+ block_breaks : BTreeMap < Span , BlockInfo > ,
37
68
}
38
69
39
70
fn check_mod_loops ( tcx : TyCtxt < ' _ > , module_def_id : LocalModDefId ) {
40
- tcx. hir ( ) . visit_item_likes_in_module (
41
- module_def_id,
42
- & mut CheckLoopVisitor { sess : tcx. sess , tcx, cx : Normal } ,
43
- ) ;
71
+ let mut check = CheckLoopVisitor {
72
+ sess : tcx. sess ,
73
+ tcx,
74
+ cx_stack : vec ! [ Normal ] ,
75
+ block_breaks : Default :: default ( ) ,
76
+ } ;
77
+ tcx. hir ( ) . visit_item_likes_in_module ( module_def_id, & mut check) ;
78
+ check. report_outside_loop_error ( ) ;
44
79
}
45
80
46
81
pub ( crate ) fn provide ( providers : & mut Providers ) {
@@ -83,6 +118,45 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
83
118
84
119
fn visit_expr ( & mut self , e : & ' hir hir:: Expr < ' hir > ) {
85
120
match e. kind {
121
+ hir:: ExprKind :: If ( cond, then, else_opt) => {
122
+ self . visit_expr ( cond) ;
123
+
124
+ let get_block = |ck_loop : & CheckLoopVisitor < ' a , ' hir > ,
125
+ expr : & hir:: Expr < ' hir > |
126
+ -> Option < & hir:: Block < ' hir > > {
127
+ if let hir:: ExprKind :: Block ( b, None ) = expr. kind
128
+ && matches ! (
129
+ ck_loop. cx_stack. last( ) ,
130
+ Some ( & Normal )
131
+ | Some ( & Constant )
132
+ | Some ( & UnlabeledBlock ( _) )
133
+ | Some ( & UnlabeledIfBlock ( _) )
134
+ )
135
+ {
136
+ Some ( b)
137
+ } else {
138
+ None
139
+ }
140
+ } ;
141
+
142
+ if let Some ( b) = get_block ( self , then) {
143
+ self . with_context ( UnlabeledIfBlock ( b. span . shrink_to_lo ( ) ) , |v| {
144
+ v. visit_block ( b)
145
+ } ) ;
146
+ } else {
147
+ self . visit_expr ( then) ;
148
+ }
149
+
150
+ if let Some ( else_expr) = else_opt {
151
+ if let Some ( b) = get_block ( self , else_expr) {
152
+ self . with_context ( UnlabeledIfBlock ( b. span . shrink_to_lo ( ) ) , |v| {
153
+ v. visit_block ( b)
154
+ } ) ;
155
+ } else {
156
+ self . visit_expr ( else_expr) ;
157
+ }
158
+ }
159
+ }
86
160
hir:: ExprKind :: Loop ( ref b, _, source, _) => {
87
161
self . with_context ( Loop ( source) , |v| v. visit_block ( b) ) ;
88
162
}
@@ -101,11 +175,14 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
101
175
hir:: ExprKind :: Block ( ref b, Some ( _label) ) => {
102
176
self . with_context ( LabeledBlock , |v| v. visit_block ( b) ) ;
103
177
}
104
- hir:: ExprKind :: Block ( ref b, None ) if matches ! ( self . cx , Fn ) => {
178
+ hir:: ExprKind :: Block ( ref b, None ) if matches ! ( self . cx_stack . last ( ) , Some ( & Fn ) ) => {
105
179
self . with_context ( Normal , |v| v. visit_block ( b) ) ;
106
180
}
107
181
hir:: ExprKind :: Block ( ref b, None )
108
- if matches ! ( self . cx, Normal | Constant | UnlabeledBlock ( _) ) =>
182
+ if matches ! (
183
+ self . cx_stack. last( ) ,
184
+ Some ( & Normal ) | Some ( & Constant ) | Some ( & UnlabeledBlock ( _) )
185
+ ) =>
109
186
{
110
187
self . with_context ( UnlabeledBlock ( b. span . shrink_to_lo ( ) ) , |v| v. visit_block ( b) ) ;
111
188
}
@@ -178,7 +255,12 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
178
255
Some ( label) => sp_lo. with_hi ( label. ident . span . hi ( ) ) ,
179
256
None => sp_lo. shrink_to_lo ( ) ,
180
257
} ;
181
- self . require_break_cx ( "break" , e. span , label_sp) ;
258
+ self . require_break_cx (
259
+ BreakContextKind :: Break ,
260
+ e. span ,
261
+ label_sp,
262
+ self . cx_stack . len ( ) - 1 ,
263
+ ) ;
182
264
}
183
265
hir:: ExprKind :: Continue ( destination) => {
184
266
self . require_label_in_labeled_block ( e. span , & destination, "continue" ) ;
@@ -200,7 +282,12 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
200
282
}
201
283
Err ( _) => { }
202
284
}
203
- self . require_break_cx ( "continue" , e. span , e. span )
285
+ self . require_break_cx (
286
+ BreakContextKind :: Continue ,
287
+ e. span ,
288
+ e. span ,
289
+ self . cx_stack . len ( ) - 1 ,
290
+ )
204
291
}
205
292
_ => intravisit:: walk_expr ( self , e) ,
206
293
}
@@ -212,18 +299,26 @@ impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> {
212
299
where
213
300
F : FnOnce ( & mut CheckLoopVisitor < ' a , ' hir > ) ,
214
301
{
215
- let old_cx = self . cx ;
216
- self . cx = cx;
302
+ self . cx_stack . push ( cx) ;
217
303
f ( self ) ;
218
- self . cx = old_cx ;
304
+ self . cx_stack . pop ( ) ;
219
305
}
220
306
221
- fn require_break_cx ( & self , name : & str , span : Span , break_span : Span ) {
222
- let is_break = name == "break" ;
223
- match self . cx {
307
+ fn require_break_cx (
308
+ & mut self ,
309
+ br_cx_kind : BreakContextKind ,
310
+ span : Span ,
311
+ break_span : Span ,
312
+ cx_pos : usize ,
313
+ ) {
314
+ match self . cx_stack [ cx_pos] {
224
315
LabeledBlock | Loop ( _) => { }
225
316
Closure ( closure_span) => {
226
- self . sess . dcx ( ) . emit_err ( BreakInsideClosure { span, closure_span, name } ) ;
317
+ self . sess . dcx ( ) . emit_err ( BreakInsideClosure {
318
+ span,
319
+ closure_span,
320
+ name : & br_cx_kind. to_string ( ) ,
321
+ } ) ;
227
322
}
228
323
Coroutine { coroutine_span, kind, source } => {
229
324
let kind = match kind {
@@ -239,17 +334,32 @@ impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> {
239
334
self . sess . dcx ( ) . emit_err ( BreakInsideCoroutine {
240
335
span,
241
336
coroutine_span,
242
- name,
337
+ name : & br_cx_kind . to_string ( ) ,
243
338
kind,
244
339
source,
245
340
} ) ;
246
341
}
247
- UnlabeledBlock ( block_span) if is_break && block_span. eq_ctxt ( break_span) => {
248
- let suggestion = Some ( OutsideLoopSuggestion { block_span, break_span } ) ;
249
- self . sess . dcx ( ) . emit_err ( OutsideLoop { span, name, is_break, suggestion } ) ;
342
+ UnlabeledBlock ( block_span)
343
+ if br_cx_kind == BreakContextKind :: Break && block_span. eq_ctxt ( break_span) =>
344
+ {
345
+ let block = self . block_breaks . entry ( block_span) . or_insert_with ( || BlockInfo {
346
+ name : br_cx_kind. to_string ( ) ,
347
+ spans : vec ! [ ] ,
348
+ suggs : vec ! [ ] ,
349
+ } ) ;
350
+ block. spans . push ( span) ;
351
+ block. suggs . push ( break_span) ;
352
+ }
353
+ UnlabeledIfBlock ( _) if br_cx_kind == BreakContextKind :: Break => {
354
+ self . require_break_cx ( br_cx_kind, span, break_span, cx_pos - 1 ) ;
250
355
}
251
- Normal | Constant | Fn | UnlabeledBlock ( _) => {
252
- self . sess . dcx ( ) . emit_err ( OutsideLoop { span, name, is_break, suggestion : None } ) ;
356
+ Normal | Constant | Fn | UnlabeledBlock ( _) | UnlabeledIfBlock ( _) => {
357
+ self . sess . dcx ( ) . emit_err ( OutsideLoop {
358
+ spans : vec ! [ span] ,
359
+ name : & br_cx_kind. to_string ( ) ,
360
+ is_break : br_cx_kind == BreakContextKind :: Break ,
361
+ suggestion : None ,
362
+ } ) ;
253
363
}
254
364
}
255
365
}
@@ -261,12 +371,26 @@ impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> {
261
371
cf_type : & str ,
262
372
) -> bool {
263
373
if !span. is_desugaring ( DesugaringKind :: QuestionMark )
264
- && self . cx == LabeledBlock
374
+ && self . cx_stack . last ( ) == Some ( & LabeledBlock )
265
375
&& label. label . is_none ( )
266
376
{
267
377
self . sess . dcx ( ) . emit_err ( UnlabeledInLabeledBlock { span, cf_type } ) ;
268
378
return true ;
269
379
}
270
380
false
271
381
}
382
+
383
+ fn report_outside_loop_error ( & mut self ) {
384
+ for ( s, block) in & self . block_breaks {
385
+ self . sess . dcx ( ) . emit_err ( OutsideLoop {
386
+ spans : block. spans . clone ( ) ,
387
+ name : & block. name ,
388
+ is_break : true ,
389
+ suggestion : Some ( OutsideLoopSuggestion {
390
+ block_span : * s,
391
+ break_spans : block. suggs . clone ( ) ,
392
+ } ) ,
393
+ } ) ;
394
+ }
395
+ }
272
396
}
0 commit comments