11use clippy_config:: Conf ;
2- use clippy_utils:: diagnostics:: span_lint_and_sugg ;
2+ use clippy_utils:: diagnostics:: span_lint_hir_and_then ;
33use clippy_utils:: source:: snippet;
44use clippy_utils:: ty:: implements_trait;
55use rustc_abi:: Size ;
66use 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 } ;
810use rustc_lint:: { LateContext , LateLintPass } ;
11+ use rustc_middle:: hir:: nested_filter;
12+ use rustc_middle:: ty:: Ty ;
913use rustc_session:: impl_lint_pass;
14+ use rustc_span:: Span ;
1015
1116declare_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
5560impl_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}
0 commit comments