Skip to content

Commit 3867e00

Browse files
committed
[infinite_loop]: fix suggestion with unit ty return;
add more test cases with async clousures;
1 parent d2e9d12 commit 3867e00

File tree

4 files changed

+196
-75
lines changed

4 files changed

+196
-75
lines changed

clippy_lints/src/loops/infinite_loop.rs

Lines changed: 83 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
use std::ops::ControlFlow;
2+
13
use clippy_utils::diagnostics::span_lint_and_then;
24
use clippy_utils::{fn_def_id, is_from_proc_macro, is_lint_allowed};
35
use hir::intravisit::{walk_expr, Visitor};
46
use hir::{ClosureKind, Expr, ExprKind, FnRetTy, FnSig, ItemKind, Node, Ty, TyKind};
5-
use rustc_ast::Label;
67
use rustc_errors::Applicability;
78
use rustc_hir as hir;
89
use rustc_lint::{LateContext, LintContext};
@@ -15,7 +16,7 @@ pub(super) fn check<'tcx>(
1516
cx: &LateContext<'tcx>,
1617
expr: &Expr<'tcx>,
1718
loop_block: &'tcx hir::Block<'_>,
18-
label: Option<Label>,
19+
hir_id: hir::HirId,
1920
) {
2021
if is_lint_allowed(cx, INFINITE_LOOP, expr.hir_id) {
2122
return;
@@ -31,21 +32,29 @@ pub(super) fn check<'tcx>(
3132

3233
let mut loop_visitor = LoopVisitor {
3334
cx,
34-
label,
35-
is_finite: false,
36-
loop_depth: 0,
35+
hir_id,
36+
block: loop_block,
37+
nested_loop_count: 0,
3738
};
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() {
4340
span_lint_and_then(cx, INFINITE_LOOP, expr.span, "infinite loop detected", |diag| {
4441
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+
};
4554
diag.span_suggestion(
4655
span,
4756
"if this is intentional, consider specifying `!` as function return",
48-
" -> !",
57+
suggestion,
4958
Applicability::MaybeIncorrect,
5059
);
5160
} else {
@@ -92,38 +101,50 @@ fn get_parent_fn_ret_ty<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option
92101

93102
struct LoopVisitor<'hir, 'tcx> {
94103
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+
}
98114
}
99115

100116
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 {
102120
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(())
109130
}
110131
},
111-
ExprKind::Ret(..) => self.is_finite = true,
132+
ExprKind::Ret(..) => ControlFlow::Break(()),
112133
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
116138
},
117139
_ => {
118140
// Calls to a function that never return
119141
if let Some(did) = fn_def_id(self.cx, ex) {
120142
let fn_ret_ty = self.cx.tcx.fn_sig(did).skip_binder().output().skip_binder();
121143
if fn_ret_ty.is_never() {
122-
self.is_finite = true;
123-
return;
144+
return ControlFlow::Break(());
124145
}
125146
}
126-
walk_expr(self, ex);
147+
walk_expr(self, ex)
127148
},
128149
}
129150
}
@@ -141,35 +162,39 @@ impl<'hir> RetTy<'hir> {
141162
/// given `ty` is not an `OpaqueDef`.
142163
fn inner_<'tcx>(cx: &LateContext<'tcx>, ty: &Ty<'tcx>) -> Option<Ty<'tcx>> {
143164
/// 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 {
153175
term: hir::Term::Ty(ty),
154-
} = type_binding.kind
176+
} = constraint.kind
155177
{
156-
self.res = Some(*ty);
178+
ControlFlow::Break(*ty)
179+
} else {
180+
ControlFlow::Continue(())
157181
}
158182
}
159183
}
160184

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+
}
173198
}
174199

175200
match fn_ret_ty {
@@ -186,7 +211,12 @@ impl<'hir> RetTy<'hir> {
186211
}
187212
}
188213
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+
)
191221
}
192222
}

clippy_lints/src/loops/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -786,11 +786,11 @@ impl<'tcx> LateLintPass<'tcx> for Loops {
786786
// check for `loop { if let {} else break }` that could be `while let`
787787
// (also matches an explicit "match" instead of "if let")
788788
// (even if the "match" or "if let" is used for declaration)
789-
if let ExprKind::Loop(block, label, LoopSource::Loop, _) = expr.kind {
789+
if let ExprKind::Loop(block, _, LoopSource::Loop, _) = expr.kind {
790790
// also check for empty `loop {}` statements, skipping those in #[panic_handler]
791791
empty_loop::check(cx, expr, block);
792792
while_let_loop::check(cx, expr, block);
793-
infinite_loop::check(cx, expr, block, label);
793+
infinite_loop::check(cx, expr, block, expr.hir_id);
794794
}
795795

796796
while_let_on_iterator::check(cx, expr);

tests/ui/infinite_loops.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
//@no-rustfix
22
//@aux-build:proc_macros.rs
33

4-
#![allow(clippy::never_loop)]
4+
#![allow(clippy::never_loop, clippy::unused_unit)]
55
#![warn(clippy::infinite_loop)]
6+
#![feature(async_closure)]
67

78
extern crate proc_macros;
89
use proc_macros::{external, with_span};
@@ -390,6 +391,20 @@ fn span_inside_fn() {
390391
}
391392
}
392393

394+
fn return_unit() -> () {
395+
loop {
396+
//~^ ERROR: infinite loop detected
397+
do_nothing();
398+
}
399+
400+
let async_return_unit = || -> () {
401+
loop {
402+
//~^ ERROR: infinite loop detected
403+
do_nothing();
404+
}
405+
};
406+
}
407+
393408
// loop in async functions
394409
mod issue_12338 {
395410
use super::do_something;
@@ -405,6 +420,26 @@ mod issue_12338 {
405420
do_something();
406421
}
407422
}
423+
// Just to make sure checks for async block works as intended.
424+
fn async_closure() {
425+
let _loop_forever = async || {
426+
loop {
427+
//~^ ERROR: infinite loop detected
428+
do_something();
429+
}
430+
};
431+
let _somehow_ok = async || -> ! {
432+
loop {
433+
do_something();
434+
}
435+
};
436+
let _ret_unit = async || -> () {
437+
loop {
438+
//~^ ERROR: infinite loop detected
439+
do_something();
440+
}
441+
};
442+
}
408443
}
409444

410445
fn main() {}

0 commit comments

Comments
 (0)