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