1
+ use rustc_data_structures:: fx:: FxHashMap ;
1
2
use Context :: * ;
2
3
3
4
use rustc_hir as hir;
@@ -24,22 +25,40 @@ enum Context {
24
25
Closure ( Span ) ,
25
26
AsyncClosure ( Span ) ,
26
27
UnlabeledBlock ( Span ) ,
28
+ IfUnlabeledBlock ( Span ) ,
27
29
LabeledBlock ,
28
30
Constant ,
29
31
}
30
32
31
- #[ derive( Copy , Clone ) ]
33
+ #[ derive( Clone ) ]
34
+ struct BlockInfo {
35
+ name : String ,
36
+ spans : Vec < Span > ,
37
+ suggs : Vec < Span > ,
38
+ }
39
+
40
+ #[ derive( Clone ) ]
32
41
struct CheckLoopVisitor < ' a , ' tcx > {
33
42
sess : & ' a Session ,
34
43
tcx : TyCtxt < ' tcx > ,
35
- cx : Context ,
44
+ // Used for diagnostic like when in a `if` block with some `break`s,
45
+ // we should not suggest adding `'block` label in `if` block,
46
+ // we can back to outer block and add label there.
47
+ cx_stack : Vec < Context > ,
48
+ blocks : Vec < Span > ,
49
+ block_breaks : FxHashMap < Span , BlockInfo > ,
36
50
}
37
51
38
52
fn check_mod_loops ( tcx : TyCtxt < ' _ > , module_def_id : LocalModDefId ) {
39
- tcx. hir ( ) . visit_item_likes_in_module (
40
- module_def_id,
41
- & mut CheckLoopVisitor { sess : tcx. sess , tcx, cx : Normal } ,
42
- ) ;
53
+ let mut check = CheckLoopVisitor {
54
+ sess : tcx. sess ,
55
+ tcx,
56
+ cx_stack : vec ! [ Normal ] ,
57
+ blocks : Default :: default ( ) ,
58
+ block_breaks : Default :: default ( ) ,
59
+ } ;
60
+ tcx. hir ( ) . visit_item_likes_in_module ( module_def_id, & mut check) ;
61
+ check. report_outside_loop_error ( ) ;
43
62
}
44
63
45
64
pub ( crate ) fn provide ( providers : & mut Providers ) {
@@ -82,6 +101,35 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
82
101
83
102
fn visit_expr ( & mut self , e : & ' hir hir:: Expr < ' hir > ) {
84
103
match e. kind {
104
+ hir:: ExprKind :: If ( cond, then, else_opt) => {
105
+ self . visit_expr ( cond) ;
106
+ if let hir:: ExprKind :: Block ( ref b, None ) = then. kind
107
+ && matches ! (
108
+ self . cx_stack. last( ) ,
109
+ Some ( & Normal ) | Some ( & Constant ) | Some ( & UnlabeledBlock ( _) ) | Some ( & IfUnlabeledBlock ( _) )
110
+ )
111
+ {
112
+ self . with_context ( IfUnlabeledBlock ( b. span . shrink_to_lo ( ) ) , |v| {
113
+ v. visit_block ( b)
114
+ } ) ;
115
+ } else {
116
+ self . visit_expr ( then) ;
117
+ }
118
+ if let Some ( else_expr) = else_opt {
119
+ if let hir:: ExprKind :: Block ( ref b, None ) = else_expr. kind
120
+ && matches ! (
121
+ self . cx_stack. last( ) ,
122
+ Some ( & Normal ) | Some ( & Constant ) | Some ( & UnlabeledBlock ( _) ) | Some ( & IfUnlabeledBlock ( _) )
123
+ )
124
+ {
125
+ self . with_context ( IfUnlabeledBlock ( b. span . shrink_to_lo ( ) ) , |v| {
126
+ v. visit_block ( b)
127
+ } ) ;
128
+ } else {
129
+ self . visit_expr ( else_expr) ;
130
+ }
131
+ }
132
+ }
85
133
hir:: ExprKind :: Loop ( ref b, _, source, _) => {
86
134
self . with_context ( Loop ( source) , |v| v. visit_block ( b) ) ;
87
135
}
@@ -102,11 +150,14 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
102
150
hir:: ExprKind :: Block ( ref b, Some ( _label) ) => {
103
151
self . with_context ( LabeledBlock , |v| v. visit_block ( b) ) ;
104
152
}
105
- hir:: ExprKind :: Block ( ref b, None ) if matches ! ( self . cx , Fn ) => {
153
+ hir:: ExprKind :: Block ( ref b, None ) if matches ! ( self . cx_stack . last ( ) , Some ( & Fn ) ) => {
106
154
self . with_context ( Normal , |v| v. visit_block ( b) ) ;
107
155
}
108
156
hir:: ExprKind :: Block ( ref b, None )
109
- if matches ! ( self . cx, Normal | Constant | UnlabeledBlock ( _) ) =>
157
+ if matches ! (
158
+ self . cx_stack. last( ) ,
159
+ Some ( & Normal ) | Some ( & Constant ) | Some ( & UnlabeledBlock ( _) )
160
+ ) =>
110
161
{
111
162
self . with_context ( UnlabeledBlock ( b. span . shrink_to_lo ( ) ) , |v| v. visit_block ( b) ) ;
112
163
}
@@ -179,7 +230,7 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
179
230
Some ( label) => sp_lo. with_hi ( label. ident . span . hi ( ) ) ,
180
231
None => sp_lo. shrink_to_lo ( ) ,
181
232
} ;
182
- self . require_break_cx ( "break" , e. span , label_sp) ;
233
+ self . require_break_cx ( "break" , e. span , label_sp, self . cx_stack . len ( ) - 1 ) ;
183
234
}
184
235
hir:: ExprKind :: Continue ( destination) => {
185
236
self . require_label_in_labeled_block ( e. span , & destination, "continue" ) ;
@@ -201,7 +252,7 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
201
252
}
202
253
Err ( _) => { }
203
254
}
204
- self . require_break_cx ( "continue" , e. span , e. span )
255
+ self . require_break_cx ( "continue" , e. span , e. span , self . cx_stack . len ( ) - 1 )
205
256
}
206
257
_ => intravisit:: walk_expr ( self , e) ,
207
258
}
@@ -213,15 +264,14 @@ impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> {
213
264
where
214
265
F : FnOnce ( & mut CheckLoopVisitor < ' a , ' hir > ) ,
215
266
{
216
- let old_cx = self . cx ;
217
- self . cx = cx;
267
+ self . cx_stack . push ( cx) ;
218
268
f ( self ) ;
219
- self . cx = old_cx ;
269
+ self . cx_stack . pop ( ) ;
220
270
}
221
271
222
- fn require_break_cx ( & self , name : & str , span : Span , break_span : Span ) {
272
+ fn require_break_cx ( & mut self , name : & str , span : Span , break_span : Span , cx_pos : usize ) {
223
273
let is_break = name == "break" ;
224
- match self . cx {
274
+ match self . cx_stack [ cx_pos ] {
225
275
LabeledBlock | Loop ( _) => { }
226
276
Closure ( closure_span) => {
227
277
self . sess . dcx ( ) . emit_err ( BreakInsideClosure { span, closure_span, name } ) ;
@@ -230,11 +280,27 @@ impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> {
230
280
self . sess . dcx ( ) . emit_err ( BreakInsideAsyncBlock { span, closure_span, name } ) ;
231
281
}
232
282
UnlabeledBlock ( block_span) if is_break && block_span. eq_ctxt ( break_span) => {
233
- let suggestion = Some ( OutsideLoopSuggestion { block_span, break_span } ) ;
234
- self . sess . dcx ( ) . emit_err ( OutsideLoop { span, name, is_break, suggestion } ) ;
283
+ let block = self . block_breaks . entry ( block_span) . or_insert_with ( || {
284
+ self . blocks . push ( block_span) ;
285
+ BlockInfo {
286
+ name : name. to_string ( ) ,
287
+ spans : vec ! [ ] ,
288
+ suggs : vec ! [ ] ,
289
+ }
290
+ } ) ;
291
+ block. spans . push ( span) ;
292
+ block. suggs . push ( break_span) ;
293
+ }
294
+ IfUnlabeledBlock ( _) if is_break => {
295
+ self . require_break_cx ( name, span, break_span, cx_pos - 1 ) ;
235
296
}
236
- Normal | Constant | Fn | UnlabeledBlock ( _) => {
237
- self . sess . dcx ( ) . emit_err ( OutsideLoop { span, name, is_break, suggestion : None } ) ;
297
+ Normal | Constant | Fn | UnlabeledBlock ( _) | IfUnlabeledBlock ( _) => {
298
+ self . sess . dcx ( ) . emit_err ( OutsideLoop {
299
+ spans : vec ! [ span] ,
300
+ name,
301
+ is_break,
302
+ suggestion : None ,
303
+ } ) ;
238
304
}
239
305
}
240
306
}
@@ -246,12 +312,28 @@ impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> {
246
312
cf_type : & str ,
247
313
) -> bool {
248
314
if !span. is_desugaring ( DesugaringKind :: QuestionMark )
249
- && self . cx == LabeledBlock
315
+ && self . cx_stack . last ( ) == Some ( & LabeledBlock )
250
316
&& label. label . is_none ( )
251
317
{
252
318
self . sess . dcx ( ) . emit_err ( UnlabeledInLabeledBlock { span, cf_type } ) ;
253
319
return true ;
254
320
}
255
321
false
256
322
}
323
+
324
+ fn report_outside_loop_error ( & mut self ) {
325
+ self . blocks . iter ( ) . for_each ( |s| {
326
+ if let Some ( block) = self . block_breaks . get ( s) {
327
+ self . sess . dcx ( ) . emit_err ( OutsideLoop {
328
+ spans : block. spans . clone ( ) ,
329
+ name : & block. name ,
330
+ is_break : true ,
331
+ suggestion : Some ( OutsideLoopSuggestion {
332
+ block_span : * s,
333
+ break_spans : block. suggs . clone ( ) ,
334
+ } ) ,
335
+ } ) ;
336
+ }
337
+ } ) ;
338
+ }
257
339
}
0 commit comments