1
+ use std:: ops:: ControlFlow ;
2
+
1
3
use clippy_utils:: diagnostics:: span_lint_and_then;
2
4
use clippy_utils:: { fn_def_id, is_from_proc_macro, is_lint_allowed} ;
3
5
use hir:: intravisit:: { walk_expr, Visitor } ;
4
6
use hir:: { ClosureKind , Expr , ExprKind , FnRetTy , FnSig , ItemKind , Node , Ty , TyKind } ;
5
- use rustc_ast:: Label ;
6
7
use rustc_errors:: Applicability ;
7
8
use rustc_hir as hir;
8
9
use rustc_lint:: { LateContext , LintContext } ;
@@ -15,7 +16,7 @@ pub(super) fn check<'tcx>(
15
16
cx : & LateContext < ' tcx > ,
16
17
expr : & Expr < ' tcx > ,
17
18
loop_block : & ' tcx hir:: Block < ' _ > ,
18
- label : Option < Label > ,
19
+ hir_id : hir :: HirId ,
19
20
) {
20
21
if is_lint_allowed ( cx, INFINITE_LOOP , expr. hir_id ) {
21
22
return ;
@@ -31,21 +32,29 @@ pub(super) fn check<'tcx>(
31
32
32
33
let mut loop_visitor = LoopVisitor {
33
34
cx,
34
- label ,
35
- is_finite : false ,
36
- loop_depth : 0 ,
35
+ hir_id ,
36
+ block : loop_block ,
37
+ nested_loop_count : 0 ,
37
38
} ;
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 ( ) {
43
40
span_lint_and_then ( cx, INFINITE_LOOP , expr. span , "infinite loop detected" , |diag| {
44
41
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
+ } ;
45
54
diag. span_suggestion (
46
55
span,
47
56
"if this is intentional, consider specifying `!` as function return" ,
48
- " -> !" ,
57
+ suggestion ,
49
58
Applicability :: MaybeIncorrect ,
50
59
) ;
51
60
} else {
@@ -92,38 +101,50 @@ fn get_parent_fn_ret_ty<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option
92
101
93
102
struct LoopVisitor < ' hir , ' tcx > {
94
103
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
+ }
98
114
}
99
115
100
116
impl < ' 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 {
102
120
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 ( ( ) )
109
130
}
110
131
} ,
111
- ExprKind :: Ret ( ..) => self . is_finite = true ,
132
+ ExprKind :: Ret ( ..) => ControlFlow :: Break ( ( ) ) ,
112
133
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
116
138
} ,
117
139
_ => {
118
140
// Calls to a function that never return
119
141
if let Some ( did) = fn_def_id ( self . cx , ex) {
120
142
let fn_ret_ty = self . cx . tcx . fn_sig ( did) . skip_binder ( ) . output ( ) . skip_binder ( ) ;
121
143
if fn_ret_ty. is_never ( ) {
122
- self . is_finite = true ;
123
- return ;
144
+ return ControlFlow :: Break ( ( ) ) ;
124
145
}
125
146
}
126
- walk_expr ( self , ex) ;
147
+ walk_expr ( self , ex)
127
148
} ,
128
149
}
129
150
}
@@ -141,35 +162,39 @@ impl<'hir> RetTy<'hir> {
141
162
/// given `ty` is not an `OpaqueDef`.
142
163
fn inner_ < ' tcx > ( cx : & LateContext < ' tcx > , ty : & Ty < ' tcx > ) -> Option < Ty < ' tcx > > {
143
164
/// 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 {
153
175
term : hir:: Term :: Ty ( ty) ,
154
- } = type_binding . kind
176
+ } = constraint . kind
155
177
{
156
- self . res = Some ( * ty) ;
178
+ ControlFlow :: Break ( * ty)
179
+ } else {
180
+ ControlFlow :: Continue ( ( ) )
157
181
}
158
182
}
159
183
}
160
184
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
+ }
173
198
}
174
199
175
200
match fn_ret_ty {
@@ -186,7 +211,12 @@ impl<'hir> RetTy<'hir> {
186
211
}
187
212
}
188
213
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
+ )
191
221
}
192
222
}
0 commit comments