1
+ use std:: collections:: BTreeMap ;
1
2
use Context :: * ;
2
3
3
4
use rustc_hir as hir;
@@ -24,22 +25,38 @@ 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
+ block_breaks : BTreeMap < Span , BlockInfo > ,
36
49
}
37
50
38
51
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
- ) ;
52
+ let mut check = CheckLoopVisitor {
53
+ sess : tcx. sess ,
54
+ tcx,
55
+ cx_stack : vec ! [ Normal ] ,
56
+ block_breaks : Default :: default ( ) ,
57
+ } ;
58
+ tcx. hir ( ) . visit_item_likes_in_module ( module_def_id, & mut check) ;
59
+ check. report_outside_loop_error ( ) ;
43
60
}
44
61
45
62
pub ( crate ) fn provide ( providers : & mut Providers ) {
@@ -82,6 +99,41 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
82
99
83
100
fn visit_expr ( & mut self , e : & ' hir hir:: Expr < ' hir > ) {
84
101
match e. kind {
102
+ hir:: ExprKind :: If ( cond, then, else_opt) => {
103
+ self . visit_expr ( cond) ;
104
+ if let hir:: ExprKind :: Block ( ref b, None ) = then. kind
105
+ && matches ! (
106
+ self . cx_stack. last( ) ,
107
+ Some ( & Normal )
108
+ | Some ( & Constant )
109
+ | Some ( & UnlabeledBlock ( _) )
110
+ | Some ( & IfUnlabeledBlock ( _) )
111
+ )
112
+ {
113
+ self . with_context ( IfUnlabeledBlock ( b. span . shrink_to_lo ( ) ) , |v| {
114
+ v. visit_block ( b)
115
+ } ) ;
116
+ } else {
117
+ self . visit_expr ( then) ;
118
+ }
119
+ if let Some ( else_expr) = else_opt {
120
+ if let hir:: ExprKind :: Block ( ref b, None ) = else_expr. kind
121
+ && matches ! (
122
+ self . cx_stack. last( ) ,
123
+ Some ( & Normal )
124
+ | Some ( & Constant )
125
+ | Some ( & UnlabeledBlock ( _) )
126
+ | Some ( & IfUnlabeledBlock ( _) )
127
+ )
128
+ {
129
+ self . with_context ( IfUnlabeledBlock ( b. span . shrink_to_lo ( ) ) , |v| {
130
+ v. visit_block ( b)
131
+ } ) ;
132
+ } else {
133
+ self . visit_expr ( else_expr) ;
134
+ }
135
+ }
136
+ }
85
137
hir:: ExprKind :: Loop ( ref b, _, source, _) => {
86
138
self . with_context ( Loop ( source) , |v| v. visit_block ( b) ) ;
87
139
}
@@ -102,11 +154,14 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
102
154
hir:: ExprKind :: Block ( ref b, Some ( _label) ) => {
103
155
self . with_context ( LabeledBlock , |v| v. visit_block ( b) ) ;
104
156
}
105
- hir:: ExprKind :: Block ( ref b, None ) if matches ! ( self . cx , Fn ) => {
157
+ hir:: ExprKind :: Block ( ref b, None ) if matches ! ( self . cx_stack . last ( ) , Some ( & Fn ) ) => {
106
158
self . with_context ( Normal , |v| v. visit_block ( b) ) ;
107
159
}
108
160
hir:: ExprKind :: Block ( ref b, None )
109
- if matches ! ( self . cx, Normal | Constant | UnlabeledBlock ( _) ) =>
161
+ if matches ! (
162
+ self . cx_stack. last( ) ,
163
+ Some ( & Normal ) | Some ( & Constant ) | Some ( & UnlabeledBlock ( _) )
164
+ ) =>
110
165
{
111
166
self . with_context ( UnlabeledBlock ( b. span . shrink_to_lo ( ) ) , |v| v. visit_block ( b) ) ;
112
167
}
@@ -179,7 +234,7 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
179
234
Some ( label) => sp_lo. with_hi ( label. ident . span . hi ( ) ) ,
180
235
None => sp_lo. shrink_to_lo ( ) ,
181
236
} ;
182
- self . require_break_cx ( "break" , e. span , label_sp) ;
237
+ self . require_break_cx ( "break" , e. span , label_sp, self . cx_stack . len ( ) - 1 ) ;
183
238
}
184
239
hir:: ExprKind :: Continue ( destination) => {
185
240
self . require_label_in_labeled_block ( e. span , & destination, "continue" ) ;
@@ -201,7 +256,7 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
201
256
}
202
257
Err ( _) => { }
203
258
}
204
- self . require_break_cx ( "continue" , e. span , e. span )
259
+ self . require_break_cx ( "continue" , e. span , e. span , self . cx_stack . len ( ) - 1 )
205
260
}
206
261
_ => intravisit:: walk_expr ( self , e) ,
207
262
}
@@ -213,15 +268,14 @@ impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> {
213
268
where
214
269
F : FnOnce ( & mut CheckLoopVisitor < ' a , ' hir > ) ,
215
270
{
216
- let old_cx = self . cx ;
217
- self . cx = cx;
271
+ self . cx_stack . push ( cx) ;
218
272
f ( self ) ;
219
- self . cx = old_cx ;
273
+ self . cx_stack . pop ( ) ;
220
274
}
221
275
222
- fn require_break_cx ( & self , name : & str , span : Span , break_span : Span ) {
276
+ fn require_break_cx ( & mut self , name : & str , span : Span , break_span : Span , cx_pos : usize ) {
223
277
let is_break = name == "break" ;
224
- match self . cx {
278
+ match self . cx_stack [ cx_pos ] {
225
279
LabeledBlock | Loop ( _) => { }
226
280
Closure ( closure_span) => {
227
281
self . sess . dcx ( ) . emit_err ( BreakInsideClosure { span, closure_span, name } ) ;
@@ -230,11 +284,24 @@ impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> {
230
284
self . sess . dcx ( ) . emit_err ( BreakInsideAsyncBlock { span, closure_span, name } ) ;
231
285
}
232
286
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 } ) ;
287
+ let block = self . block_breaks . entry ( block_span) . or_insert_with ( || BlockInfo {
288
+ name : name. to_string ( ) ,
289
+ spans : vec ! [ ] ,
290
+ suggs : vec ! [ ] ,
291
+ } ) ;
292
+ block. spans . push ( span) ;
293
+ block. suggs . push ( break_span) ;
294
+ }
295
+ IfUnlabeledBlock ( _) if is_break => {
296
+ self . require_break_cx ( name, span, break_span, cx_pos - 1 ) ;
235
297
}
236
- Normal | Constant | Fn | UnlabeledBlock ( _) => {
237
- self . sess . dcx ( ) . emit_err ( OutsideLoop { span, name, is_break, suggestion : None } ) ;
298
+ Normal | Constant | Fn | UnlabeledBlock ( _) | IfUnlabeledBlock ( _) => {
299
+ self . sess . dcx ( ) . emit_err ( OutsideLoop {
300
+ spans : vec ! [ span] ,
301
+ name,
302
+ is_break,
303
+ suggestion : None ,
304
+ } ) ;
238
305
}
239
306
}
240
307
}
@@ -246,12 +313,26 @@ impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> {
246
313
cf_type : & str ,
247
314
) -> bool {
248
315
if !span. is_desugaring ( DesugaringKind :: QuestionMark )
249
- && self . cx == LabeledBlock
316
+ && self . cx_stack . last ( ) == Some ( & LabeledBlock )
250
317
&& label. label . is_none ( )
251
318
{
252
319
self . sess . dcx ( ) . emit_err ( UnlabeledInLabeledBlock { span, cf_type } ) ;
253
320
return true ;
254
321
}
255
322
false
256
323
}
324
+
325
+ fn report_outside_loop_error ( & mut self ) {
326
+ self . block_breaks . iter ( ) . for_each ( |( s, block) | {
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
+ }
257
338
}
0 commit comments