1+ use std:: ops:: ControlFlow ;
2+
13use clippy_utils:: diagnostics:: span_lint_and_then;
24use clippy_utils:: { fn_def_id, is_from_proc_macro, is_lint_allowed} ;
35use hir:: intravisit:: { walk_expr, Visitor } ;
46use hir:: { ClosureKind , Expr , ExprKind , FnRetTy , FnSig , ItemKind , Node , Ty , TyKind } ;
5- use rustc_ast:: Label ;
67use rustc_errors:: Applicability ;
78use rustc_hir as hir;
89use rustc_lint:: { LateContext , LintContext } ;
@@ -15,7 +16,7 @@ pub(super) fn check<'tcx>(
1516 cx : & LateContext < ' tcx > ,
1617 expr : & Expr < ' tcx > ,
1718 loop_block : & ' tcx hir:: Block < ' _ > ,
18- label : Option < Label > ,
19+ hir_id : hir :: HirId ,
1920) {
2021 if is_lint_allowed ( cx, INFINITE_LOOP , expr. hir_id ) {
2122 return ;
@@ -31,21 +32,29 @@ pub(super) fn check<'tcx>(
3132
3233 let mut loop_visitor = LoopVisitor {
3334 cx,
34- label ,
35- is_finite : false ,
36- loop_depth : 0 ,
35+ hir_id ,
36+ block : loop_block ,
37+ nested_loop_count : 0 ,
3738 } ;
38- loop_visitor. visit_block ( loop_block) ;
39-
40- let is_finite_loop = loop_visitor. is_finite ;
41-
42- if !is_finite_loop {
39+ if !loop_visitor. is_finite ( ) {
4340 span_lint_and_then ( cx, INFINITE_LOOP , expr. span , "infinite loop detected" , |diag| {
4441 if let Some ( span) = parent_fn_ret. sugg_span ( ) {
42+ // Check if the type span is after a `->` or not.
43+ let suggestion = if cx
44+ . tcx
45+ . sess
46+ . source_map ( )
47+ . span_extend_to_prev_str ( span, "->" , false , true )
48+ . is_none ( )
49+ {
50+ " -> !"
51+ } else {
52+ "!"
53+ } ;
4554 diag. span_suggestion (
4655 span,
4756 "if this is intentional, consider specifying `!` as function return" ,
48- " -> !" ,
57+ suggestion ,
4958 Applicability :: MaybeIncorrect ,
5059 ) ;
5160 } else {
@@ -92,38 +101,50 @@ fn get_parent_fn_ret_ty<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option
92101
93102struct LoopVisitor < ' hir , ' tcx > {
94103 cx : & ' hir LateContext < ' tcx > ,
95- label : Option < Label > ,
96- loop_depth : usize ,
97- is_finite : bool ,
104+ // `HirId` for the entry (outer most) loop.
105+ hir_id : hir:: HirId ,
106+ block : & ' hir hir:: Block < ' tcx > ,
107+ nested_loop_count : usize ,
108+ }
109+
110+ impl LoopVisitor < ' _ , ' _ > {
111+ fn is_finite ( & mut self ) -> bool {
112+ self . visit_block ( self . block ) == ControlFlow :: Break ( ( ) )
113+ }
98114}
99115
100116impl < ' hir > Visitor < ' hir > for LoopVisitor < ' hir , ' _ > {
101- fn visit_expr ( & mut self , ex : & ' hir Expr < ' _ > ) {
117+ type Result = ControlFlow < ( ) > ;
118+
119+ fn visit_expr ( & mut self , ex : & ' hir Expr < ' _ > ) -> Self :: Result {
102120 match & ex. kind {
103- ExprKind :: Break ( hir:: Destination { label, .. } , ..) => {
104- // Assuming breaks the loop when `loop_depth` is 0,
105- // as it could only means this `break` breaks current loop or any of its upper loop.
106- // Or, the depth is not zero but the label is matched.
107- if self . loop_depth == 0 || ( label. is_some ( ) && * label == self . label ) {
108- self . is_finite = true ;
121+ ExprKind :: Break ( hir:: Destination { target_id, .. } , ..) => {
122+ // When there are no nested loops and we've encountered a `break`, meaning it's either breaking the
123+ // current loop or any of the parent loop, assuming that this loop is not infinite.
124+ let assume_is_finite = self . nested_loop_count == 0 ;
125+
126+ if assume_is_finite || matches ! ( target_id, Ok ( target_loop_hid) if * target_loop_hid == self . hir_id) {
127+ ControlFlow :: Break ( ( ) )
128+ } else {
129+ ControlFlow :: Continue ( ( ) )
109130 }
110131 } ,
111- ExprKind :: Ret ( ..) => self . is_finite = true ,
132+ ExprKind :: Ret ( ..) => ControlFlow :: Break ( ( ) ) ,
112133 ExprKind :: Loop ( ..) => {
113- self . loop_depth += 1 ;
114- walk_expr ( self , ex) ;
115- self . loop_depth = self . loop_depth . saturating_sub ( 1 ) ;
134+ self . nested_loop_count += 1 ;
135+ let cf = walk_expr ( self , ex) ;
136+ self . nested_loop_count = self . nested_loop_count . saturating_sub ( 1 ) ;
137+ cf
116138 } ,
117139 _ => {
118140 // Calls to a function that never return
119141 if let Some ( did) = fn_def_id ( self . cx , ex) {
120142 let fn_ret_ty = self . cx . tcx . fn_sig ( did) . skip_binder ( ) . output ( ) . skip_binder ( ) ;
121143 if fn_ret_ty. is_never ( ) {
122- self . is_finite = true ;
123- return ;
144+ return ControlFlow :: Break ( ( ) ) ;
124145 }
125146 }
126- walk_expr ( self , ex) ;
147+ walk_expr ( self , ex)
127148 } ,
128149 }
129150 }
@@ -141,35 +162,39 @@ impl<'hir> RetTy<'hir> {
141162 /// given `ty` is not an `OpaqueDef`.
142163 fn inner_ < ' tcx > ( cx : & LateContext < ' tcx > , ty : & Ty < ' tcx > ) -> Option < Ty < ' tcx > > {
143164 /// Visitor to find the type binding.
144- struct BindingVisitor < ' tcx > {
145- res : Option < Ty < ' tcx > > ,
146- }
147- impl < ' tcx > Visitor < ' tcx > for BindingVisitor < ' tcx > {
148- fn visit_assoc_type_binding ( & mut self , type_binding : & ' tcx hir:: TypeBinding < ' tcx > ) {
149- if self . res . is_some ( ) {
150- return ;
151- }
152- if let hir:: TypeBindingKind :: Equality {
165+ struct BindingVisitor ;
166+
167+ impl < ' tcx > Visitor < ' tcx > for BindingVisitor {
168+ type Result = ControlFlow < Ty < ' tcx > > ;
169+
170+ fn visit_assoc_item_constraint (
171+ & mut self ,
172+ constraint : & ' tcx hir:: AssocItemConstraint < ' tcx > ,
173+ ) -> Self :: Result {
174+ if let hir:: AssocItemConstraintKind :: Equality {
153175 term : hir:: Term :: Ty ( ty) ,
154- } = type_binding . kind
176+ } = constraint . kind
155177 {
156- self . res = Some ( * ty) ;
178+ ControlFlow :: Break ( * ty)
179+ } else {
180+ ControlFlow :: Continue ( ( ) )
157181 }
158182 }
159183 }
160184
161- let TyKind :: OpaqueDef ( item_id, ..) = ty. kind else {
162- return None ;
163- } ;
164- let opaque_ty_item = cx. tcx . hir ( ) . item ( item_id) ;
165-
166- // Sinces the `item_id` is from a `TyKind::OpaqueDef`,
167- // therefore the `Item` related to it should always be `OpaqueTy`.
168- assert ! ( matches!( opaque_ty_item. kind, ItemKind :: OpaqueTy ( _) ) ) ;
169-
170- let mut vis = BindingVisitor { res : None } ;
171- vis. visit_item ( opaque_ty_item) ;
172- vis. res
185+ if let TyKind :: OpaqueDef ( item_id, ..) = ty. kind
186+ && let opaque_ty_item = cx. tcx . hir ( ) . item ( item_id)
187+ && let ItemKind :: OpaqueTy ( _) = opaque_ty_item. kind
188+ {
189+ let mut vis = BindingVisitor ;
190+ // Use `vis.break_value()` once it's stablized.
191+ match vis. visit_item ( opaque_ty_item) {
192+ ControlFlow :: Break ( res) => Some ( res) ,
193+ ControlFlow :: Continue ( ( ) ) => None ,
194+ }
195+ } else {
196+ None
197+ }
173198 }
174199
175200 match fn_ret_ty {
@@ -186,7 +211,12 @@ impl<'hir> RetTy<'hir> {
186211 }
187212 }
188213 fn is_never ( & self ) -> bool {
189- let Self :: Return ( ty) = self else { return false } ;
190- matches ! ( ty. kind, TyKind :: Never )
214+ matches ! (
215+ self ,
216+ RetTy :: Return ( Ty {
217+ kind: TyKind :: Never ,
218+ ..
219+ } )
220+ )
191221 }
192222}
0 commit comments