11use clippy_utils:: diagnostics:: span_lint_and_then;
22use clippy_utils:: { fn_def_id, is_from_proc_macro, is_lint_allowed} ;
33use 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 } ;
55use rustc_ast:: Label ;
66use rustc_errors:: Applicability ;
77use rustc_hir as hir;
88use rustc_lint:: { LateContext , LintContext } ;
99use rustc_middle:: lint:: in_external_macro;
10+ use rustc_span:: Span ;
1011
1112use super :: INFINITE_LOOP ;
1213
@@ -24,18 +25,7 @@ pub(super) fn check<'tcx>(
2425 let Some ( parent_fn_ret) = get_parent_fn_ret_ty ( cx, expr) else {
2526 return ;
2627 } ;
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) {
3929 return ;
4030 }
4131
@@ -51,9 +41,9 @@ pub(super) fn check<'tcx>(
5141
5242 if !is_finite_loop {
5343 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 ( ) {
5545 diag. span_suggestion (
56- ret_span ,
46+ span ,
5747 "if this is intentional, consider specifying `!` as function return" ,
5848 " -> !" ,
5949 Applicability :: MaybeIncorrect ,
@@ -65,11 +55,21 @@ pub(super) fn check<'tcx>(
6555 }
6656}
6757
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 > > {
6959 for ( _, parent_node) in cx. tcx . hir ( ) . parent_iter ( expr. hir_id ) {
7060 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+ } ) => ( ) ,
7171 Node :: Item ( hir:: Item {
72- kind : hir :: ItemKind :: Fn ( FnSig { decl, .. } , _, _) ,
72+ kind : ItemKind :: Fn ( FnSig { decl, .. } , _, _) ,
7373 ..
7474 } )
7575 | Node :: TraitItem ( hir:: TraitItem {
@@ -83,7 +83,7 @@ fn get_parent_fn_ret_ty<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option
8383 | Node :: Expr ( Expr {
8484 kind : ExprKind :: Closure ( hir:: Closure { fn_decl : decl, .. } ) ,
8585 ..
86- } ) => return Some ( decl. output ) ,
86+ } ) => return Some ( RetTy :: from_fn_ret_ty ( cx , decl. output ) ) ,
8787 _ => ( ) ,
8888 }
8989 }
@@ -128,3 +128,65 @@ impl<'hir> Visitor<'hir> for LoopVisitor<'hir, '_> {
128128 }
129129 }
130130}
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