11use clippy_utils:: diagnostics:: span_lint_and_then;
2- use clippy_utils:: is_lint_allowed;
2+ use clippy_utils:: { fn_def_id , is_lint_allowed} ;
33use hir:: intravisit:: { walk_expr, Visitor } ;
4- use hir:: { Block , Destination , Expr , ExprKind , FnRetTy , Ty , TyKind } ;
4+ use hir:: { Expr , ExprKind , FnRetTy , FnSig , Node } ;
55use rustc_ast:: Label ;
66use rustc_errors:: Applicability ;
77use rustc_hir as hir;
88use rustc_lint:: LateContext ;
99
1010use super :: INFINITE_LOOPS ;
1111
12- pub ( super ) fn check (
13- cx : & LateContext < ' _ > ,
12+ pub ( super ) fn check < ' tcx > (
13+ cx : & LateContext < ' tcx > ,
1414 expr : & Expr < ' _ > ,
15- loop_block : & Block < ' _ > ,
15+ loop_block : & ' tcx hir :: Block < ' _ > ,
1616 label : Option < Label > ,
17- parent_fn_ret_ty : FnRetTy < ' _ > ,
1817) {
19- if is_lint_allowed ( cx, INFINITE_LOOPS , expr. hir_id )
20- || matches ! (
21- parent_fn_ret_ty,
22- FnRetTy :: Return ( Ty {
23- kind: TyKind :: Never ,
24- ..
25- } )
26- )
27- {
18+ if is_lint_allowed ( cx, INFINITE_LOOPS , expr. hir_id ) {
19+ return ;
20+ }
21+
22+ // Skip check if this loop is not in a function/method/closure. (In some weird case)
23+ let Some ( parent_fn_ret) = get_parent_fn_ret_ty ( cx, expr) else {
24+ return ;
25+ } ;
26+ // Or, its parent function is already returning `Never`
27+ if matches ! (
28+ parent_fn_ret,
29+ FnRetTy :: Return ( hir:: Ty {
30+ kind: hir:: TyKind :: Never ,
31+ ..
32+ } )
33+ ) {
2834 return ;
2935 }
3036
3137 // First, find any `break` or `return` without entering any inner loop,
3238 // then, find `return` or labeled `break` which breaks this loop with entering inner loop,
3339 // otherwise this loop is a infinite loop.
34- let mut direct_br_or_ret_finder = BreakOrRetFinder :: default ( ) ;
35- direct_br_or_ret_finder. visit_block ( loop_block) ;
40+ let mut direct_visitor = LoopVisitor {
41+ cx,
42+ label,
43+ is_finite : false ,
44+ enter_nested_loop : false ,
45+ } ;
46+ direct_visitor. visit_block ( loop_block) ;
3647
37- let is_finite_loop = direct_br_or_ret_finder. found || {
38- let mut inner_br_or_ret_finder = BreakOrRetFinder {
48+ let is_finite_loop = direct_visitor. is_finite || {
49+ let mut inner_loop_visitor = LoopVisitor {
50+ cx,
3951 label,
52+ is_finite : false ,
4053 enter_nested_loop : true ,
41- ..Default :: default ( )
4254 } ;
43- inner_br_or_ret_finder . visit_block ( loop_block) ;
44- inner_br_or_ret_finder . found
55+ inner_loop_visitor . visit_block ( loop_block) ;
56+ inner_loop_visitor . is_finite
4557 } ;
4658
4759 if !is_finite_loop {
4860 span_lint_and_then ( cx, INFINITE_LOOPS , expr. span , "infinite loop detected" , |diag| {
49- if let FnRetTy :: DefaultReturn ( ret_span) = parent_fn_ret_ty {
61+ if let FnRetTy :: DefaultReturn ( ret_span) = parent_fn_ret {
5062 diag. span_suggestion (
5163 ret_span,
5264 "if this is intentional, consider specifing `!` as function return" ,
@@ -56,37 +68,72 @@ pub(super) fn check(
5668 } else {
5769 diag. span_help (
5870 expr. span ,
59- "if this is not intended, add a `break` or `return` condition in this loop" ,
71+ "if this is not intended, try adding a `break` or `return` condition in this loop" ,
6072 ) ;
6173 }
6274 } ) ;
6375 }
6476}
6577
66- #[ derive( Default ) ]
67- struct BreakOrRetFinder {
78+ fn get_parent_fn_ret_ty < ' tcx > ( cx : & LateContext < ' tcx > , expr : & Expr < ' _ > ) -> Option < FnRetTy < ' tcx > > {
79+ for ( _, parent_node) in cx. tcx . hir ( ) . parent_iter ( expr. hir_id ) {
80+ match parent_node {
81+ Node :: Item ( hir:: Item {
82+ kind : hir:: ItemKind :: Fn ( FnSig { decl, .. } , _, _) ,
83+ ..
84+ } )
85+ | Node :: TraitItem ( hir:: TraitItem {
86+ kind : hir:: TraitItemKind :: Fn ( FnSig { decl, .. } , _) ,
87+ ..
88+ } )
89+ | Node :: ImplItem ( hir:: ImplItem {
90+ kind : hir:: ImplItemKind :: Fn ( FnSig { decl, .. } , _) ,
91+ ..
92+ } )
93+ | Node :: Expr ( Expr {
94+ kind : ExprKind :: Closure ( hir:: Closure { fn_decl : decl, .. } ) ,
95+ ..
96+ } ) => return Some ( decl. output ) ,
97+ _ => ( ) ,
98+ }
99+ }
100+ None
101+ }
102+
103+ struct LoopVisitor < ' hir , ' tcx > {
104+ cx : & ' hir LateContext < ' tcx > ,
68105 label : Option < Label > ,
69- found : bool ,
106+ is_finite : bool ,
70107 enter_nested_loop : bool ,
71108}
72109
73- impl < ' hir > Visitor < ' hir > for BreakOrRetFinder {
110+ impl < ' hir > Visitor < ' hir > for LoopVisitor < ' hir , ' _ > {
74111 fn visit_expr ( & mut self , ex : & ' hir Expr < ' _ > ) {
75112 match & ex. kind {
76- ExprKind :: Break ( Destination { label, .. } , ..) => {
113+ ExprKind :: Break ( hir :: Destination { label, .. } , ..) => {
77114 // When entering nested loop, only by breaking this loop's label
78115 // would be considered as exiting this loop.
79116 if self . enter_nested_loop {
80117 if label. is_some ( ) && * label == self . label {
81- self . found = true ;
118+ self . is_finite = true ;
82119 }
83120 } else {
84- self . found = true ;
121+ self . is_finite = true ;
85122 }
86123 } ,
87- ExprKind :: Ret ( ..) => self . found = true ,
124+ ExprKind :: Ret ( ..) => self . is_finite = true ,
88125 ExprKind :: Loop ( ..) if !self . enter_nested_loop => ( ) ,
89- _ => walk_expr ( self , ex) ,
126+ _ => {
127+ // Calls to a function that never return
128+ if let Some ( did) = fn_def_id ( self . cx , ex) {
129+ let fn_ret_ty = self . cx . tcx . fn_sig ( did) . skip_binder ( ) . output ( ) . skip_binder ( ) ;
130+ if fn_ret_ty. is_never ( ) {
131+ self . is_finite = true ;
132+ return ;
133+ }
134+ }
135+ walk_expr ( self , ex) ;
136+ } ,
90137 }
91138 }
92139}
0 commit comments