Skip to content

Commit f41378a

Browse files
committed
remove hard-coded suggestion, add ui-toml test
1 parent 3351b96 commit f41378a

File tree

6 files changed

+197
-97
lines changed

6 files changed

+197
-97
lines changed
+74-63
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
use std::ops::ControlFlow;
22

33
use clippy_utils::diagnostics::span_lint_and_then;
4+
use clippy_utils::source::snippet_with_applicability;
45
use clippy_utils::visitors::for_each_expr_with_closures;
56
use clippy_utils::{def_path_def_ids, fn_def_id, is_lint_allowed};
67
use rustc_data_structures::fx::FxHashMap;
7-
use rustc_errors::Applicability;
8+
use rustc_errors::{Applicability, Diagnostic};
89
use rustc_hir::def_id::DefId;
910
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};
1112
use rustc_lint::{LateContext, LateLintPass};
1213
use rustc_session::{declare_tool_lint, impl_lint_pass};
14+
use rustc_span::Span;
1315

1416
declare_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

6870
impl_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

10498
impl<'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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
blocking-ops = ["unnecessary_blocking_ops::blocking_mod::sleep"]
2+
blocking-ops-with-suggestions = [
3+
["std::fs::remove_dir", "tokio::fs::remove_dir"],
4+
["std::fs::copy", "tokio::fs::copy"],
5+
["std::io::copy", "tokio::io::copy"],
6+
["std::io::read_to_string", "unnecessary_blocking_ops::async_mod::read_to_string"],
7+
["std::thread::sleep", "unnecessary_blocking_ops::async_mod::sleep"],
8+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//@no-rustfix
2+
#![warn(clippy::unnecessary_blocking_ops)]
3+
use std::thread::sleep;
4+
use std::time::Duration;
5+
use std::{fs, io};
6+
7+
mod async_mod {
8+
pub async fn sleep(_dur: std::time::Duration) {}
9+
pub async fn read_to_string(mut reader: std::io::Stdin) -> Result<String, ()> {
10+
Ok(String::new())
11+
}
12+
}
13+
14+
mod blocking_mod {
15+
pub async fn sleep(_dur: std::time::Duration) {}
16+
}
17+
18+
pub async fn async_fn() {
19+
sleep(Duration::from_secs(1));
20+
//~^ ERROR: blocking function call detected in an async body
21+
fs::remove_dir("").unwrap();
22+
//~^ ERROR: blocking function call detected in an async body
23+
fs::copy("", "_").unwrap();
24+
//~^ ERROR: blocking function call detected in an async body
25+
let mut r: &[u8] = b"hello";
26+
let mut w: Vec<u8> = vec![];
27+
io::copy(&mut r, &mut w).unwrap();
28+
//~^ ERROR: blocking function call detected in an async body
29+
let _cont = io::read_to_string(io::stdin()).unwrap();
30+
//~^ ERROR: blocking function call detected in an async body
31+
fs::create_dir("").unwrap();
32+
//~^ ERROR: blocking function call detected in an async body
33+
}
34+
35+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
error: blocking function call detected in an async body
2+
--> $DIR/unnecessary_blocking_ops.rs:19:5
3+
|
4+
LL | sleep(Duration::from_secs(1));
5+
| ^^^^^------------------------
6+
| |
7+
| help: try using its async counterpart: `unnecessary_blocking_ops::async_mod::sleep(Duration::from_secs(1)).await`
8+
|
9+
= note: `-D clippy::unnecessary-blocking-ops` implied by `-D warnings`
10+
= help: to override `-D warnings` add `#[allow(clippy::unnecessary_blocking_ops)]`
11+
12+
error: blocking function call detected in an async body
13+
--> $DIR/unnecessary_blocking_ops.rs:21:5
14+
|
15+
LL | fs::remove_dir("").unwrap();
16+
| ^^^^^^^^^^^^^^----
17+
| |
18+
| help: try using its async counterpart: `tokio::fs::remove_dir("").await`
19+
20+
error: blocking function call detected in an async body
21+
--> $DIR/unnecessary_blocking_ops.rs:23:5
22+
|
23+
LL | fs::copy("", "_").unwrap();
24+
| ^^^^^^^^---------
25+
| |
26+
| help: try using its async counterpart: `tokio::fs::copy("", "_").await`
27+
28+
error: blocking function call detected in an async body
29+
--> $DIR/unnecessary_blocking_ops.rs:27:5
30+
|
31+
LL | io::copy(&mut r, &mut w).unwrap();
32+
| ^^^^^^^^----------------
33+
| |
34+
| help: try using its async counterpart: `tokio::io::copy(&mut r, &mut w).await`
35+
36+
error: blocking function call detected in an async body
37+
--> $DIR/unnecessary_blocking_ops.rs:29:17
38+
|
39+
LL | let _cont = io::read_to_string(io::stdin()).unwrap();
40+
| ^^^^^^^^^^^^^^^^^^-------------
41+
| |
42+
| help: try using its async counterpart: `unnecessary_blocking_ops::async_mod::read_to_string(io::stdin()).await`
43+
44+
error: blocking function call detected in an async body
45+
--> $DIR/unnecessary_blocking_ops.rs:31:5
46+
|
47+
LL | fs::create_dir("").unwrap();
48+
| ^^^^^^^^^^^^^^
49+
50+
error: aborting due to 6 previous errors
51+

tests/ui/unnecessary_blocking_ops.rs

+11-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
//@no-rustfix
21
#![feature(async_fn_in_trait)]
32
#![feature(async_closure)]
43
#![allow(incomplete_features)]
54
#![warn(clippy::unnecessary_blocking_ops)]
6-
use std::{fs, io};
75
use std::thread::sleep;
86
use std::time::Duration;
7+
use std::{fs, io};
98
use tokio::io as tokio_io;
109

1110
mod totally_thread_safe {
@@ -14,24 +13,29 @@ mod totally_thread_safe {
1413

1514
pub async fn async_fn() {
1615
sleep(Duration::from_secs(1));
16+
//~^ ERROR: blocking function call detected in an async body
1717
fs::remove_dir("").unwrap();
18+
//~^ ERROR: blocking function call detected in an async body
1819
fs::copy("", "_").unwrap();
20+
//~^ ERROR: blocking function call detected in an async body
1921
let _ = fs::canonicalize("");
22+
//~^ ERROR: blocking function call detected in an async body
2023

2124
{
2225
fs::write("", "").unwrap();
26+
//~^ ERROR: blocking function call detected in an async body
2327
let _ = io::stdin();
2428
}
2529
let _stdout = io::stdout();
2630
let mut r: &[u8] = b"hello";
2731
let mut w: Vec<u8> = vec![];
2832
io::copy(&mut r, &mut w).unwrap();
33+
//~^ ERROR: blocking function call detected in an async body
2934
}
3035

3136
pub async fn non_blocking() {
3237
totally_thread_safe::sleep(Duration::from_secs(1)).await; // don't lint, not blocking
33-
34-
38+
3539
let mut r: &[u8] = b"hello";
3640
let mut w: Vec<u8> = vec![];
3741
tokio_io::copy(&mut r, &mut w).await; // don't lint, not blocking
@@ -45,17 +49,20 @@ struct SomeType(u8);
4549
impl AsyncTrait for SomeType {
4650
async fn foo(&self) {
4751
sleep(Duration::from_secs(self.0 as _));
52+
//~^ ERROR: blocking function call detected in an async body
4853
}
4954
}
5055

5156
fn do_something() {}
5257

5358
fn closures() {
5459
let _ = async || sleep(Duration::from_secs(1));
60+
//~^ ERROR: blocking function call detected in an async body
5561
let async_closure = async move |_a: i32| {
5662
let _ = 1;
5763
do_something();
5864
sleep(Duration::from_secs(1));
65+
//~^ ERROR: blocking function call detected in an async body
5966
};
6067
let non_async_closure = |_a: i32| {
6168
sleep(Duration::from_secs(1)); // don't lint, not async

0 commit comments

Comments
 (0)