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,41 @@ 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
+ if let hir:: ExprKind :: Block ( ref b, None ) = then. kind
124
+ && matches ! (
125
+ self . cx_stack. last( ) ,
126
+ Some ( & Normal )
127
+ | Some ( & Constant )
128
+ | Some ( & UnlabeledBlock ( _) )
129
+ | Some ( & UnlabeledIfBlock ( _) )
130
+ )
131
+ {
132
+ self . with_context ( UnlabeledIfBlock ( b. span . shrink_to_lo ( ) ) , |v| {
133
+ v. visit_block ( b)
134
+ } ) ;
135
+ } else {
136
+ self . visit_expr ( then) ;
137
+ }
138
+ if let Some ( else_expr) = else_opt {
139
+ if let hir:: ExprKind :: Block ( ref b, None ) = else_expr. kind
140
+ && matches ! (
141
+ self . cx_stack. last( ) ,
142
+ Some ( & Normal )
143
+ | Some ( & Constant )
144
+ | Some ( & UnlabeledBlock ( _) )
145
+ | Some ( & UnlabeledIfBlock ( _) )
146
+ )
147
+ {
148
+ self . with_context ( UnlabeledIfBlock ( b. span . shrink_to_lo ( ) ) , |v| {
149
+ v. visit_block ( b)
150
+ } ) ;
151
+ } else {
152
+ self . visit_expr ( else_expr) ;
153
+ }
154
+ }
155
+ }
86
156
hir:: ExprKind :: Loop ( ref b, _, source, _) => {
87
157
self . with_context ( Loop ( source) , |v| v. visit_block ( b) ) ;
88
158
}
@@ -101,11 +171,14 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
101
171
hir:: ExprKind :: Block ( ref b, Some ( _label) ) => {
102
172
self . with_context ( LabeledBlock , |v| v. visit_block ( b) ) ;
103
173
}
104
- hir:: ExprKind :: Block ( ref b, None ) if matches ! ( self . cx , Fn ) => {
174
+ hir:: ExprKind :: Block ( ref b, None ) if matches ! ( self . cx_stack . last ( ) , Some ( & Fn ) ) => {
105
175
self . with_context ( Normal , |v| v. visit_block ( b) ) ;
106
176
}
107
177
hir:: ExprKind :: Block ( ref b, None )
108
- if matches ! ( self . cx, Normal | Constant | UnlabeledBlock ( _) ) =>
178
+ if matches ! (
179
+ self . cx_stack. last( ) ,
180
+ Some ( & Normal ) | Some ( & Constant ) | Some ( & UnlabeledBlock ( _) )
181
+ ) =>
109
182
{
110
183
self . with_context ( UnlabeledBlock ( b. span . shrink_to_lo ( ) ) , |v| v. visit_block ( b) ) ;
111
184
}
@@ -178,7 +251,12 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
178
251
Some ( label) => sp_lo. with_hi ( label. ident . span . hi ( ) ) ,
179
252
None => sp_lo. shrink_to_lo ( ) ,
180
253
} ;
181
- self . require_break_cx ( "break" , e. span , label_sp) ;
254
+ self . require_break_cx (
255
+ BreakContextKind :: Break ,
256
+ e. span ,
257
+ label_sp,
258
+ self . cx_stack . len ( ) - 1 ,
259
+ ) ;
182
260
}
183
261
hir:: ExprKind :: Continue ( destination) => {
184
262
self . require_label_in_labeled_block ( e. span , & destination, "continue" ) ;
@@ -200,7 +278,12 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
200
278
}
201
279
Err ( _) => { }
202
280
}
203
- self . require_break_cx ( "continue" , e. span , e. span )
281
+ self . require_break_cx (
282
+ BreakContextKind :: Continue ,
283
+ e. span ,
284
+ e. span ,
285
+ self . cx_stack . len ( ) - 1 ,
286
+ )
204
287
}
205
288
_ => intravisit:: walk_expr ( self , e) ,
206
289
}
@@ -212,18 +295,26 @@ impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> {
212
295
where
213
296
F : FnOnce ( & mut CheckLoopVisitor < ' a , ' hir > ) ,
214
297
{
215
- let old_cx = self . cx ;
216
- self . cx = cx;
298
+ self . cx_stack . push ( cx) ;
217
299
f ( self ) ;
218
- self . cx = old_cx ;
300
+ self . cx_stack . pop ( ) ;
219
301
}
220
302
221
- fn require_break_cx ( & self , name : & str , span : Span , break_span : Span ) {
222
- let is_break = name == "break" ;
223
- match self . cx {
303
+ fn require_break_cx (
304
+ & mut self ,
305
+ br_cx_kind : BreakContextKind ,
306
+ span : Span ,
307
+ break_span : Span ,
308
+ cx_pos : usize ,
309
+ ) {
310
+ match self . cx_stack [ cx_pos] {
224
311
LabeledBlock | Loop ( _) => { }
225
312
Closure ( closure_span) => {
226
- self . sess . dcx ( ) . emit_err ( BreakInsideClosure { span, closure_span, name } ) ;
313
+ self . sess . dcx ( ) . emit_err ( BreakInsideClosure {
314
+ span,
315
+ closure_span,
316
+ name : & br_cx_kind. to_string ( ) ,
317
+ } ) ;
227
318
}
228
319
Coroutine { coroutine_span, kind, source } => {
229
320
let kind = match kind {
@@ -239,17 +330,32 @@ impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> {
239
330
self . sess . dcx ( ) . emit_err ( BreakInsideCoroutine {
240
331
span,
241
332
coroutine_span,
242
- name,
333
+ name : & br_cx_kind . to_string ( ) ,
243
334
kind,
244
335
source,
245
336
} ) ;
246
337
}
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 } ) ;
338
+ UnlabeledBlock ( block_span)
339
+ if br_cx_kind == BreakContextKind :: Break && block_span. eq_ctxt ( break_span) =>
340
+ {
341
+ let block = self . block_breaks . entry ( block_span) . or_insert_with ( || BlockInfo {
342
+ name : br_cx_kind. to_string ( ) ,
343
+ spans : vec ! [ ] ,
344
+ suggs : vec ! [ ] ,
345
+ } ) ;
346
+ block. spans . push ( span) ;
347
+ block. suggs . push ( break_span) ;
348
+ }
349
+ UnlabeledIfBlock ( _) if br_cx_kind == BreakContextKind :: Break => {
350
+ self . require_break_cx ( br_cx_kind, span, break_span, cx_pos - 1 ) ;
250
351
}
251
- Normal | Constant | Fn | UnlabeledBlock ( _) => {
252
- self . sess . dcx ( ) . emit_err ( OutsideLoop { span, name, is_break, suggestion : None } ) ;
352
+ Normal | Constant | Fn | UnlabeledBlock ( _) | UnlabeledIfBlock ( _) => {
353
+ self . sess . dcx ( ) . emit_err ( OutsideLoop {
354
+ spans : vec ! [ span] ,
355
+ name : & br_cx_kind. to_string ( ) ,
356
+ is_break : br_cx_kind == BreakContextKind :: Break ,
357
+ suggestion : None ,
358
+ } ) ;
253
359
}
254
360
}
255
361
}
@@ -261,12 +367,26 @@ impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> {
261
367
cf_type : & str ,
262
368
) -> bool {
263
369
if !span. is_desugaring ( DesugaringKind :: QuestionMark )
264
- && self . cx == LabeledBlock
370
+ && self . cx_stack . last ( ) == Some ( & LabeledBlock )
265
371
&& label. label . is_none ( )
266
372
{
267
373
self . sess . dcx ( ) . emit_err ( UnlabeledInLabeledBlock { span, cf_type } ) ;
268
374
return true ;
269
375
}
270
376
false
271
377
}
378
+
379
+ fn report_outside_loop_error ( & mut self ) {
380
+ for ( s, block) in & self . block_breaks {
381
+ self . sess . dcx ( ) . emit_err ( OutsideLoop {
382
+ spans : block. spans . clone ( ) ,
383
+ name : & block. name ,
384
+ is_break : true ,
385
+ suggestion : Some ( OutsideLoopSuggestion {
386
+ block_span : * s,
387
+ break_spans : block. suggs . clone ( ) ,
388
+ } ) ,
389
+ } ) ;
390
+ }
391
+ }
272
392
}
0 commit comments