Skip to content

Commit 207a248

Browse files
committed
Improved large_futures to be applicable to any expression of type Future
1 parent 637108e commit 207a248

File tree

3 files changed

+361
-76
lines changed

3 files changed

+361
-76
lines changed

clippy_lints/src/large_futures.rs

Lines changed: 107 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
11
use clippy_config::Conf;
2-
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::diagnostics::span_lint_hir_and_then;
33
use clippy_utils::source::snippet;
44
use clippy_utils::ty::implements_trait;
55
use rustc_abi::Size;
66
use rustc_errors::Applicability;
7-
use rustc_hir::{Expr, ExprKind, LangItem, MatchSource};
7+
use rustc_hir::def_id::LocalDefId;
8+
use rustc_hir::intravisit::{FnKind, Visitor, walk_body, walk_expr};
9+
use rustc_hir::{Body, Expr, FnDecl};
810
use rustc_lint::{LateContext, LateLintPass};
11+
use rustc_middle::hir::nested_filter;
12+
use rustc_middle::ty::Ty;
913
use rustc_session::impl_lint_pass;
14+
use rustc_span::Span;
1015

1116
declare_clippy_lint! {
1217
/// ### What it does
1318
/// It checks for the size of a `Future` created by `async fn` or `async {}`.
1419
///
1520
/// ### Why is this bad?
1621
/// Due to the current [unideal implementation](https://github.com/rust-lang/rust/issues/69826) of `Coroutine`,
17-
/// large size of a `Future` may cause stack overflows.
22+
/// large size of a `Future` may cause stack overflows or be inefficient.
1823
///
1924
/// ### Example
2025
/// ```no_run
@@ -25,7 +30,7 @@ declare_clippy_lint! {
2530
/// }
2631
/// ```
2732
///
28-
/// `Box::pin` the big future instead.
33+
/// `Box::pin` the big future or the captured elements inside the future instead.
2934
///
3035
/// ```no_run
3136
/// async fn large_future(_x: [u8; 16 * 1024]) {}
@@ -54,29 +59,106 @@ impl LargeFuture {
5459

5560
impl_lint_pass!(LargeFuture => [LARGE_FUTURES]);
5661

57-
impl<'tcx> LateLintPass<'tcx> for LargeFuture {
58-
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
59-
if let ExprKind::Match(scrutinee, _, MatchSource::AwaitDesugar) = expr.kind
60-
&& let ExprKind::Call(func, [arg]) = scrutinee.kind
61-
&& let ExprKind::Path(qpath) = func.kind
62-
&& cx.tcx.qpath_is_lang_item(qpath, LangItem::IntoFutureIntoFuture)
62+
struct AsyncFnVisitor<'a, 'tcx> {
63+
cx: &'a LateContext<'tcx>,
64+
future_size_threshold: u64,
65+
/// Depth of the visitor with value `0` denoting the top-level expression.
66+
depth: usize,
67+
/// Whether a clippy suggestion was emitted for deeper levels of expressions.
68+
emitted: bool,
69+
}
70+
71+
impl<'tcx> Visitor<'tcx> for AsyncFnVisitor<'_, 'tcx> {
72+
type NestedFilter = nested_filter::OnlyBodies;
73+
74+
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
75+
// For each expression, try to find the 'deepest' occurance of a too large a future.
76+
// Chances are that subsequent less deep expression contain that too large a future,
77+
// and hence it is redudant to also emit a suggestion for these expressions.
78+
// Expressions on the same depth level can however emit a suggestion too.
79+
80+
self.depth += 1;
81+
let old_emitted = self.emitted;
82+
self.emitted = false; // Reset emitted for deeper level, but recover it later.
83+
walk_expr(self, expr);
84+
self.depth -= 1;
85+
86+
if !self.emitted
6387
&& !expr.span.from_expansion()
64-
&& let ty = cx.typeck_results().expr_ty(arg)
65-
&& let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait()
66-
&& implements_trait(cx, ty, future_trait_def_id, &[])
67-
&& let Ok(layout) = cx.tcx.layout_of(cx.typing_env().as_query_input(ty))
68-
&& let size = layout.layout.size()
69-
&& size >= Size::from_bytes(self.future_size_threshold)
88+
&& let Some(ty) = self.cx.typeck_results().expr_ty_opt(expr)
89+
&& let Some(size) = type_is_large_future(self.cx, ty, self.future_size_threshold)
7090
{
71-
span_lint_and_sugg(
72-
cx,
73-
LARGE_FUTURES,
74-
arg.span,
75-
format!("large future with a size of {} bytes", size.bytes()),
76-
"consider `Box::pin` on it",
77-
format!("Box::pin({})", snippet(cx, arg.span, "..")),
78-
Applicability::Unspecified,
79-
);
91+
if self.depth == 0 {
92+
// Special lint for top level fn bodies.
93+
span_lint_hir_and_then(
94+
self.cx,
95+
LARGE_FUTURES,
96+
expr.hir_id,
97+
expr.span,
98+
format!("definition for a large future with a size of {} bytes", size.bytes()),
99+
|db| {
100+
db.span_help(expr.span, "consider `Box::pin` when constructing it or reducing direct ownership of the items in scope and use references instead where possible");
101+
},
102+
);
103+
} else {
104+
// Expressions within a fn body.
105+
span_lint_hir_and_then(
106+
self.cx,
107+
LARGE_FUTURES,
108+
expr.hir_id,
109+
expr.span,
110+
format!("usage of large future with a size of {} bytes", size.bytes()),
111+
|db| {
112+
db.span_suggestion(
113+
expr.span,
114+
"consider `Box::pin` on it or reducing direct ownership of the items in scope and use references instead where possible",
115+
format!("Box::pin({})", snippet(self.cx, expr.span, "..")),
116+
Applicability::Unspecified,
117+
);
118+
},
119+
);
120+
}
121+
122+
self.emitted = true;
80123
}
124+
125+
self.emitted |= old_emitted;
126+
}
127+
128+
fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
129+
self.cx.tcx
130+
}
131+
}
132+
133+
fn type_is_large_future<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, future_size_threshold: u64) -> Option<Size> {
134+
if let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait()
135+
&& implements_trait(cx, ty, future_trait_def_id, &[])
136+
&& let Ok(layout) = cx.tcx.layout_of(cx.typing_env().as_query_input(ty))
137+
&& let size = layout.layout.size()
138+
&& size >= Size::from_bytes(future_size_threshold)
139+
{
140+
Some(size)
141+
} else {
142+
None
143+
}
144+
}
145+
146+
impl<'tcx> LateLintPass<'tcx> for LargeFuture {
147+
fn check_fn(
148+
&mut self,
149+
cx: &LateContext<'tcx>,
150+
_: FnKind<'tcx>,
151+
_: &'tcx FnDecl<'_>,
152+
body: &'tcx Body<'_>,
153+
_: Span,
154+
_: LocalDefId,
155+
) {
156+
let mut visitor = AsyncFnVisitor {
157+
cx,
158+
depth: 0,
159+
future_size_threshold: self.future_size_threshold,
160+
emitted: false,
161+
};
162+
walk_body(&mut visitor, body);
81163
}
82164
}

tests/ui/large_futures.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#![warn(clippy::large_futures)]
88

99
async fn big_fut(_arg: [u8; 1024 * 16]) {}
10+
//~^ large_futures
1011

1112
async fn wait() {
1213
let f = async {
@@ -45,24 +46,24 @@ pub fn foo() -> impl std::future::Future<Output = ()> {
4546
async {}.await;
4647
dbg!(x);
4748
}
49+
//~^ large_futures
4850
}
4951

5052
pub async fn lines() {
5153
async {
52-
//~^ large_futures
53-
5454
let x = [0i32; 1024 * 16];
5555
async {}.await;
56+
5657
println!("{:?}", x);
5758
}
59+
//~^ large_futures
5860
.await;
5961
}
6062

6163
pub async fn macro_expn() {
6264
macro_rules! macro_ {
6365
() => {
6466
async {
65-
//~^ large_futures
6667
let x = [0i32; 1024 * 16];
6768
async {}.await;
6869
println!("macro: {:?}", x);
@@ -71,5 +72,6 @@ pub async fn macro_expn() {
7172
}
7273
macro_!().await
7374
}
75+
//~^ large_futures
7476

7577
fn main() {}

0 commit comments

Comments
 (0)