Skip to content

Commit 3e95fa8

Browse files
committed
[infinite_loops]: fix suggestion on async functions
1 parent 8631790 commit 3e95fa8

File tree

3 files changed

+112
-19
lines changed

3 files changed

+112
-19
lines changed

clippy_lints/src/loops/infinite_loop.rs

Lines changed: 80 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
22
use clippy_utils::{fn_def_id, is_from_proc_macro, is_lint_allowed};
33
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};
55
use rustc_ast::Label;
66
use rustc_errors::Applicability;
77
use rustc_hir as hir;
88
use rustc_lint::{LateContext, LintContext};
99
use rustc_middle::lint::in_external_macro;
10+
use rustc_span::Span;
1011

1112
use 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+
}

tests/ui/infinite_loops.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,4 +390,21 @@ fn span_inside_fn() {
390390
}
391391
}
392392

393+
// loop in async functions
394+
mod issue_12338 {
395+
use super::do_something;
396+
397+
async fn foo() -> ! {
398+
loop {
399+
do_something();
400+
}
401+
}
402+
async fn bar() {
403+
loop {
404+
//~^ ERROR: infinite loop detected
405+
do_something();
406+
}
407+
}
408+
}
409+
393410
fn main() {}

tests/ui/infinite_loops.stderr

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,5 +255,19 @@ LL | | })
255255
|
256256
= help: if this is not intended, try adding a `break` or `return` condition in the loop
257257

258-
error: aborting due to 17 previous errors
258+
error: infinite loop detected
259+
--> tests/ui/infinite_loops.rs:403:9
260+
|
261+
LL | / loop {
262+
LL | |
263+
LL | | do_something();
264+
LL | | }
265+
| |_________^
266+
|
267+
help: if this is intentional, consider specifying `!` as function return
268+
|
269+
LL | async fn bar() -> ! {
270+
| ++++
271+
272+
error: aborting due to 18 previous errors
259273

0 commit comments

Comments
 (0)