1
1
use std:: ops:: ControlFlow ;
2
2
3
3
use clippy_utils:: diagnostics:: span_lint_and_then;
4
+ use clippy_utils:: source:: snippet_with_applicability;
4
5
use clippy_utils:: visitors:: for_each_expr_with_closures;
5
6
use clippy_utils:: { def_path_def_ids, fn_def_id, is_lint_allowed} ;
6
7
use rustc_data_structures:: fx:: FxHashMap ;
7
- use rustc_errors:: Applicability ;
8
+ use rustc_errors:: { Applicability , Diagnostic } ;
8
9
use rustc_hir:: def_id:: DefId ;
9
10
use rustc_hir:: hir_id:: CRATE_HIR_ID ;
10
- use rustc_hir:: { Body , ExprKind , GeneratorKind , HirIdSet } ;
11
+ use rustc_hir:: { Body , Expr , ExprKind , GeneratorKind , HirIdSet } ;
11
12
use rustc_lint:: { LateContext , LateLintPass } ;
12
13
use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
14
+ use rustc_span:: Span ;
13
15
14
16
declare_clippy_lint ! {
15
17
/// ### What it does
@@ -33,7 +35,7 @@ declare_clippy_lint! {
33
35
/// }
34
36
/// ```
35
37
/// Use instead:
36
- /// ```rust
38
+ /// ```ignore
37
39
/// use std::time::Duration;
38
40
/// pub async fn foo() {
39
41
/// tokio::time::sleep(Duration::from_secs(5));
@@ -49,7 +51,7 @@ pub(crate) struct UnnecessaryBlockingOps {
49
51
blocking_ops : Vec < String > ,
50
52
blocking_ops_with_suggs : Vec < [ String ; 2 ] > ,
51
53
/// 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 > > ,
53
55
/// Keep track of visited block ids to skip checking the same bodies in `check_body` calls
54
56
visited_block : HirIdSet ,
55
57
}
@@ -67,38 +69,30 @@ impl UnnecessaryBlockingOps {
67
69
68
70
impl_lint_pass ! ( UnnecessaryBlockingOps => [ UNNECESSARY_BLOCKING_OPS ] ) ;
69
71
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" ] ,
83
74
// 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" ] ,
102
96
] ;
103
97
104
98
impl < ' tcx > LateLintPass < ' tcx > for UnnecessaryBlockingOps {
@@ -108,55 +102,72 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryBlockingOps {
108
102
return ;
109
103
}
110
104
111
- let full_fn_list = HARD_CODED_BLOCKING_OPS_WITH_SUGG
105
+ let full_fn_list = HARD_CODED_BLOCKING_OPS
112
106
. into_iter ( )
107
+ . map ( |p| ( p. to_vec ( ) , None ) )
113
108
// 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
+ )
115
114
// Chain configured functions with suggestions
116
115
. chain (
117
116
self . blocking_ops_with_suggs
118
117
. iter ( )
119
- . map ( |[ p, s] | [ p . as_str ( ) , s. as_str ( ) ] ) ,
118
+ . map ( |[ p, s] | ( p . split ( "::" ) . collect :: < Vec < _ > > ( ) , Some ( s. as_str ( ) ) ) ) ,
120
119
) ;
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 {
124
121
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 ) ) ;
126
123
}
127
124
}
128
125
}
129
126
130
127
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
+ {
132
131
return ;
133
132
}
134
133
if let Some ( GeneratorKind :: Async ( _) ) = body. generator_kind ( ) {
135
134
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
+ }
154
151
}
155
- }
156
- ) ;
152
+ ) ;
153
+ }
154
+ _ => { }
157
155
}
158
156
ControlFlow :: < ( ) > :: Continue ( ( ) )
159
157
} ) ;
160
158
}
161
159
}
162
160
}
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