11use std:: ops:: ControlFlow ;
22
33use clippy_utils:: diagnostics:: span_lint_and_then;
4+ use clippy_utils:: source:: snippet_with_applicability;
45use clippy_utils:: visitors:: for_each_expr_with_closures;
56use clippy_utils:: { def_path_def_ids, fn_def_id, is_lint_allowed} ;
67use rustc_data_structures:: fx:: FxHashMap ;
7- use rustc_errors:: Applicability ;
8+ use rustc_errors:: { Applicability , Diagnostic } ;
89use rustc_hir:: def_id:: DefId ;
910use rustc_hir:: hir_id:: CRATE_HIR_ID ;
10- use rustc_hir:: { Body , ExprKind , GeneratorKind , HirIdSet } ;
11+ use rustc_hir:: { Body , Expr , ExprKind , GeneratorKind , HirIdSet } ;
1112use rustc_lint:: { LateContext , LateLintPass } ;
1213use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
14+ use rustc_span:: Span ;
1315
1416declare_clippy_lint ! {
1517 /// ### What it does
@@ -33,7 +35,7 @@ declare_clippy_lint! {
3335 /// }
3436 /// ```
3537 /// Use instead:
36- /// ```rust
38+ /// ```ignore
3739 /// use std::time::Duration;
3840 /// pub async fn foo() {
3941 /// tokio::time::sleep(Duration::from_secs(5));
@@ -49,7 +51,7 @@ pub(crate) struct UnnecessaryBlockingOps {
4951 blocking_ops : Vec < String > ,
5052 blocking_ops_with_suggs : Vec < [ String ; 2 ] > ,
5153 /// Map of resolved funtion def_id with suggestion string after checking crate
52- id_with_suggs : FxHashMap < DefId , String > ,
54+ id_with_suggs : FxHashMap < DefId , Option < String > > ,
5355 /// Keep track of visited block ids to skip checking the same bodies in `check_body` calls
5456 visited_block : HirIdSet ,
5557}
@@ -67,38 +69,30 @@ impl UnnecessaryBlockingOps {
6769
6870impl_lint_pass ! ( UnnecessaryBlockingOps => [ UNNECESSARY_BLOCKING_OPS ] ) ;
6971
70- // TODO: Should we throw away all suggestions and and give full control to user configurations?
71- // this feels like a free ad for tokio :P
72- static HARD_CODED_BLOCKING_OPS_WITH_SUGG : [ [ & str ; 2 ] ; 26 ] = [
73- // Sleep
74- [ "std::thread::sleep" , "tokio::time::sleep" ] ,
75- // IO functions
76- [ "std::io::copy" , "tokio::io::copy" ] ,
77- [ "std::io::empty" , "tokio::io::empty" ] ,
78- [ "std::io::repeat" , "tokio::io::repeat" ] ,
79- [ "std::io::sink" , "tokio::io::sink" ] ,
80- [ "std::io::stderr" , "tokio::io::stderr" ] ,
81- [ "std::io::stdin" , "tokio::io::stdin" ] ,
82- [ "std::io::stdout" , "tokio::io::stdout" ] ,
72+ static HARD_CODED_BLOCKING_OPS : [ & [ & str ] ; 21 ] = [
73+ & [ "std" , "thread" , "sleep" ] ,
8374 // Filesystem functions
84- [ "std::fs::try_exists" , "tokio::fs::try_exists" ] ,
85- [ "std::fs::canonicalize" , "tokio::fs::canonicalize" ] ,
86- [ "std::fs::copy" , "tokio::fs::copy" ] ,
87- [ "std::fs::create_dir" , "tokio::fs::create_dir" ] ,
88- [ "std::fs::create_dir_all" , "tokio::fs::create_dir_all" ] ,
89- [ "std::fs::hard_link" , "tokio::fs::hard_link" ] ,
90- [ "std::fs::metadata" , "tokio::fs::metadata" ] ,
91- [ "std::fs::read" , "tokio::fs::read" ] ,
92- [ "std::fs::read_dir" , "tokio::fs::read_dir" ] ,
93- [ "std::fs::read_to_string" , "tokio::fs::read_to_string" ] ,
94- [ "std::fs::remove_dir" , "tokio::fs::remove_dir" ] ,
95- [ "std::fs::remove_dir_all" , "tokio::fs::remove_dir_all" ] ,
96- [ "std::fs::remove_file" , "tokio::fs::remove_file" ] ,
97- [ "std::fs::rename" , "tokio::fs::rename" ] ,
98- [ "std::fs::set_permissions" , "tokio::fs::set_permissions" ] ,
99- [ "std::fs::soft_link" , "tokio::fs::soft_link" ] ,
100- [ "std::fs::symlink_metadata" , "tokio::fs::symlink_metadata" ] ,
101- [ "std::fs::write" , "tokio::fs::write" ] ,
75+ & [ "std" , "fs" , "try_exists" ] ,
76+ & [ "std" , "fs" , "canonicalize" ] ,
77+ & [ "std" , "fs" , "copy" ] ,
78+ & [ "std" , "fs" , "create_dir" ] ,
79+ & [ "std" , "fs" , "create_dir_all" ] ,
80+ & [ "std" , "fs" , "hard_link" ] ,
81+ & [ "std" , "fs" , "metadata" ] ,
82+ & [ "std" , "fs" , "read" ] ,
83+ & [ "std" , "fs" , "read_dir" ] ,
84+ & [ "std" , "fs" , "read_link" ] ,
85+ & [ "std" , "fs" , "read_to_string" ] ,
86+ & [ "std" , "fs" , "remove_dir" ] ,
87+ & [ "std" , "fs" , "remove_dir_all" ] ,
88+ & [ "std" , "fs" , "remove_file" ] ,
89+ & [ "std" , "fs" , "rename" ] ,
90+ & [ "std" , "fs" , "set_permissions" ] ,
91+ & [ "std" , "fs" , "symlink_metadata" ] ,
92+ & [ "std" , "fs" , "write" ] ,
93+ // IO functions
94+ & [ "std" , "io" , "copy" ] ,
95+ & [ "std" , "io" , "read_to_string" ] ,
10296] ;
10397
10498impl < ' tcx > LateLintPass < ' tcx > for UnnecessaryBlockingOps {
@@ -108,55 +102,72 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryBlockingOps {
108102 return ;
109103 }
110104
111- let full_fn_list = HARD_CODED_BLOCKING_OPS_WITH_SUGG
105+ let full_fn_list = HARD_CODED_BLOCKING_OPS
112106 . into_iter ( )
107+ . map ( |p| ( p. to_vec ( ) , None ) )
113108 // Chain configured functions without suggestions
114- . chain ( self . blocking_ops . iter ( ) . map ( |p| [ p, "" ] ) )
109+ . chain (
110+ self . blocking_ops
111+ . iter ( )
112+ . map ( |p| ( p. split ( "::" ) . collect :: < Vec < _ > > ( ) , None ) ) ,
113+ )
115114 // Chain configured functions with suggestions
116115 . chain (
117116 self . blocking_ops_with_suggs
118117 . iter ( )
119- . map ( |[ p, s] | [ p . as_str ( ) , s. as_str ( ) ] ) ,
118+ . map ( |[ p, s] | ( p . split ( "::" ) . collect :: < Vec < _ > > ( ) , Some ( s. as_str ( ) ) ) ) ,
120119 ) ;
121-
122- for [ path_str, sugg_path_str] in full_fn_list {
123- let path = path_str. split ( "::" ) . collect :: < Vec < _ > > ( ) ;
120+ for ( path, maybe_sugg_str) in full_fn_list {
124121 for did in def_path_def_ids ( cx, & path) {
125- self . id_with_suggs . insert ( did, sugg_path_str . to_string ( ) ) ;
122+ self . id_with_suggs . insert ( did, maybe_sugg_str . map ( ToOwned :: to_owned ) ) ;
126123 }
127124 }
128125 }
129126
130127 fn check_body ( & mut self , cx : & LateContext < ' tcx > , body : & ' tcx Body < ' tcx > ) {
131- if self . visited_block . contains ( & body. value . hir_id ) {
128+ if is_lint_allowed ( cx, UNNECESSARY_BLOCKING_OPS , body. value . hir_id )
129+ || self . visited_block . contains ( & body. value . hir_id )
130+ {
132131 return ;
133132 }
134133 if let Some ( GeneratorKind :: Async ( _) ) = body. generator_kind ( ) {
135134 for_each_expr_with_closures ( cx, body, |ex| {
136- if let ExprKind :: Block ( block, _) = ex. kind {
137- self . visited_block . insert ( block. hir_id ) ;
138- } else if let Some ( call_did) = fn_def_id ( cx, ex) &&
139- let Some ( replace_sugg) = self . id_with_suggs . get ( & call_did)
140- {
141- span_lint_and_then (
142- cx,
143- UNNECESSARY_BLOCKING_OPS ,
144- ex. span ,
145- "blocking function call detected in an async body" ,
146- |diag| {
147- if !replace_sugg. is_empty ( ) {
148- diag. span_suggestion (
149- ex. span ,
150- "try using an async counterpart such as" ,
151- replace_sugg,
152- Applicability :: Unspecified ,
153- ) ;
135+ match ex. kind {
136+ ExprKind :: Block ( block, _) => {
137+ self . visited_block . insert ( block. hir_id ) ;
138+ }
139+ ExprKind :: Call ( call, _)
140+ if let Some ( call_did) = fn_def_id ( cx, ex) &&
141+ let Some ( maybe_sugg) = self . id_with_suggs . get ( & call_did) => {
142+ span_lint_and_then (
143+ cx,
144+ UNNECESSARY_BLOCKING_OPS ,
145+ call. span ,
146+ "blocking function call detected in an async body" ,
147+ |diag| {
148+ if let Some ( sugg_fn_path) = maybe_sugg {
149+ make_suggestion ( diag, cx, ex, call. span , sugg_fn_path) ;
150+ }
154151 }
155- }
156- ) ;
152+ ) ;
153+ }
154+ _ => { }
157155 }
158156 ControlFlow :: < ( ) > :: Continue ( ( ) )
159157 } ) ;
160158 }
161159 }
162160}
161+
162+ fn make_suggestion ( diag : & mut Diagnostic , cx : & LateContext < ' _ > , expr : & Expr < ' _ > , fn_span : Span , sugg_fn_path : & str ) {
163+ let mut applicability = Applicability :: Unspecified ;
164+ let args_span = expr. span . with_lo ( fn_span. hi ( ) ) ;
165+ let args_snippet = snippet_with_applicability ( cx, args_span, ".." , & mut applicability) ;
166+ let suggestion = format ! ( "{sugg_fn_path}{args_snippet}.await" ) ;
167+ diag. span_suggestion (
168+ expr. span ,
169+ "try using its async counterpart" ,
170+ suggestion,
171+ Applicability :: Unspecified ,
172+ ) ;
173+ }
0 commit comments