1
1
use clippy_utils:: diagnostics:: span_lint_and_then;
2
2
use clippy_utils:: { fn_def_id, is_from_proc_macro, is_lint_allowed} ;
3
3
use hir:: intravisit:: { walk_expr, Visitor } ;
4
- use hir:: { Expr , ExprKind , FnRetTy , FnSig , Node } ;
4
+ use hir:: { ClosureKind , Expr , ExprKind , FnRetTy , FnSig , ItemKind , Node , Ty , TyKind } ;
5
5
use rustc_ast:: Label ;
6
6
use rustc_errors:: Applicability ;
7
7
use rustc_hir as hir;
8
8
use rustc_lint:: { LateContext , LintContext } ;
9
9
use rustc_middle:: lint:: in_external_macro;
10
+ use rustc_span:: Span ;
10
11
11
12
use super :: INFINITE_LOOP ;
12
13
@@ -24,18 +25,7 @@ pub(super) fn check<'tcx>(
24
25
let Some ( parent_fn_ret) = get_parent_fn_ret_ty ( cx, expr) else {
25
26
return ;
26
27
} ;
27
- // Or, its parent function is already returning `Never`
28
- if matches ! (
29
- parent_fn_ret,
30
- FnRetTy :: Return ( hir:: Ty {
31
- kind: hir:: TyKind :: Never ,
32
- ..
33
- } )
34
- ) {
35
- return ;
36
- }
37
-
38
- if in_external_macro ( cx. sess ( ) , expr. span ) || is_from_proc_macro ( cx, expr) {
28
+ if parent_fn_ret. is_never ( ) || in_external_macro ( cx. sess ( ) , expr. span ) || is_from_proc_macro ( cx, expr) {
39
29
return ;
40
30
}
41
31
@@ -51,9 +41,9 @@ pub(super) fn check<'tcx>(
51
41
52
42
if !is_finite_loop {
53
43
span_lint_and_then ( cx, INFINITE_LOOP , expr. span , "infinite loop detected" , |diag| {
54
- if let FnRetTy :: DefaultReturn ( ret_span ) = parent_fn_ret {
44
+ if let Some ( span ) = parent_fn_ret. sugg_span ( ) {
55
45
diag. span_suggestion (
56
- ret_span ,
46
+ span ,
57
47
"if this is intentional, consider specifying `!` as function return" ,
58
48
" -> !" ,
59
49
Applicability :: MaybeIncorrect ,
@@ -65,11 +55,21 @@ pub(super) fn check<'tcx>(
65
55
}
66
56
}
67
57
68
- fn get_parent_fn_ret_ty < ' tcx > ( cx : & LateContext < ' tcx > , expr : & Expr < ' _ > ) -> Option < FnRetTy < ' tcx > > {
58
+ fn get_parent_fn_ret_ty < ' tcx > ( cx : & LateContext < ' tcx > , expr : & Expr < ' _ > ) -> Option < RetTy < ' tcx > > {
69
59
for ( _, parent_node) in cx. tcx . hir ( ) . parent_iter ( expr. hir_id ) {
70
60
match parent_node {
61
+ // Skip `Coroutine`, these are the body of `async fn`, not the async closures.
62
+ // This is because we still need to backtrack one parent node to get the `OpaqueDef` ty.
63
+ Node :: Expr ( Expr {
64
+ kind :
65
+ ExprKind :: Closure ( hir:: Closure {
66
+ kind : ClosureKind :: Coroutine ( _) ,
67
+ ..
68
+ } ) ,
69
+ ..
70
+ } ) => ( ) ,
71
71
Node :: Item ( hir:: Item {
72
- kind : hir :: ItemKind :: Fn ( FnSig { decl, .. } , _, _) ,
72
+ kind : ItemKind :: Fn ( FnSig { decl, .. } , _, _) ,
73
73
..
74
74
} )
75
75
| Node :: TraitItem ( hir:: TraitItem {
@@ -83,7 +83,7 @@ fn get_parent_fn_ret_ty<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option
83
83
| Node :: Expr ( Expr {
84
84
kind : ExprKind :: Closure ( hir:: Closure { fn_decl : decl, .. } ) ,
85
85
..
86
- } ) => return Some ( decl. output ) ,
86
+ } ) => return Some ( RetTy :: from_fn_ret_ty ( cx , decl. output ) ) ,
87
87
_ => ( ) ,
88
88
}
89
89
}
@@ -128,3 +128,65 @@ impl<'hir> Visitor<'hir> for LoopVisitor<'hir, '_> {
128
128
}
129
129
}
130
130
}
131
+
132
+ /// Similar to [`FnRetTy`], but reveals the actual type of an `OpaqueDef`.
133
+ enum RetTy < ' hir > {
134
+ DefaultReturn ( Span ) ,
135
+ Return ( Ty < ' hir > ) ,
136
+ }
137
+
138
+ impl < ' hir > RetTy < ' hir > {
139
+ fn from_fn_ret_ty ( cx : & LateContext < ' hir > , fn_ret_ty : FnRetTy < ' hir > ) -> Self {
140
+ /// Reveal and return the related type of an `opaque`, return `None` if the
141
+ /// given `ty` is not an `OpaqueDef`.
142
+ fn inner_ < ' tcx > ( cx : & LateContext < ' tcx > , ty : & Ty < ' tcx > ) -> Option < Ty < ' tcx > > {
143
+ /// 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 {
153
+ term : hir:: Term :: Ty ( ty) ,
154
+ } = type_binding. kind
155
+ {
156
+ self . res = Some ( * ty) ;
157
+ }
158
+ }
159
+ }
160
+
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
173
+ }
174
+
175
+ match fn_ret_ty {
176
+ FnRetTy :: DefaultReturn ( span) => Self :: DefaultReturn ( span) ,
177
+ FnRetTy :: Return ( ty) => Self :: Return ( inner_ ( cx, ty) . unwrap_or ( * ty) ) ,
178
+ }
179
+ }
180
+ /// Returns the span to where the suggestion should be.
181
+ fn sugg_span ( & self ) -> Option < Span > {
182
+ match self {
183
+ Self :: DefaultReturn ( span) => Some ( * span) ,
184
+ Self :: Return ( ty) if matches ! ( ty. kind, TyKind :: Tup ( & [ ] ) ) => Some ( ty. span ) ,
185
+ Self :: Return ( _) => None ,
186
+ }
187
+ }
188
+ fn is_never ( & self ) -> bool {
189
+ let Self :: Return ( ty) = self else { return false } ;
190
+ matches ! ( ty. kind, TyKind :: Never )
191
+ }
192
+ }
0 commit comments