1+ use rustc_data_structures:: fx:: FxHashMap ;
12use Context :: * ;
23
34use rustc_hir as hir;
@@ -24,22 +25,40 @@ enum Context {
2425 Closure ( Span ) ,
2526 AsyncClosure ( Span ) ,
2627 UnlabeledBlock ( Span ) ,
28+ IfUnlabeledBlock ( Span ) ,
2729 LabeledBlock ,
2830 Constant ,
2931}
3032
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 ) ]
3241struct CheckLoopVisitor < ' a , ' tcx > {
3342 sess : & ' a Session ,
3443 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 > ,
3650}
3751
3852fn 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 ( ) ;
4362}
4463
4564pub ( crate ) fn provide ( providers : & mut Providers ) {
@@ -82,6 +101,35 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
82101
83102 fn visit_expr ( & mut self , e : & ' hir hir:: Expr < ' hir > ) {
84103 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( ) . unwrap( ) ,
109+ Normal | Constant | UnlabeledBlock ( _) | 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( ) . unwrap( ) ,
122+ Normal | Constant | UnlabeledBlock ( _) | 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+ }
85133 hir:: ExprKind :: Loop ( ref b, _, source, _) => {
86134 self . with_context ( Loop ( source) , |v| v. visit_block ( b) ) ;
87135 }
@@ -102,11 +150,14 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
102150 hir:: ExprKind :: Block ( ref b, Some ( _label) ) => {
103151 self . with_context ( LabeledBlock , |v| v. visit_block ( b) ) ;
104152 }
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 ( ) . unwrap ( ) , Fn ) => {
106154 self . with_context ( Normal , |v| v. visit_block ( b) ) ;
107155 }
108156 hir:: ExprKind :: Block ( ref b, None )
109- if matches ! ( self . cx, Normal | Constant | UnlabeledBlock ( _) ) =>
157+ if matches ! (
158+ self . cx_stack. last( ) . unwrap( ) ,
159+ Normal | Constant | UnlabeledBlock ( _)
160+ ) =>
110161 {
111162 self . with_context ( UnlabeledBlock ( b. span . shrink_to_lo ( ) ) , |v| v. visit_block ( b) ) ;
112163 }
@@ -179,7 +230,7 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
179230 Some ( label) => sp_lo. with_hi ( label. ident . span . hi ( ) ) ,
180231 None => sp_lo. shrink_to_lo ( ) ,
181232 } ;
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 ) ;
183234 }
184235 hir:: ExprKind :: Continue ( destination) => {
185236 self . require_label_in_labeled_block ( e. span , & destination, "continue" ) ;
@@ -201,7 +252,7 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
201252 }
202253 Err ( _) => { }
203254 }
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 )
205256 }
206257 _ => intravisit:: walk_expr ( self , e) ,
207258 }
@@ -213,15 +264,14 @@ impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> {
213264 where
214265 F : FnOnce ( & mut CheckLoopVisitor < ' a , ' hir > ) ,
215266 {
216- let old_cx = self . cx ;
217- self . cx = cx;
267+ self . cx_stack . push ( cx) ;
218268 f ( self ) ;
219- self . cx = old_cx ;
269+ self . cx_stack . pop ( ) ;
220270 }
221271
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 ) {
223273 let is_break = name == "break" ;
224- match self . cx {
274+ match self . cx_stack [ cx_pos ] {
225275 LabeledBlock | Loop ( _) => { }
226276 Closure ( closure_span) => {
227277 self . sess . dcx ( ) . emit_err ( BreakInsideClosure { span, closure_span, name } ) ;
@@ -230,11 +280,31 @@ impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> {
230280 self . sess . dcx ( ) . emit_err ( BreakInsideAsyncBlock { span, closure_span, name } ) ;
231281 }
232282 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+ if let Some ( block) = self . block_breaks . get_mut ( & block_span) {
284+ block. spans . push ( span) ;
285+ block. suggs . push ( break_span) ;
286+ } else {
287+ self . blocks . push ( block_span) ;
288+ self . block_breaks . insert (
289+ block_span,
290+ BlockInfo {
291+ name : name. to_string ( ) ,
292+ spans : vec ! [ span] ,
293+ suggs : vec ! [ break_span] ,
294+ } ,
295+ ) ;
296+ }
297+ }
298+ IfUnlabeledBlock ( _) if is_break => {
299+ self . require_break_cx ( name, span, break_span, cx_pos - 1 ) ;
235300 }
236- Normal | Constant | Fn | UnlabeledBlock ( _) => {
237- self . sess . dcx ( ) . emit_err ( OutsideLoop { span, name, is_break, suggestion : None } ) ;
301+ Normal | Constant | Fn | UnlabeledBlock ( _) | IfUnlabeledBlock ( _) => {
302+ self . sess . dcx ( ) . emit_err ( OutsideLoop {
303+ spans : vec ! [ span] ,
304+ name,
305+ is_break,
306+ suggestion : None ,
307+ } ) ;
238308 }
239309 }
240310 }
@@ -246,12 +316,28 @@ impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> {
246316 cf_type : & str ,
247317 ) -> bool {
248318 if !span. is_desugaring ( DesugaringKind :: QuestionMark )
249- && self . cx == LabeledBlock
319+ && * self . cx_stack . last ( ) . unwrap ( ) == LabeledBlock
250320 && label. label . is_none ( )
251321 {
252322 self . sess . dcx ( ) . emit_err ( UnlabeledInLabeledBlock { span, cf_type } ) ;
253323 return true ;
254324 }
255325 false
256326 }
327+
328+ fn report_outside_loop_error ( & mut self ) {
329+ self . blocks . iter ( ) . for_each ( |s| {
330+ if let Some ( block) = self . block_breaks . get ( s) {
331+ self . sess . dcx ( ) . emit_err ( OutsideLoop {
332+ spans : block. spans . clone ( ) ,
333+ name : & block. name ,
334+ is_break : true ,
335+ suggestion : Some ( OutsideLoopSuggestion {
336+ block_span : * s,
337+ break_spans : block. suggs . clone ( ) ,
338+ } ) ,
339+ } ) ;
340+ }
341+ } ) ;
342+ }
257343}
0 commit comments